diff --git a/backend-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationToolTest.kt b/backend-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationToolTest.kt index f7a7d4af..35947508 100644 --- a/backend-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationToolTest.kt +++ b/backend-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationToolTest.kt @@ -6,7 +6,6 @@ import org.junit.Test import kotlin.test.assertContentEquals class RustDerivationToolTest { - companion object { private const val SEED_PHRASE = "kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree shock scan" + @@ -14,12 +13,13 @@ class RustDerivationToolTest { } @Test - fun create_spending_key_does_not_mutate_passed_bytes() = runTest { - val bytesOne = Mnemonics.MnemonicCode(SEED_PHRASE).toEntropy() - val bytesTwo = Mnemonics.MnemonicCode(SEED_PHRASE).toEntropy() + fun create_spending_key_does_not_mutate_passed_bytes() = + runTest { + val bytesOne = Mnemonics.MnemonicCode(SEED_PHRASE).toEntropy() + val bytesTwo = Mnemonics.MnemonicCode(SEED_PHRASE).toEntropy() - RustDerivationTool.new().deriveUnifiedSpendingKey(bytesOne, networkId = 1, accountIndex = 0) + RustDerivationTool.new().deriveUnifiedSpendingKey(bytesOne, networkId = 1, accountIndex = 0) - assertContentEquals(bytesTwo, bytesOne) - } + assertContentEquals(bytesTwo, bytesOne) + } } 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 a69fd1df..936c97f0 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 @@ -14,7 +14,6 @@ import cash.z.ecc.android.sdk.internal.model.JniWalletSummary */ @Suppress("TooManyFunctions") interface Backend { - val networkId: Int suspend fun initBlockMetaDb(): Int @@ -53,7 +52,11 @@ interface Backend { * @throws RuntimeException as a common indicator of the operation failure */ @Throws(RuntimeException::class) - suspend fun createAccount(seed: ByteArray, treeState: ByteArray, recoverUntil: Long?): JniUnifiedSpendingKey + suspend fun createAccount( + seed: ByteArray, + treeState: ByteArray, + recoverUntil: Long? + ): JniUnifiedSpendingKey fun isValidShieldedAddr(addr: String): Boolean @@ -75,7 +78,10 @@ interface Backend { * @throws RuntimeException as a common indicator of the operation failure */ @Throws(RuntimeException::class) - suspend fun getMemoAsUtf8(txId: ByteArray, outputIndex: Int): String? + suspend fun getMemoAsUtf8( + txId: ByteArray, + outputIndex: Int + ): String? suspend fun getNearestRewindHeight(height: Long): Long @@ -139,7 +145,10 @@ interface Backend { * @throws RuntimeException as a common indicator of the operation failure */ @Throws(RuntimeException::class) - suspend fun scanBlocks(fromHeight: Long, limit: Long) + suspend fun scanBlocks( + fromHeight: Long, + limit: Long + ) /** * @throws RuntimeException as a common indicator of the operation failure @@ -157,6 +166,7 @@ interface Backend { suspend fun rewindBlockMetadataToHeight(height: Long) suspend fun getVerifiedTransparentBalance(address: String): Long + suspend fun getTotalTransparentBalance(address: String): Long /** diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/SdkDispatchers.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/SdkDispatchers.kt index db565335..3a5decd5 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/SdkDispatchers.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/SdkDispatchers.kt @@ -6,29 +6,29 @@ import java.util.concurrent.Executors internal object SdkExecutors { /** * Executor used for database IO that's shared with the Rust native library. - */ - /* + * * Based on internal discussion, keep the SDK internals confined to a single IO thread. * * We don't expect things to break, but we don't have the WAL enabled for SQLite so this * is a simple solution. */ - val DATABASE_IO = Executors.newSingleThreadExecutor { - Thread(it, "zc-io").apply { isDaemon = true } - } + val DATABASE_IO = + Executors.newSingleThreadExecutor { + Thread(it, "zc-io").apply { isDaemon = true } + } } object SdkDispatchers { /** * Dispatcher used for database IO that's shared with the Rust native library. - */ - /* + * * Based on internal discussion, keep the SDK internals confined to a single IO thread. * * We don't expect things to break, but we don't have the WAL enabled for SQLite so this * is a simple solution. + * + * Don't use `Dispatchers.IO.limitedParallelism(1)`. + * While it executes serially, each dispatch can be on a different thread. */ - // Don't use `Dispatchers.IO.limitedParallelism(1)`. - // While it executes serially, each dispatch can be on a different thread. val DATABASE_IO = SdkExecutors.DATABASE_IO.asCoroutineDispatcher() } diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/NativeLibraryLoader.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/NativeLibraryLoader.kt index 2e8906b7..2de990f9 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/NativeLibraryLoader.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/NativeLibraryLoader.kt @@ -46,6 +46,7 @@ internal class NativeLibraryLoader(private val libraryName: String) { } } -private suspend fun loadLibrarySuspend(libraryName: String) = withContext(Dispatchers.IO) { - System.loadLibrary(libraryName) -} +private suspend fun loadLibrarySuspend(libraryName: String) = + withContext(Dispatchers.IO) { + System.loadLibrary(libraryName) + } 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 18bff102..9b8edf85 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 @@ -25,7 +25,6 @@ class RustBackend private constructor( private val saplingSpendFile: File, private val saplingOutputFile: File, ) : Backend { - /** * This function deletes the data database file and the cache directory (compact blocks files) if set by input * parameters. @@ -35,7 +34,10 @@ class RustBackend private constructor( * * @return false in case of any required and failed deletion, true otherwise. */ - suspend fun clear(clearCache: Boolean = true, clearDataDb: Boolean = true): Boolean { + suspend fun clear( + clearCache: Boolean = true, + clearDataDb: Boolean = true + ): Boolean { var cacheClearResult = true var dataClearResult = true if (clearCache) { @@ -55,19 +57,21 @@ class RustBackend private constructor( // Wrapper Functions // - override suspend fun initBlockMetaDb() = withContext(SdkDispatchers.DATABASE_IO) { - initBlockMetaDb( - fsBlockDbRoot.absolutePath, - ) - } + override suspend fun initBlockMetaDb() = + withContext(SdkDispatchers.DATABASE_IO) { + initBlockMetaDb( + fsBlockDbRoot.absolutePath, + ) + } - override suspend fun initDataDb(seed: ByteArray?) = withContext(SdkDispatchers.DATABASE_IO) { - initDataDb( - dataDbFile.absolutePath, - seed, - networkId = networkId - ) - } + override suspend fun initDataDb(seed: ByteArray?) = + withContext(SdkDispatchers.DATABASE_IO) { + initDataDb( + dataDbFile.absolutePath, + seed, + networkId = networkId + ) + } override suspend fun createAccount( seed: ByteArray, @@ -108,15 +112,17 @@ class RustBackend private constructor( } } - override suspend fun getMemoAsUtf8(txId: ByteArray, outputIndex: Int) = - withContext(SdkDispatchers.DATABASE_IO) { - getMemoAsUtf8( - dataDbFile.absolutePath, - txId, - outputIndex, - networkId = networkId - ) - } + override suspend fun getMemoAsUtf8( + txId: ByteArray, + outputIndex: Int + ) = withContext(SdkDispatchers.DATABASE_IO) { + getMemoAsUtf8( + dataDbFile.absolutePath, + txId, + outputIndex, + networkId = networkId + ) + } override suspend fun writeBlockMetadata(blockMetadata: List) = withContext(SdkDispatchers.DATABASE_IO) { @@ -217,10 +223,11 @@ class RustBackend private constructor( override suspend fun getFullyScannedHeight() = withContext(SdkDispatchers.DATABASE_IO) { - val height = getFullyScannedHeight( - dataDbFile.absolutePath, - networkId = networkId - ) + val height = + getFullyScannedHeight( + dataDbFile.absolutePath, + networkId = networkId + ) if (-1L == height) { null @@ -231,10 +238,11 @@ class RustBackend private constructor( override suspend fun getMaxScannedHeight() = withContext(SdkDispatchers.DATABASE_IO) { - val height = getMaxScannedHeight( - dataDbFile.absolutePath, - networkId = networkId - ) + val height = + getMaxScannedHeight( + dataDbFile.absolutePath, + networkId = networkId + ) if (-1L == height) { null @@ -260,7 +268,10 @@ class RustBackend private constructor( } } - override suspend fun scanBlocks(fromHeight: Long, limit: Long) { + override suspend fun scanBlocks( + fromHeight: Long, + limit: Long + ) { return withContext(SdkDispatchers.DATABASE_IO) { scanBlocks( fsBlockDbRoot.absolutePath, @@ -287,19 +298,20 @@ class RustBackend private constructor( to: String, value: Long, memo: ByteArray? - ): ByteArray = withContext(SdkDispatchers.DATABASE_IO) { - createToAddress( - dataDbFile.absolutePath, - unifiedSpendingKey, - to, - value, - memo ?: ByteArray(0), - spendParamsPath = saplingSpendFile.absolutePath, - outputParamsPath = saplingOutputFile.absolutePath, - networkId = networkId, - useZip317Fees = IS_USE_ZIP_317_FEES - ) - } + ): ByteArray = + withContext(SdkDispatchers.DATABASE_IO) { + createToAddress( + dataDbFile.absolutePath, + unifiedSpendingKey, + to, + value, + memo ?: ByteArray(0), + spendParamsPath = saplingSpendFile.absolutePath, + outputParamsPath = saplingOutputFile.absolutePath, + networkId = networkId, + useZip317Fees = IS_USE_ZIP_317_FEES + ) + } override suspend fun shieldToAddress( account: Int, @@ -339,17 +351,13 @@ class RustBackend private constructor( ) } - override fun isValidShieldedAddr(addr: String) = - isValidShieldedAddress(addr, networkId = networkId) + override fun isValidShieldedAddr(addr: String) = isValidShieldedAddress(addr, networkId = networkId) - override fun isValidTransparentAddr(addr: String) = - isValidTransparentAddress(addr, networkId = networkId) + override fun isValidTransparentAddr(addr: String) = isValidTransparentAddress(addr, networkId = networkId) - override fun isValidUnifiedAddr(addr: String) = - isValidUnifiedAddress(addr, networkId = networkId) + override fun isValidUnifiedAddr(addr: String) = isValidUnifiedAddress(addr, networkId = networkId) - override fun getBranchIdForHeight(height: Long): Long = - branchIdForHeight(height, networkId = networkId) + override fun getBranchIdForHeight(height: Long): Long = branchIdForHeight(height, networkId = networkId) /** * Exposes all of the librustzcash functions along with helpers for loading the static library. @@ -397,7 +405,11 @@ class RustBackend private constructor( private external fun initBlockMetaDb(fsBlockDbRoot: String): Int @JvmStatic - private external fun initDataDb(dbDataPath: String, seed: ByteArray?, networkId: Int): Int + private external fun initDataDb( + dbDataPath: String, + seed: ByteArray?, + networkId: Int + ): Int @JvmStatic private external fun createAccount( @@ -422,7 +434,11 @@ class RustBackend private constructor( private external fun getSaplingReceiverForUnifiedAddress(ua: String): String? @JvmStatic - private external fun listTransparentReceivers(dbDataPath: String, account: Int, networkId: Int): Array + private external fun listTransparentReceivers( + dbDataPath: String, + account: Int, + networkId: Int + ): Array fun validateUnifiedSpendingKey(bytes: ByteArray) = isValidSpendingKey(bytes) @@ -430,13 +446,22 @@ class RustBackend private constructor( private external fun isValidSpendingKey(bytes: ByteArray): Boolean @JvmStatic - private external fun isValidShieldedAddress(addr: String, networkId: Int): Boolean + private external fun isValidShieldedAddress( + addr: String, + networkId: Int + ): Boolean @JvmStatic - private external fun isValidTransparentAddress(addr: String, networkId: Int): Boolean + private external fun isValidTransparentAddress( + addr: String, + networkId: Int + ): Boolean @JvmStatic - private external fun isValidUnifiedAddress(addr: String, networkId: Int): Boolean + private external fun isValidUnifiedAddress( + addr: String, + networkId: Int + ): Boolean @JvmStatic private external fun getMemoAsUtf8( @@ -563,7 +588,10 @@ class RustBackend private constructor( ): ByteArray @JvmStatic - private external fun branchIdForHeight(height: Long, networkId: Int): Long + private external fun branchIdForHeight( + height: Long, + networkId: Int + ): Long @JvmStatic @Suppress("LongParameterList") diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationTool.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationTool.kt index f4210c31..be2143bf 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationTool.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustDerivationTool.kt @@ -4,19 +4,16 @@ import cash.z.ecc.android.sdk.internal.Derivation import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey class RustDerivationTool private constructor() : Derivation { - override fun deriveUnifiedFullViewingKeys( seed: ByteArray, networkId: Int, numberOfAccounts: Int - ): Array = - deriveUnifiedFullViewingKeysFromSeed(seed, numberOfAccounts, networkId = networkId) + ): Array = deriveUnifiedFullViewingKeysFromSeed(seed, numberOfAccounts, networkId = networkId) override fun deriveUnifiedFullViewingKey( usk: JniUnifiedSpendingKey, networkId: Int - ): String = - deriveUnifiedFullViewingKey(usk.bytes, networkId = networkId) + ): String = deriveUnifiedFullViewingKey(usk.bytes, networkId = networkId) override fun deriveUnifiedSpendingKey( seed: ByteArray, @@ -24,8 +21,11 @@ class RustDerivationTool private constructor() : Derivation { accountIndex: Int ): JniUnifiedSpendingKey = deriveSpendingKey(seed, accountIndex, networkId = networkId) - override fun deriveUnifiedAddress(seed: ByteArray, networkId: Int, accountIndex: Int): String = - deriveUnifiedAddressFromSeed(seed, accountIndex = accountIndex, networkId = networkId) + override fun deriveUnifiedAddress( + seed: ByteArray, + networkId: Int, + accountIndex: Int + ): String = deriveUnifiedAddressFromSeed(seed, accountIndex = accountIndex, networkId = networkId) /** * Given a Unified Full Viewing Key string, return the associated Unified Address. @@ -38,8 +38,7 @@ class RustDerivationTool private constructor() : Derivation { override fun deriveUnifiedAddress( viewingKey: String, networkId: Int - ): String = - deriveUnifiedAddressFromViewingKey(viewingKey, networkId = networkId) + ): String = deriveUnifiedAddressFromViewingKey(viewingKey, networkId = networkId) companion object { suspend fun new(): Derivation { @@ -63,7 +62,10 @@ class RustDerivationTool private constructor() : Derivation { ): Array @JvmStatic - private external fun deriveUnifiedFullViewingKey(usk: ByteArray, networkId: Int): String + private external fun deriveUnifiedFullViewingKey( + usk: ByteArray, + networkId: Int + ): String @JvmStatic private external fun deriveUnifiedAddressFromSeed( @@ -73,6 +75,9 @@ class RustDerivationTool private constructor() : Derivation { ): String @JvmStatic - private external fun deriveUnifiedAddressFromViewingKey(key: String, networkId: Int): String + private external fun deriveUnifiedAddressFromViewingKey( + key: String, + networkId: Int + ): String } } diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniSubtreeRoot.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniSubtreeRoot.kt index 9fd50ba9..7714f249 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniSubtreeRoot.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniSubtreeRoot.kt @@ -24,7 +24,10 @@ class JniSubtreeRoot( } companion object { - fun new(rootHash: ByteArray, completingBlockHeight: Long): JniSubtreeRoot { + fun new( + rootHash: ByteArray, + completingBlockHeight: Long + ): JniSubtreeRoot { return JniSubtreeRoot( rootHash = rootHash, completingBlockHeight = completingBlockHeight diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniUnifiedSpendingKey.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniUnifiedSpendingKey.kt index 87d9eedb..ad9948b6 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniUnifiedSpendingKey.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniUnifiedSpendingKey.kt @@ -24,7 +24,6 @@ class JniUnifiedSpendingKey( */ val bytes: ByteArray ) { - // Override to prevent leaking key to logs override fun toString() = "JniUnifiedSpendingKey(account=$account, bytes=***)" diff --git a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt index bb0551cc..4d5d52a5 100644 --- a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt +++ b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/fixture/JniAccountBalanceFixture.kt @@ -3,7 +3,6 @@ package cash.z.ecc.android.sdk.internal.fixture import cash.z.ecc.android.sdk.internal.model.JniAccountBalance object JniAccountBalanceFixture { - const val ACCOUNT_ID: Int = 0 const val SAPLING_TOTAL_BALANCE: Long = 0L const val SAPLING_VERIFIED_BALANCE: Long = 0L @@ -12,7 +11,6 @@ object JniAccountBalanceFixture { account: Int = ACCOUNT_ID, saplingTotalBalance: Long = SAPLING_TOTAL_BALANCE, saplingVerifiedBalance: Long = SAPLING_VERIFIED_BALANCE, - ) = JniAccountBalance( account = account, saplingTotalBalance = saplingTotalBalance, diff --git a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniBlockMetaTest.kt b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniBlockMetaTest.kt index 8194c06a..05f304b1 100644 --- a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniBlockMetaTest.kt +++ b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniBlockMetaTest.kt @@ -7,13 +7,14 @@ import kotlin.test.assertIs class JniBlockMetaTest { @Test fun attributes_within_constraints() { - val instance = JniBlockMeta( - height = UInt.MAX_VALUE.toLong(), - hash = byteArrayOf(), - time = 0L, - saplingOutputsCount = UInt.MIN_VALUE.toLong(), - orchardOutputsCount = UInt.MIN_VALUE.toLong() - ) + val instance = + JniBlockMeta( + height = UInt.MAX_VALUE.toLong(), + hash = byteArrayOf(), + time = 0L, + saplingOutputsCount = UInt.MIN_VALUE.toLong(), + orchardOutputsCount = UInt.MIN_VALUE.toLong() + ) assertIs(instance) } diff --git a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniScanRangeTest.kt b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniScanRangeTest.kt index ada935b5..a3cba987 100644 --- a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniScanRangeTest.kt +++ b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniScanRangeTest.kt @@ -7,11 +7,12 @@ import kotlin.test.assertIs class JniScanRangeTest { @Test fun attributes_within_constraints() { - val instance = JniScanRange( - startHeight = UInt.MIN_VALUE.toLong(), - endHeight = UInt.MAX_VALUE.toLong(), - priority = 10 - ) + val instance = + JniScanRange( + startHeight = UInt.MIN_VALUE.toLong(), + endHeight = UInt.MAX_VALUE.toLong(), + priority = 10 + ) assertIs(instance) } diff --git a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniSubtreeRootTest.kt b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniSubtreeRootTest.kt index a9690f6d..c0e77dc7 100644 --- a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniSubtreeRootTest.kt +++ b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniSubtreeRootTest.kt @@ -7,10 +7,11 @@ import kotlin.test.assertIs class JniSubtreeRootTest { @Test fun attributes_within_constraints() { - val instance = JniSubtreeRoot( - rootHash = byteArrayOf(), - completingBlockHeight = UInt.MAX_VALUE.toLong() - ) + val instance = + JniSubtreeRoot( + rootHash = byteArrayOf(), + completingBlockHeight = UInt.MAX_VALUE.toLong() + ) assertIs(instance) } diff --git a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniWalletSummaryTest.kt b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniWalletSummaryTest.kt index 9a41c119..9ab967ae 100644 --- a/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniWalletSummaryTest.kt +++ b/backend-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/JniWalletSummaryTest.kt @@ -8,11 +8,12 @@ import kotlin.test.assertIs class JniWalletSummaryTest { @Test fun both_attribute_within_constraints() { - val instance = JniWalletSummary( - accountBalances = arrayOf(JniAccountBalanceFixture.new()), - progressNumerator = 1L, - progressDenominator = 100L - ) + val instance = + JniWalletSummary( + accountBalances = arrayOf(JniAccountBalanceFixture.new()), + progressNumerator = 1L, + progressDenominator = 100L + ) assertIs(instance) } diff --git a/build-conventions/src/main/kotlin/zcash-sdk.ktlint-conventions.gradle.kts b/build-conventions/src/main/kotlin/zcash-sdk.ktlint-conventions.gradle.kts index 386fca8b..ae2db55a 100644 --- a/build-conventions/src/main/kotlin/zcash-sdk.ktlint-conventions.gradle.kts +++ b/build-conventions/src/main/kotlin/zcash-sdk.ktlint-conventions.gradle.kts @@ -5,7 +5,7 @@ plugins { val ktlint by configurations.creating dependencies { - ktlint("com.pinterest:ktlint:${project.property("KTLINT_VERSION")}") { + ktlint("com.pinterest.ktlint:ktlint-cli:${project.property("KTLINT_VERSION")}") { attributes { attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL)) } diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/TransparentIntegrationTest.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/TransparentIntegrationTest.kt index b25021a8..68092753 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/TransparentIntegrationTest.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/TransparentIntegrationTest.kt @@ -7,17 +7,19 @@ import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +// TODO [#1224]: Refactor and re-enable disabled darkside tests +// TODO [#1224]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1224 + /** * Integration test to run in order to catch any regressions in transparent behavior. */ -// TODO [#1224]: Refactor and re-enable disabled darkside tests -// TODO [#1224]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1224 @RunWith(AndroidJUnit4::class) class TransparentIntegrationTest : DarksideTest() { @Before - fun setup() = runOnce { - // sithLord.await() - } + fun setup() = + runOnce { + // sithLord.await() + } @Test @Ignore("This test is broken") diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/InboundTxTests.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/InboundTxTests.kt index 4f014e1a..b7943fec 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/InboundTxTests.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/InboundTxTests.kt @@ -14,7 +14,6 @@ import org.junit.runner.RunWith // TODO [#1224]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1224 @RunWith(AndroidJUnit4::class) class InboundTxTests : ScopedTest() { - @Test @Ignore("Temporarily disabled") fun testTargetBlock_synced() { @@ -35,12 +34,15 @@ class InboundTxTests : ScopedTest() { @Ignore("Temporarily disabled") fun testTxCountAfter() { // add 2 transactions to block 663188 and 'mine' that block - addTransactions(targetTxBlock, tx663174, tx663188) + addTransactions(targetTxBlock, TX_663174, TX_663188) // sithLord.await(timeout = 30_000L, targetHeight = targetTxBlock) validator.validateTxCount(2) } - private fun addTransactions(targetHeight: BlockHeight, vararg txs: String) { + private fun addTransactions( + targetHeight: BlockHeight, + vararg txs: String + ) { // val overwriteBlockCount = 5 chainMaker // .stageEmptyBlocks(targetHeight, overwriteBlockCount) @@ -48,40 +50,42 @@ class InboundTxTests : ScopedTest() { .applyTipHeight(targetHeight) } - @Suppress("MaxLineLength") + @Suppress("MaxLineLength", "UnusedPrivateProperty", "ktlint:standard:max-line-length") companion object { - private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt" - private const val tx663174 = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt" - private const val tx663188 = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt" - private const val txIndexReorg = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-index-reorg/t1.txt" - private val txSend = arrayOf( - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/t-shielded-spend.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/c9e35e6ff444b071d63bf9bab6480409d6361760445c8a28d24179adb35c2495.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/72a29d7db511025da969418880b749f7fc0fc910cdb06f52193b5fa5c0401d9d.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/ff6ea36765dc29793775c7aa71de19fca039c5b5b873a0497866e9c4bc48af01.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/34e507cab780546f980176f3ff2695cd404917508c7e5ee18cc1d2ff3858cb08.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/6edf869063eccff3345676b0fed9f1aa6988fb2524e3d9ca7420a13cfadcd76c.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/de97394ae220c28a33ba78b944e82dabec8cb404a4407650b134b3d5950358c0.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/4eaa902279f8380914baf5bcc470d8b7c11d84fda809f67f517a7cb48912b87b.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/73c5edf8ffba774d99155121ccf07e67fbcf14284458f7e732751fea60d3bcbc.txt" - ) + private const val BLOCKS_URL = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt" + private const val TX_663174 = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt" + private const val TX_663188 = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt" + private const val TX_INDEX_REORG = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-index-reorg/t1.txt" + private val txSend = + arrayOf( + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/t-shielded-spend.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/c9e35e6ff444b071d63bf9bab6480409d6361760445c8a28d24179adb35c2495.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/72a29d7db511025da969418880b749f7fc0fc910cdb06f52193b5fa5c0401d9d.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/ff6ea36765dc29793775c7aa71de19fca039c5b5b873a0497866e9c4bc48af01.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/34e507cab780546f980176f3ff2695cd404917508c7e5ee18cc1d2ff3858cb08.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/6edf869063eccff3345676b0fed9f1aa6988fb2524e3d9ca7420a13cfadcd76c.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/de97394ae220c28a33ba78b944e82dabec8cb404a4407650b134b3d5950358c0.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/4eaa902279f8380914baf5bcc470d8b7c11d84fda809f67f517a7cb48912b87b.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/73c5edf8ffba774d99155121ccf07e67fbcf14284458f7e732751fea60d3bcbc.txt" + ) - private val txRecv = arrayOf( - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/8f064d23c66dc36e32445e5f3b50e0f32ac3ddb78cff21fb521eb6c19c07c99a.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/d2e7be14bbb308f9d4d68de424d622cbf774226d01cd63cc6f155fafd5cd212c.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e6566be3a4f9a80035dab8e1d97e40832a639e3ea938fb7972ea2f8482ff51ce.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e9527891b5d43d1ac72f2c0a3ac18a33dc5a0529aec04fa600616ed35f8123f8.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/4dcc95dd0a2f1f51bd64bb9f729b423c6de1690664a1b6614c75925e781662f7.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/75f2cdd2ff6a94535326abb5d9e663d53cbfa5f31ebb24b4d7e420e9440d41a2.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/7690c8ec740c1be3c50e2aedae8bf907ac81141ae8b6a134c1811706c73f49a6.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/71935e29127a7de0b96081f4c8a42a9c11584d83adedfaab414362a6f3d965cf.txt" - ) + private val txRecv = + arrayOf( + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/8f064d23c66dc36e32445e5f3b50e0f32ac3ddb78cff21fb521eb6c19c07c99a.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/d2e7be14bbb308f9d4d68de424d622cbf774226d01cd63cc6f155fafd5cd212c.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e6566be3a4f9a80035dab8e1d97e40832a639e3ea938fb7972ea2f8482ff51ce.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e9527891b5d43d1ac72f2c0a3ac18a33dc5a0529aec04fa600616ed35f8123f8.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/4dcc95dd0a2f1f51bd64bb9f729b423c6de1690664a1b6614c75925e781662f7.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/75f2cdd2ff6a94535326abb5d9e663d53cbfa5f31ebb24b4d7e420e9440d41a2.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/7690c8ec740c1be3c50e2aedae8bf907ac81141ae8b6a134c1811706c73f49a6.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/71935e29127a7de0b96081f4c8a42a9c11584d83adedfaab414362a6f3d965cf.txt" + ) private val firstBlock = BlockHeight.new(ZcashNetwork.Mainnet, 663150L) private val targetTxBlock = BlockHeight.new(ZcashNetwork.Mainnet, 663188L) - private const val lastBlockHash = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a" + private const val LAST_BLOCK_HASH = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a" private val sithLord = DarksideTestCoordinator() private val validator = sithLord.validator private val chainMaker = sithLord.chainMaker @@ -92,7 +96,7 @@ class InboundTxTests : ScopedTest() { sithLord.enterTheDarkside() chainMaker - .resetBlocks(blocksUrl, startHeight = firstBlock, tipHeight = targetTxBlock) + .resetBlocks(BLOCKS_URL, startHeight = firstBlock, tipHeight = targetTxBlock) .stageEmptyBlocks(firstBlock + 1, 100) .applyTipHeight(BlockHeight.new(ZcashNetwork.Mainnet, targetTxBlock.value - 1)) diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/ReorgSetupTest.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/ReorgSetupTest.kt index f4e82721..e627d3fe 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/ReorgSetupTest.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/ReorgSetupTest.kt @@ -13,7 +13,6 @@ import org.junit.runner.RunWith // TODO [#1224]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1224 @RunWith(AndroidJUnit4::class) class ReorgSetupTest : ScopedTest() { - /* private val birthdayHeight = BlockHeight.new(ZcashNetwork.Mainnet, 663150) private val targetHeight = BlockHeight.new(ZcashNetwork.Mainnet, 663250) @@ -26,20 +25,21 @@ class ReorgSetupTest : ScopedTest() { @Test @Ignore("Temporarily disabled") - fun testBeforeReorg_minHeight() = timeout(30_000L) { - // validate that we are synced, at least to the birthday height - // validator.validateMinHeightSynced(birthdayHeight) - } + fun testBeforeReorg_minHeight() = + timeout(30_000L) { + // validate that we are synced, at least to the birthday height + // validator.validateMinHeightSynced(birthdayHeight) + } @Test @Ignore("Temporarily disabled") - fun testBeforeReorg_maxHeight() = timeout(30_000L) { - // validate that we are not synced beyond the target height - // validator.validateMaxHeightSynced(targetHeight) - } + fun testBeforeReorg_maxHeight() = + timeout(30_000L) { + // validate that we are not synced beyond the target height + // validator.validateMaxHeightSynced(targetHeight) + } companion object { - private val sithLord = DarksideTestCoordinator() private val validator = sithLord.validator diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/ReorgSmallTest.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/ReorgSmallTest.kt index bf58e840..6bb3b0ee 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/ReorgSmallTest.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/ReorgSmallTest.kt @@ -12,7 +12,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ReorgSmallTest : ScopedTest() { - /* private val targetHeight = BlockHeight.new( ZcashNetwork.Mainnet, @@ -29,27 +28,29 @@ class ReorgSmallTest : ScopedTest() { @Test @Ignore("Temporarily disabled") - fun testBeforeReorg_latestBlockHash() = timeout(30_000L) { - // validator.validateBlockHash(targetHeight, hashBeforeReorg) - } + fun testBeforeReorg_latestBlockHash() = + timeout(30_000L) { + // validator.validateBlockHash(targetHeight, hashBeforeReorg) + } @Test @Ignore("Temporarily disabled") - fun testAfterReorg_callbackTriggered() = timeout(30_000L) { - hadReorg = false + fun testAfterReorg_callbackTriggered() = + timeout(30_000L) { + hadReorg = false // sithLord.triggerSmallReorg() // sithLord.await() - assertTrue(hadReorg) - } + assertTrue(hadReorg) + } @Test @Ignore("Temporarily disabled") - fun testAfterReorg_latestBlockHash() = timeout(30_000L) { - // validator.validateBlockHash(targetHeight, hashAfterReorg) - } + fun testAfterReorg_latestBlockHash() = + timeout(30_000L) { + // validator.validateBlockHash(targetHeight, hashAfterReorg) + } companion object { - private val sithLord = DarksideTestCoordinator() private val validator = sithLord.validator private var hadReorg = false diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/SetupTest.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/SetupTest.kt index 401899fd..a1b94b7d 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/SetupTest.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/reorgs/SetupTest.kt @@ -12,8 +12,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SetupTest : ScopedTest() { - -// @Test + // @Test // fun testFirstBlockExists() { // validator.validateHasBlock( // firstBlock @@ -38,8 +37,9 @@ class SetupTest : ScopedTest() { @Test @Ignore("This test is broken") fun tempTest() { - val phrase = "still champion voice habit trend flight survey between bitter process artefact blind" + - " carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" + val phrase = + "still champion voice habit trend flight survey between bitter process artefact blind" + + " carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" val result = SimpleMnemonics().toSeed(phrase.toCharArray()).toHex() assertEquals("abc", result) } @@ -54,12 +54,12 @@ class SetupTest : ScopedTest() { assertEquals("a", "${ent.toHex()}|${String(phrase)}") } + @Suppress("MaxLineLength", "UnusedPrivateProperty", "ktlint:standard:max-line-length") companion object { - @Suppress("MaxLineLength") - private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt" - private const val firstBlock = 663150 - private const val lastBlock = 663200 - private const val lastBlockHash = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a" + private const val BLOCKS_URL = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt" + private const val FIRST_BLOCK = 663150 + private const val LAST_BLOCK = 663200 + private const val LAST_BLOCK_HASH = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a" private val sithLord = DarksideTestCoordinator() private val validator = sithLord.validator diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTest.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTest.kt index 9da81699..572f14d4 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTest.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTest.kt @@ -11,6 +11,7 @@ open class DarksideTest : ScopedTest() { ranOnce = true } } + companion object { private var ranOnce = false } diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTestCoordinator.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTestCoordinator.kt index 912258f6..5920c3e1 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTestCoordinator.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTestCoordinator.kt @@ -49,28 +49,29 @@ class DarksideTestCoordinator(val wallet: TestWallet) { /** * Setup dependencies, including the synchronizer and the darkside API connection */ - fun enterTheDarkside(): DarksideTestCoordinator = runBlocking { - // verify that we are on the darkside - try { - initiate() - // In the future, we may want to have the SDK internally verify being on the darkside by matching the - // network type + fun enterTheDarkside(): DarksideTestCoordinator = + runBlocking { + // verify that we are on the darkside + try { + initiate() + // In the future, we may want to have the SDK internally verify being on the darkside by matching the + // network type - // synchronizer.getServerInfo().apply { - // assertTrue( - // "Error: not on the darkside", - // vendor.contains("dark", true) - // or chainName.contains("dark", true) - // ) - // } - } catch (error: StatusRuntimeException) { - Assert.fail( - "Error while fetching server status. Testing cannot begin due to:" + - " ${error.message} Caused by: ${error.cause} Verify that the server is running!" - ) + // synchronizer.getServerInfo().apply { + // assertTrue( + // "Error: not on the darkside", + // vendor.contains("dark", true) + // or chainName.contains("dark", true) + // ) + // } + } catch (error: StatusRuntimeException) { + Assert.fail( + "Error while fetching server status. Testing cannot begin due to:" + + " ${error.message} Caused by: ${error.cause} Verify that the server is running!" + ) + } + this@DarksideTestCoordinator } - this@DarksideTestCoordinator - } /** * Setup the synchronizer and darksidewalletd with their initial state @@ -80,7 +81,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) { darkside.reset(BlockHeightUnsafe(wallet.network.saplingActivationHeight.value)) } -// fun triggerSmallReorg() { + // fun triggerSmallReorg() { // darkside.setBlocksUrl(smallReorg) // } // @@ -89,6 +90,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) { // } // redo this as a call to wallet but add delay time to wallet join() function + /** * Waits for, at most, the given amount of time for the synchronizer to download and scan blocks * and reach a 'SYNCED' status. @@ -125,25 +127,27 @@ class DarksideTestCoordinator(val wallet: TestWallet) { // wallet.send(toAddress, memo, zatoshi, fromAccountIndex) // } - fun stall(delay: Long = 5000L) = runBlocking { - delay(delay) - } + @Suppress("ktlint:standard:no-consecutive-comments") + fun stall(delay: Long = 5000L) = + runBlocking { + delay(delay) + } // // Validation // inner class DarksideTestValidator { - - fun validateLatestHeight(height: BlockHeight) = runBlocking { - val info = synchronizer.processorInfo.first() - val networkBlockHeight = info.networkBlockHeight - assertTrue( - "Expected latestHeight of $height but the server last reported a height of" + - " $networkBlockHeight! Full details: $info", - networkBlockHeight == height - ) - } + fun validateLatestHeight(height: BlockHeight) = + runBlocking { + val info = synchronizer.processorInfo.first() + val networkBlockHeight = info.networkBlockHeight + assertTrue( + "Expected latestHeight of $height but the server last reported a height of" + + " $networkBlockHeight! Full details: $info", + networkBlockHeight == height + ) + } /* fun validateMinHeightSynced(minHeight: BlockHeight) = runBlocking { @@ -182,7 +186,10 @@ class DarksideTestCoordinator(val wallet: TestWallet) { assertEquals("Expected $count transactions but found $txCount instead!", count, txCount) } - fun validateMinBalance(available: Long = -1, total: Long = -1) { + fun validateMinBalance( + available: Long = -1, + total: Long = -1 + ) { val balance = synchronizer.saplingBalances.value if (available > 0) { assertTrue( @@ -198,7 +205,11 @@ class DarksideTestCoordinator(val wallet: TestWallet) { } } - suspend fun validateBalance(available: Long = -1, total: Long = -1, account: Account) { + suspend fun validateBalance( + available: Long = -1, + total: Long = -1, + account: Account + ) { val balance = synchronizer.processor.getBalanceInfo(account) if (available > 0) { assertEquals("invalid available balance", available, balance.available) @@ -224,33 +235,47 @@ class DarksideTestCoordinator(val wallet: TestWallet) { blocksUrl: String, startHeight: BlockHeight = DEFAULT_START_HEIGHT, tipHeight: BlockHeight = startHeight + 100 - ): DarksideChainMaker = apply { - darkside - .reset(BlockHeightUnsafe(startHeight.value)) - .stageBlocks(blocksUrl) - applyTipHeight(tipHeight) - } - - fun stageTransaction(url: String, targetHeight: BlockHeight): DarksideChainMaker = apply { - darkside.stageTransactions(url, BlockHeightUnsafe(targetHeight.value)) - } - - fun stageTransactions(targetHeight: BlockHeight, vararg urls: String): DarksideChainMaker = apply { - urls.forEach { - darkside.stageTransactions(it, BlockHeightUnsafe(targetHeight.value)) + ): DarksideChainMaker = + apply { + darkside + .reset(BlockHeightUnsafe(startHeight.value)) + .stageBlocks(blocksUrl) + applyTipHeight(tipHeight) } - } - fun stageEmptyBlocks(startHeight: BlockHeight, count: Int = 10): DarksideChainMaker = apply { - darkside.stageEmptyBlocks(BlockHeightUnsafe(startHeight.value), count) - } + fun stageTransaction( + url: String, + targetHeight: BlockHeight + ): DarksideChainMaker = + apply { + darkside.stageTransactions(url, BlockHeightUnsafe(targetHeight.value)) + } + + fun stageTransactions( + targetHeight: BlockHeight, + vararg urls: String + ): DarksideChainMaker = + apply { + urls.forEach { + darkside.stageTransactions(it, BlockHeightUnsafe(targetHeight.value)) + } + } + + fun stageEmptyBlocks( + startHeight: BlockHeight, + count: Int = 10 + ): DarksideChainMaker = + apply { + darkside.stageEmptyBlocks(BlockHeightUnsafe(startHeight.value), count) + } fun stageEmptyBlock() = stageEmptyBlocks(lastTipHeight!! + 1, 1) - fun applyTipHeight(tipHeight: BlockHeight): DarksideChainMaker = apply { - darkside.applyBlocks(BlockHeightUnsafe(tipHeight.value)) - lastTipHeight = tipHeight - } + fun applyTipHeight(tipHeight: BlockHeight): DarksideChainMaker = + apply { + darkside.applyBlocks(BlockHeightUnsafe(tipHeight.value)) + lastTipHeight = tipHeight + } /** * Creates a chain with 100 blocks and a transaction in the middle. @@ -281,7 +306,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) { } } - @Suppress("MaxLineLength") + @Suppress("MaxLineLength", "UnusedPrivateProperty") companion object { /** * This is a special localhost value on the Android emulator, which allows it to contact @@ -290,14 +315,15 @@ class DarksideTestCoordinator(val wallet: TestWallet) { const val COMPUTER_LOCALHOST = "10.0.2.2" // Block URLS - private const val beforeReorg = + private const val BEFORE_REORG = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt" - private const val smallReorg = + private const val SMALL_REORG = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-small-reorg.txt" - private const val largeReorg = + private const val LARGE_REORG = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-large-reorg.txt" private val DEFAULT_START_HEIGHT = BlockHeight.new(ZcashNetwork.Mainnet, 663150) private const val DEFAULT_SEED_PHRASE = - "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" + "still champion voice habit trend flight survey between bitter process artefact blind carbon truly" + + " provide dizzy crush flush breeze blouse charge solid fish spread" } } diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTestPrerequisites.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTestPrerequisites.kt index ee5b281e..9973f5ea 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTestPrerequisites.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/DarksideTestPrerequisites.kt @@ -39,15 +39,16 @@ open class DarksideTestPrerequisites { * ApplicationInfo object (`BuildInfo` is useless for libraries.) */ private fun isDebuggable(context: Context): Boolean { - val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - context.packageManager.getPackageInfo( - context.packageName, - PackageManager.PackageInfoFlags.of(0L) - ) - } else { - @Suppress("Deprecation") - context.packageManager.getPackageInfo(context.packageName, 0) - } + val packageInfo = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.packageManager.getPackageInfo( + context.packageName, + PackageManager.PackageInfoFlags.of(0L) + ) + } else { + @Suppress("Deprecation") + context.packageManager.getPackageInfo(context.packageName, 0) + } // Normally shouldn't be null, but could be with a MockContext return packageInfo.applicationInfo?.let { diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/ScopedTest.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/ScopedTest.kt index 0073d367..e8dc0224 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/ScopedTest.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/ScopedTest.kt @@ -26,21 +26,27 @@ open class ScopedTest(val defaultTimeout: Long = 2000L) : DarksideTestPrerequisi @Before fun start() { - testScope = CoroutineScope( - Job(classScope.coroutineContext[Job]!!) + newFixedThreadPoolContext( - 5, - this.javaClass.simpleName + testScope = + CoroutineScope( + Job(classScope.coroutineContext[Job]!!) + + newFixedThreadPoolContext( + 5, + this.javaClass.simpleName + ) ) - ) } @After - fun end() = runBlocking { - testScope.cancel() - testScope.coroutineContext[Job]?.join() - } + fun end() = + runBlocking { + testScope.cancel() + testScope.coroutineContext[Job]?.join() + } - fun timeout(duration: Long, block: suspend () -> Unit) = timeoutWith(testScope, duration, block) + fun timeout( + duration: Long, + block: suspend () -> Unit + ) = timeoutWith(testScope, duration, block) companion object { @JvmStatic @@ -49,20 +55,26 @@ open class ScopedTest(val defaultTimeout: Long = 2000L) : DarksideTestPrerequisi @BeforeClass @JvmStatic fun createScope() { - classScope = CoroutineScope( - SupervisorJob() + newFixedThreadPoolContext(2, this::class.java.simpleName) - ) + classScope = + CoroutineScope( + SupervisorJob() + newFixedThreadPoolContext(2, this::class.java.simpleName) + ) } @AfterClass @JvmStatic - fun destroyScope() = runBlocking { - classScope.cancel() - classScope.coroutineContext[Job]?.join() - } + fun destroyScope() = + runBlocking { + classScope.cancel() + classScope.coroutineContext[Job]?.join() + } @JvmStatic - fun timeoutWith(scope: CoroutineScope, duration: Long, block: suspend () -> Unit) { + fun timeoutWith( + scope: CoroutineScope, + duration: Long, + block: suspend () -> Unit + ) { scope.launch { delay(duration) val message = "ERROR: Test timed out after ${duration}ms" diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/SimpleMnemonics.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/SimpleMnemonics.kt index 7b885aff..28c86e5c 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/SimpleMnemonics.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/SimpleMnemonics.kt @@ -10,11 +10,18 @@ import java.util.Locale class SimpleMnemonics : MnemonicPlugin { override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language) + override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy() + override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars + override fun nextMnemonic(seed: ByteArray): CharArray = MnemonicCode(seed).chars + override fun nextMnemonicList(): List = MnemonicCode(WordCount.COUNT_24).words + override fun nextMnemonicList(seed: ByteArray): List = MnemonicCode(seed).words + override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed() + override fun toWordList(mnemonic: CharArray): List = MnemonicCode(mnemonic).words } diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt index 6f33bd53..2310c5d1 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt @@ -48,9 +48,10 @@ class TestWallet( alias = alias ) - val walletScope = CoroutineScope( - SupervisorJob() + newFixedThreadPoolContext(3, this.javaClass.simpleName) - ) + val walletScope = + CoroutineScope( + SupervisorJob() + newFixedThreadPoolContext(3, this.javaClass.simpleName) + ) // Although runBlocking isn't great, this usage is OK because this is only used within the // automated tests @@ -60,16 +61,17 @@ class TestWallet( private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed() private val shieldedSpendingKey = runBlocking { DerivationTool.getInstance().deriveUnifiedSpendingKey(seed, network = network, account) } - val synchronizer: SdkSynchronizer = Synchronizer.newBlocking( - context, - network, - alias, - endpoint, - seed, - startHeight, - // Using existing wallet init mode as simplification for the test - walletInitMode = WalletInitMode.ExistingWallet - ) as SdkSynchronizer + val synchronizer: SdkSynchronizer = + Synchronizer.newBlocking( + context, + network, + alias, + endpoint, + seed, + startHeight, + // Using existing wallet init mode as simplification for the test + walletInitMode = WalletInitMode.ExistingWallet + ) as SdkSynchronizer val available get() = synchronizer.saplingBalances.value?.available val unifiedAddress = @@ -85,12 +87,13 @@ class TestWallet( } suspend fun sync(timeout: Long = -1): TestWallet { - val killSwitch = walletScope.launch { - if (timeout > 0) { - delay(timeout) - throw TimeoutException("Failed to sync wallet within ${timeout}ms") + val killSwitch = + walletScope.launch { + if (timeout > 0) { + delay(timeout) + throw TimeoutException("Failed to sync wallet within ${timeout}ms") + } } - } // block until synced synchronizer.status.first { it == Synchronizer.Status.SYNCED } diff --git a/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/benchmark/StartupBenchmark.kt b/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/benchmark/StartupBenchmark.kt index a596b6c9..00332a44 100644 --- a/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/benchmark/StartupBenchmark.kt +++ b/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/benchmark/StartupBenchmark.kt @@ -20,6 +20,9 @@ import java.util.regex.Pattern import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds +// TODO [#809]: Enable macrobenchmark on CI +// TODO [#809]: https://github.com/zcash/zcash-android-wallet-sdk/issues/809 + /** * Purpose of this class is to provide a basic startup measurements, and captured system traces for investigating the * app's performance. It navigates to the device's home screen, and launches the default activity. @@ -29,11 +32,7 @@ import kotlin.time.Duration.Companion.seconds * We ideally run this against a physical device with Android SDK level 29, at least, as profiling is provided by this * version and later on. */ - -// TODO [#809]: Enable macrobenchmark on CI -// TODO [#809]: https://github.com/zcash/zcash-android-wallet-sdk/issues/809 class StartupBenchmark : UiTestPrerequisites() { - companion object { private const val APP_TARGET_PACKAGE_NAME = "cash.z.ecc.android.sdk.demoapp.mainnet" // NON-NLS private const val APP_TARGET_ACTIVITY_NAME = "cash.z.ecc.android.sdk.demoapp.MainActivity" // NON-NLS @@ -55,18 +54,19 @@ class StartupBenchmark : UiTestPrerequisites() { * This test starts the Demo-app on Home screen and measures its metrics. */ @Test - fun appStartup() = benchmarkRule.measureRepeated( - packageName = APP_TARGET_PACKAGE_NAME, - metrics = listOf(StartupTimingMetric()), - iterations = 5, - startupMode = StartupMode.COLD, - setupBlock = { - // Press home button before each run to ensure the starting activity isn't visible - pressHome() + fun appStartup() = + benchmarkRule.measureRepeated( + packageName = APP_TARGET_PACKAGE_NAME, + metrics = listOf(StartupTimingMetric()), + iterations = 5, + startupMode = StartupMode.COLD, + setupBlock = { + // Press home button before each run to ensure the starting activity isn't visible + pressHome() + } + ) { + startLegacyActivityAndWait() } - ) { - startLegacyActivityAndWait() - } /** * Advanced trace events startup test, which starts the Demo-app on the Home screen and then navigates to the @@ -74,24 +74,26 @@ class StartupBenchmark : UiTestPrerequisites() { */ @Test @OptIn(ExperimentalMetricApi::class) - fun tracesSdkStartup() = benchmarkRule.measureRepeated( - packageName = APP_TARGET_PACKAGE_NAME, - metrics = listOf( - TraceSectionMetric(ADDRESS_SCREEN_SECTION, TraceSectionMetric.Mode.First, false), - TraceSectionMetric(UNIFIED_ADDRESS_SECTION, TraceSectionMetric.Mode.First, false), - TraceSectionMetric(SAPLING_ADDRESS_SECTION, TraceSectionMetric.Mode.First, false), - TraceSectionMetric(TRANSPARENT_ADDRESS_SECTION, TraceSectionMetric.Mode.First, false) - ), - compilationMode = CompilationMode.Full(), - startupMode = StartupMode.COLD, - iterations = 5, - measureBlock = { - startLegacyActivityAndWait() - gotoAddressScreen() - waitForAddressScreen() - closeAddressScreen() - } - ) + fun tracesSdkStartup() = + benchmarkRule.measureRepeated( + packageName = APP_TARGET_PACKAGE_NAME, + metrics = + listOf( + TraceSectionMetric(ADDRESS_SCREEN_SECTION, TraceSectionMetric.Mode.First, false), + TraceSectionMetric(UNIFIED_ADDRESS_SECTION, TraceSectionMetric.Mode.First, false), + TraceSectionMetric(SAPLING_ADDRESS_SECTION, TraceSectionMetric.Mode.First, false), + TraceSectionMetric(TRANSPARENT_ADDRESS_SECTION, TraceSectionMetric.Mode.First, false) + ), + compilationMode = CompilationMode.Full(), + startupMode = StartupMode.COLD, + iterations = 5, + measureBlock = { + startLegacyActivityAndWait() + gotoAddressScreen() + waitForAddressScreen() + closeAddressScreen() + } + ) private fun MacrobenchmarkScope.closeAddressScreen() { // To close the Address screen and disconnect from SDK Synchronizer @@ -109,7 +111,10 @@ class StartupBenchmark : UiTestPrerequisites() { } } - private fun MacrobenchmarkScope.waitForAddressAppear(addressPattern: Pattern, timeout: Duration): Boolean { + private fun MacrobenchmarkScope.waitForAddressAppear( + addressPattern: Pattern, + timeout: Duration + ): Boolean { return device.waitFor(Until.hasObject(By.text(addressPattern)), timeout) } @@ -125,9 +130,10 @@ class StartupBenchmark : UiTestPrerequisites() { } private fun MacrobenchmarkScope.startLegacyActivityAndWait() { - val intent = Intent(Intent.ACTION_MAIN).apply { - component = ComponentName(APP_TARGET_PACKAGE_NAME, APP_TARGET_ACTIVITY_NAME) - } + val intent = + Intent(Intent.ACTION_MAIN).apply { + component = ComponentName(APP_TARGET_PACKAGE_NAME, APP_TARGET_ACTIVITY_NAME) + } startActivityAndWait(intent) } diff --git a/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/benchmark/SyncBlockchainBenchmark.kt b/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/benchmark/SyncBlockchainBenchmark.kt index d72122e8..d1ccf062 100644 --- a/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/benchmark/SyncBlockchainBenchmark.kt +++ b/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/benchmark/SyncBlockchainBenchmark.kt @@ -18,6 +18,9 @@ import org.junit.Test import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds +// TODO [#809]: Enable macrobenchmark on CI +// TODO [#809]: https://github.com/zcash/zcash-android-wallet-sdk/issues/809 + /** * This benchmark class provides measurements and captured custom traces for investigating SDK syncing mechanisms * with restricted blockchain range. It always resets the SDK before the next sync iteration. It uses UIAutomator to @@ -29,11 +32,7 @@ import kotlin.time.Duration.Companion.seconds * We ideally run this on a physical device with Android SDK level 29, at least, as profiling is provided by this * version and later on. */ - -// TODO [#809]: Enable macrobenchmark on CI -// TODO [#809]: https://github.com/zcash/zcash-android-wallet-sdk/issues/809 class SyncBlockchainBenchmark : UiTestPrerequisites() { - companion object { private const val APP_TARGET_PACKAGE_NAME = "cash.z.ecc.android.sdk.demoapp.mainnet" // NON-NLS private const val APP_TARGET_ACTIVITY_NAME = "cash.z.ecc.android.sdk.demoapp.MainActivity" // NON-NLS @@ -54,26 +53,28 @@ class SyncBlockchainBenchmark : UiTestPrerequisites() { */ @Test @OptIn(ExperimentalMetricApi::class) - fun tracesSyncBlockchain() = benchmarkRule.measureRepeated( - packageName = APP_TARGET_PACKAGE_NAME, - metrics = listOf( - TraceSectionMetric(BALANCE_SCREEN_SECTION, TraceSectionMetric.Mode.First, false), - TraceSectionMetric(BLOCKCHAIN_SYNC_SECTION, TraceSectionMetric.Mode.First, false), - TraceSectionMetric(DOWNLOAD_SECTION, TraceSectionMetric.Mode.First, false), - TraceSectionMetric(VALIDATION_SECTION, TraceSectionMetric.Mode.First, false), - TraceSectionMetric(SCAN_SECTION, TraceSectionMetric.Mode.First, false) - ), - compilationMode = CompilationMode.Full(), - startupMode = StartupMode.COLD, - iterations = 3, - measureBlock = { - startLegacyActivityAndWait() - resetSDK() - gotoBalanceScreen() - waitForBalanceScreen() - closeBalanceScreen() - } - ) + fun tracesSyncBlockchain() = + benchmarkRule.measureRepeated( + packageName = APP_TARGET_PACKAGE_NAME, + metrics = + listOf( + TraceSectionMetric(BALANCE_SCREEN_SECTION, TraceSectionMetric.Mode.First, false), + TraceSectionMetric(BLOCKCHAIN_SYNC_SECTION, TraceSectionMetric.Mode.First, false), + TraceSectionMetric(DOWNLOAD_SECTION, TraceSectionMetric.Mode.First, false), + TraceSectionMetric(VALIDATION_SECTION, TraceSectionMetric.Mode.First, false), + TraceSectionMetric(SCAN_SECTION, TraceSectionMetric.Mode.First, false) + ), + compilationMode = CompilationMode.Full(), + startupMode = StartupMode.COLD, + iterations = 3, + measureBlock = { + startLegacyActivityAndWait() + resetSDK() + gotoBalanceScreen() + waitForBalanceScreen() + closeBalanceScreen() + } + ) // TODO [#808]: Add demo-ui-lib module (and reference the hardcoded texts here) // TODO [#808]: https://github.com/zcash/zcash-android-wallet-sdk/issues/808 @@ -103,9 +104,10 @@ class SyncBlockchainBenchmark : UiTestPrerequisites() { } private fun MacrobenchmarkScope.startLegacyActivityAndWait() { - val intent = Intent(Intent.ACTION_MAIN).apply { - component = ComponentName(APP_TARGET_PACKAGE_NAME, APP_TARGET_ACTIVITY_NAME) - } + val intent = + Intent(Intent.ACTION_MAIN).apply { + component = ComponentName(APP_TARGET_PACKAGE_NAME, APP_TARGET_ACTIVITY_NAME) + } startActivityAndWait(intent) } diff --git a/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/test/Global.kt b/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/test/Global.kt index fac4ba54..163efead 100644 --- a/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/test/Global.kt +++ b/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/test/Global.kt @@ -6,7 +6,11 @@ import androidx.test.core.app.ApplicationProvider fun getAppContext(): Context = ApplicationProvider.getApplicationContext() -fun getStringResource(@StringRes resId: Int) = getAppContext().getString(resId) +fun getStringResource( + @StringRes resId: Int +) = getAppContext().getString(resId) -fun getStringResourceWithArgs(@StringRes resId: Int, vararg formatArgs: String) = - getAppContext().getString(resId, *formatArgs) +fun getStringResourceWithArgs( + @StringRes resId: Int, + vararg formatArgs: String +) = getAppContext().getString(resId, *formatArgs) diff --git a/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/test/UiAutomatorExt.kt b/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/test/UiAutomatorExt.kt index c7faa9e2..e2687b4f 100644 --- a/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/test/UiAutomatorExt.kt +++ b/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/test/UiAutomatorExt.kt @@ -6,10 +6,16 @@ import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiObject2 import kotlin.time.Duration -fun UiDevice.waitFor(condition: SearchCondition, timeout: Duration): Boolean { +fun UiDevice.waitFor( + condition: SearchCondition, + timeout: Duration +): Boolean { return wait(condition, timeout.inWholeMilliseconds) } -fun UiObject2.clickAndWaitFor(condition: EventCondition, timeout: Duration): Boolean { +fun UiObject2.clickAndWaitFor( + condition: EventCondition, + timeout: Duration +): Boolean { return clickAndWait(condition, timeout.inWholeMilliseconds) } diff --git a/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/test/UiTestPrerequisites.kt b/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/test/UiTestPrerequisites.kt index fe97eee2..3b9f86fd 100644 --- a/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/test/UiTestPrerequisites.kt +++ b/demo-app-benchmark-test/src/main/java/cash/z/ecc/android/sdk/demoapp/test/UiTestPrerequisites.kt @@ -25,8 +25,9 @@ open class UiTestPrerequisites { } private fun isScreenOn(): Boolean { - val powerService = ApplicationProvider.getApplicationContext() - .getSystemService(Context.POWER_SERVICE) as PowerManager + val powerService = + ApplicationProvider.getApplicationContext() + .getSystemService(Context.POWER_SERVICE) as PowerManager return powerService.isInteractive } @@ -40,7 +41,7 @@ open class UiTestPrerequisites { val keyguardService = ( ApplicationProvider.getApplicationContext() .getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager - ) + ) return keyguardService.isKeyguardLocked } diff --git a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt index 304023df..2d395a5e 100644 --- a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt +++ b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt @@ -37,7 +37,6 @@ import org.junit.Test * https://github.com/EdgeApp/eosjs-node-cli/blob/paul/cleanup/app.js */ class SampleCodeTest { - // /////////////////////////////////////////////////// // Seed derivation @Ignore("This test is not implemented") @@ -71,11 +70,12 @@ class SampleCodeTest { // /////////////////////////////////////////////////// // Get Address @Test - fun getAddress() = runBlocking { - val address = synchronizer.getUnifiedAddress(Account.DEFAULT) - assertFalse(address.isBlank()) - log("Address: $address") - } + fun getAddress() = + runBlocking { + val address = synchronizer.getUnifiedAddress(Account.DEFAULT) + assertFalse(address.isBlank()) + log("Address: $address") + } // /////////////////////////////////////////////////// // Derive address from Extended Full Viewing Key @@ -87,61 +87,64 @@ class SampleCodeTest { // Query latest block height @Test @OptIn(ExperimentalCoroutinesApi::class) - fun getLatestBlockHeightTest() = runTest { - // Test the result, only if there is no server communication problem. - runCatching { - LightWalletClient.new(context, lightwalletdHost).getLatestBlockHeight() - }.onFailure { - Twig.debug(it) { "Failed to retrieve data" } - }.onSuccess { - assertTrue(it is Response.Success) - Twig.debug { "Latest Block: ${(it as Response.Success).result}" } + fun getLatestBlockHeightTest() = + runTest { + // Test the result, only if there is no server communication problem. + runCatching { + LightWalletClient.new(context, lightwalletdHost).getLatestBlockHeight() + }.onFailure { + Twig.debug(it) { "Failed to retrieve data" } + }.onSuccess { + assertTrue(it is Response.Success) + Twig.debug { "Latest Block: ${(it as Response.Success).result}" } + } } - } // /////////////////////////////////////////////////// // Download compact block range @Test - @OptIn(ExperimentalCoroutinesApi::class) - fun getBlockRange() = runTest { - val blockRange = BlockHeightUnsafe( - BlockHeight.new( - ZcashNetwork.Mainnet, - 500_000 - ).value - )..BlockHeightUnsafe( - ( - BlockHeight.new( - ZcashNetwork.Mainnet, - 500_009 - ).value + fun getBlockRange() = + runTest { + @Suppress("ktlint:standard:multiline-expression-wrapping") + val blockRange = + BlockHeightUnsafe( + BlockHeight.new( + ZcashNetwork.Mainnet, + 500_000 + ).value + )..BlockHeightUnsafe( + ( + BlockHeight.new( + ZcashNetwork.Mainnet, + 500_009 + ).value + ) ) - ) - val lightWalletClient = LightWalletClient.new(context, lightwalletdHost) + val lightWalletClient = LightWalletClient.new(context, lightwalletdHost) - // Test the result, only if there is no server communication problem. - runCatching { - lightWalletClient.getBlockRange(blockRange) - }.onFailure { - Twig.debug(it) { "Failed to retrieve data" } - }.onSuccess { - it.onEach { response -> - assert(response is Response.Success) { "Server communication failed." } - } - .filterIsInstance>() - .map { response -> - response.result - }.toList() - .also { blocks -> - assertEquals(blockRange.endInclusive.value - blockRange.start.value, blocks.count()) - - blocks.forEachIndexed { i, block -> - log("Block #$i: height:${block.height} hash:${block.hash.toHex()}") - } + // Test the result, only if there is no server communication problem. + runCatching { + lightWalletClient.getBlockRange(blockRange) + }.onFailure { + Twig.debug(it) { "Failed to retrieve data" } + }.onSuccess { + it.onEach { response -> + assert(response is Response.Success) { "Server communication failed." } } + .filterIsInstance>() + .map { response -> + response.result + }.toList() + .also { blocks -> + assertEquals(blockRange.endInclusive.value - blockRange.start.value, blocks.count()) + + blocks.forEachIndexed { i, block -> + log("Block #$i: height:${block.height} hash:${block.hash.toHex()}") + } + } + } } - } // /////////////////////////////////////////////////// // Query account outgoing transactions @@ -175,17 +178,19 @@ class SampleCodeTest { // /////////////////////////////////////////////////// // Create a signed transaction (with memo) and broadcast @Test - fun submitTransaction() = runBlocking { - val amount = 0.123.convertZecToZatoshi() - val address = "ztestsapling1tklsjr0wyw0d58f3p7wufvrj2cyfv6q6caumyueadq8qvqt8lda6v6tpx474rfru9y6u75u7qnw" - val memo = "Test Transaction" - val spendingKey = DerivationTool.getInstance().deriveUnifiedSpendingKey( - seed, - ZcashNetwork.Mainnet, - Account.DEFAULT - ) - synchronizer.sendToAddress(spendingKey, amount, address, memo) - } + fun submitTransaction() = + runBlocking { + val amount = 0.123.convertZecToZatoshi() + val address = "ztestsapling1tklsjr0wyw0d58f3p7wufvrj2cyfv6q6caumyueadq8qvqt8lda6v6tpx474rfru9y6u75u7qnw" + val memo = "Test Transaction" + val spendingKey = + DerivationTool.getInstance().deriveUnifiedSpendingKey( + seed, + ZcashNetwork.Mainnet, + Account.DEFAULT + ) + synchronizer.sendToAddress(spendingKey, amount, address, memo) + } // ///////////////////////////////////////////////////// // Utility Functions @@ -196,18 +201,19 @@ class SampleCodeTest { private val lightwalletdHost = LightWalletEndpoint.Mainnet private val context = InstrumentationRegistry.getInstrumentation().targetContext - private val synchronizer: Synchronizer = run { - val network = ZcashNetwork.fromResources(context) - Synchronizer.newBlocking( - context, - network, - lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network), - seed = seed, - birthday = null, - // Using existing wallet init mode as simplification for the test - walletInitMode = WalletInitMode.ExistingWallet - ) - } + private val synchronizer: Synchronizer = + run { + val network = ZcashNetwork.fromResources(context) + Synchronizer.newBlocking( + context, + network, + lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network), + seed = seed, + birthday = null, + // Using existing wallet init mode as simplification for the test + walletInitMode = WalletInitMode.ExistingWallet + ) + } fun log(message: String?) = Twig.debug { message ?: "null" } } diff --git a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/preference/MockPreferenceProvider.kt b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/preference/MockPreferenceProvider.kt index df347386..110c0279 100644 --- a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/preference/MockPreferenceProvider.kt +++ b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/preference/MockPreferenceProvider.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.flowOf */ class MockPreferenceProvider(mutableMapFactory: () -> MutableMap = { mutableMapOf() }) : PreferenceProvider { - private val map = mutableMapFactory() override suspend fun getString(key: Key) = map[key.key] @@ -20,7 +19,10 @@ class MockPreferenceProvider(mutableMapFactory: () -> MutableMap() - .getSystemService(Context.POWER_SERVICE) as PowerManager + val powerService = + ApplicationProvider.getApplicationContext() + .getSystemService(Context.POWER_SERVICE) as PowerManager return powerService.isInteractive } @@ -38,7 +39,7 @@ open class UiTestPrerequisites { val keyguardService = ( ApplicationProvider.getApplicationContext() .getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager - ) + ) return keyguardService.isKeyguardLocked } diff --git a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/ui/common/FlowExtTest.kt b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/ui/common/FlowExtTest.kt index 5fc4dd2b..ff4121ef 100644 --- a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/ui/common/FlowExtTest.kt +++ b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/ui/common/FlowExtTest.kt @@ -17,52 +17,55 @@ import kotlin.time.TimeMark import kotlin.time.TimeSource class FlowExtTest { - @OptIn(ExperimentalTime::class, ExperimentalCoroutinesApi::class) @Test @SmallTest - fun throttle_one_sec() = runTest { - val timer = TimeSource.Monotonic.markNow() - val flow = flow { - while (timer.elapsedNow() <= 5.seconds) { - emit(1) - } - }.throttle(1.seconds) + fun throttle_one_sec() = + runTest { + val timer = TimeSource.Monotonic.markNow() + val flow = + flow { + while (timer.elapsedNow() <= 5.seconds) { + emit(1) + } + }.throttle(1.seconds) - var timeMark: TimeMark? = null - flow.collect { - if (timeMark == null) { - timeMark = TimeSource.Monotonic.markNow() - } else { - assert(timeMark!!.elapsedNow() >= 1.seconds) - timeMark = TimeSource.Monotonic.markNow() + var timeMark: TimeMark? = null + flow.collect { + if (timeMark == null) { + timeMark = TimeSource.Monotonic.markNow() + } else { + assert(timeMark!!.elapsedNow() >= 1.seconds) + timeMark = TimeSource.Monotonic.markNow() + } } } - } @OptIn(ExperimentalTime::class) - private fun raceConditionTest(duration: Duration): Boolean = runBlocking { - val flow = (0..1000).asFlow().throttle(duration) + private fun raceConditionTest(duration: Duration): Boolean = + runBlocking { + val flow = (0..1000).asFlow().throttle(duration) - val values = mutableListOf() - flow.collect { - values.add(it) + val values = mutableListOf() + flow.collect { + values.add(it) + } + + return@runBlocking values.zipWithNext().all { it.first <= it.second } } - return@runBlocking values.zipWithNext().all { it.first <= it.second } - } - @FlakyTest @Test - fun stressTest() = runBlocking { - for (i in 0..10) { - assertTrue { raceConditionTest(0.001.seconds) } + fun stressTest() = + runBlocking { + for (i in 0..10) { + assertTrue { raceConditionTest(0.001.seconds) } + } + for (i in 0..10) { + assertTrue { raceConditionTest(0.0001.seconds) } + } + for (i in 0..10) { + assertTrue { raceConditionTest(0.00001.seconds) } + } } - for (i in 0..10) { - assertTrue { raceConditionTest(0.0001.seconds) } - } - for (i in 0..10) { - assertTrue { raceConditionTest(0.00001.seconds) } - } - } } diff --git a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/ui/common/Global.kt b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/ui/common/Global.kt index 14668389..e2d591df 100644 --- a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/ui/common/Global.kt +++ b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/ui/common/Global.kt @@ -7,9 +7,13 @@ import androidx.annotation.StringRes import androidx.test.core.app.ApplicationProvider import java.util.Locale -fun getStringResource(@StringRes resId: Int) = ApplicationProvider.getApplicationContext().getString(resId) +fun getStringResource( + @StringRes resId: Int +) = ApplicationProvider.getApplicationContext().getString(resId) -fun getStringResourceWithArgs(@StringRes resId: Int, formatArgs: Array) = - ApplicationProvider.getApplicationContext().getString(resId, *formatArgs) +fun getStringResourceWithArgs( + @StringRes resId: Int, + formatArgs: Array +) = ApplicationProvider.getApplicationContext().getString(resId, *formatArgs) fun isLocaleRTL(locale: Locale) = TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL diff --git a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/ui/common/ScreenTimeoutTest.kt b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/ui/common/ScreenTimeoutTest.kt index d64a746c..f775504f 100644 --- a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/ui/common/ScreenTimeoutTest.kt +++ b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/ui/common/ScreenTimeoutTest.kt @@ -27,18 +27,18 @@ class ScreenTimeoutTest : UiTestPrerequisites() { @Test @MediumTest - fun acquireAndReleaseScreenTimeout() = runTest { - val testSetup = TestSetup(composeTestRule) + fun acquireAndReleaseScreenTimeout() = + runTest { + val testSetup = TestSetup(composeTestRule) - assertEquals(1, testSetup.getScreenTimeoutCount()) + assertEquals(1, testSetup.getScreenTimeoutCount()) - testSetup.mutableScreenTimeoutFlag.update { false } - composeTestRule.awaitIdle() - assertEquals(0, testSetup.getScreenTimeoutCount()) - } + testSetup.mutableScreenTimeoutFlag.update { false } + composeTestRule.awaitIdle() + assertEquals(0, testSetup.getScreenTimeoutCount()) + } private class TestSetup(composeTestRule: ComposeContentTestRule) { - val mutableScreenTimeoutFlag = MutableStateFlow(true) private val screenTimeout = ScreenTimeout() @@ -58,6 +58,7 @@ class ScreenTimeoutTest : UiTestPrerequisites() { } @Composable + @Suppress("ktlint:standard:function-naming") private fun TestView(disableScreenTimeout: Boolean) { if (disableScreenTimeout) { DisableScreenTimeout() diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/App.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/App.kt index 065d886c..3d87509b 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/App.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/App.kt @@ -4,7 +4,6 @@ import androidx.multidex.MultiDexApplication import cash.z.ecc.android.sdk.internal.Twig class App : MultiDexApplication() { - override fun onCreate() { super.onCreate() diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/BaseDemoFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/BaseDemoFragment.kt index 3e94c09b..4369bae0 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/BaseDemoFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/BaseDemoFragment.kt @@ -21,7 +21,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch abstract class BaseDemoFragment : Fragment() { - /** * Since the lightwalletClient is not a component that apps typically use, directly, we provide * this from one place. Everything that can be done with the service can/should be done with the @@ -83,7 +82,10 @@ abstract class BaseDemoFragment : Fragment() { /** * Convenience function to the given text to the clipboard. */ - open fun copyToClipboard(text: String, description: String = "Copied to clipboard!") { + open fun copyToClipboard( + text: String, + description: String = "Copied to clipboard!" + ) { (activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager) .setPrimaryClip(ClipData.newPlainText("DemoAppClip", text)) toast(description) diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ComposeActivity.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ComposeActivity.kt index 8580655b..bb68da12 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ComposeActivity.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ComposeActivity.kt @@ -33,6 +33,7 @@ class ComposeActivity : ComponentActivity() { } @Composable + @Suppress("ktlint:standard:function-naming") private fun MainContent() { when (walletViewModel.secretState.collectAsStateWithLifecycle().value) { SecretState.Loading -> { diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/MainActivity.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/MainActivity.kt index 8bdfe4aa..87c84333 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/MainActivity.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/MainActivity.kt @@ -63,14 +63,15 @@ class MainActivity : val navController = findNavController(R.id.nav_host_fragment) // Passing each menu ID as a set of Ids because each // menu should be considered as top level destinations. - appBarConfiguration = AppBarConfiguration( - setOf( - R.id.nav_home, R.id.nav_address, R.id.nav_balance, R.id.nav_block, R.id.nav_private_key, - R.id.nav_latest_height, R.id.nav_block_range, - R.id.nav_transactions, R.id.nav_utxos, R.id.nav_send - ), - drawerLayout - ) + appBarConfiguration = + AppBarConfiguration( + setOf( + R.id.nav_home, R.id.nav_address, R.id.nav_balance, R.id.nav_block, R.id.nav_private_key, + R.id.nav_latest_height, R.id.nav_block_range, + R.id.nav_transactions, R.id.nav_utxos, R.id.nav_send + ), + drawerLayout + ) setupActionBarWithNavController(navController, appBarConfiguration) navView.setupWithNavController(navController) drawerLayout.addDrawerListener(this) @@ -127,10 +128,11 @@ class MainActivity : lightwalletClient?.shutdown() } val network = ZcashNetwork.fromResources(applicationContext) - lightwalletClient = LightWalletClient.new( - applicationContext, - LightWalletEndpoint.defaultForNetwork(network) - ) + lightwalletClient = + LightWalletClient.new( + applicationContext, + LightWalletEndpoint.defaultForNetwork(network) + ) } private fun onFabClicked() { @@ -162,7 +164,10 @@ class MainActivity : } @Suppress("EmptyFunctionBlock") - override fun onDrawerSlide(drawerView: View, slideOffset: Float) { + override fun onDrawerSlide( + drawerView: View, + slideOffset: Float + ) { } override fun onDrawerOpened(drawerView: View) { @@ -176,9 +181,10 @@ class MainActivity : private fun newBrowserIntent(url: String): Intent { val uri = Uri.parse(url) - val intent = Intent(Intent.ACTION_VIEW, uri).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } + val intent = + Intent(Intent.ACTION_VIEW, uri).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } return intent } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Navigation.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Navigation.kt index 7f431d51..22ab0adb 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Navigation.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/Navigation.kt @@ -36,7 +36,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @Composable -@Suppress("LongMethod") +@Suppress("LongMethod", "ktlint:standard:function-naming") internal fun ComposeActivity.Navigation() { val navController = rememberNavController() @@ -187,10 +187,11 @@ private fun copyToClipboard( ) { val clipboardManager = context.getSystemService(ClipboardManager::class.java) - val data = ClipData.newPlainText( - tag, - textToCopy - ) + val data = + ClipData.newPlainText( + tag, + textToCopy + ) clipboardManager.setPrimaryClip(data) // Notify users with Snackbar only on Android level 32 and lower, as 33 and higher notifies users by its own system @@ -207,9 +208,10 @@ private fun copyToClipboard( private fun newBrowserIntent(url: String): Intent { val uri = Uri.parse(url) - val intent = Intent(Intent.ACTION_VIEW, uri).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } + val intent = + Intent(Intent.ACTION_VIEW, uri).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } return intent } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/SharedViewModel.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/SharedViewModel.kt index 0035c267..048801be 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/SharedViewModel.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/SharedViewModel.kt @@ -40,74 +40,78 @@ import kotlin.time.Duration.Companion.seconds * Shared mutable state for the demo */ class SharedViewModel(application: Application) : AndroidViewModel(application) { - private val _seedPhrase = MutableStateFlow(DemoConstants.INITIAL_SEED_WORDS) - private val _blockHeight = MutableStateFlow( - runBlocking { - BlockHeight.ofLatestCheckpoint( - getApplication(), - ZcashNetwork.fromResources(application) - ) - } - ) + private val _birthdayHeight = + MutableStateFlow( + runBlocking { + BlockHeight.ofLatestCheckpoint( + getApplication(), + ZcashNetwork.fromResources(application) + ) + } + ) // publicly, this is read-only val seedPhrase: StateFlow get() = _seedPhrase // publicly, this is read-only - val birthdayHeight: StateFlow get() = _blockHeight + val birthdayHeight: StateFlow get() = _birthdayHeight private val lockoutMutex = Mutex() private val lockoutIdFlow = MutableStateFlow(null) @OptIn(ExperimentalCoroutinesApi::class) - private val synchronizerOrLockout: Flow = lockoutIdFlow.flatMapLatest { lockoutId -> - if (null != lockoutId) { - flowOf(InternalSynchronizerStatus.Lockout(lockoutId)) - } else { - callbackFlow { - // Use a BIP-39 library to convert a seed phrase into a byte array. Most wallets already - // have the seed stored - val seedBytes = Mnemonics.MnemonicCode(seedPhrase.value).toSeed() + private val synchronizerOrLockout: Flow = + lockoutIdFlow.flatMapLatest { lockoutId -> + if (null != lockoutId) { + flowOf(InternalSynchronizerStatus.Lockout(lockoutId)) + } else { + callbackFlow { + // Use a BIP-39 library to convert a seed phrase into a byte array. Most wallets already + // have the seed stored + val seedBytes = Mnemonics.MnemonicCode(seedPhrase.value).toSeed() - val network = ZcashNetwork.fromResources(application) - val synchronizer = Synchronizer.new( - application, - network, - lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network), - seed = seedBytes, - birthday = if (BenchmarkingExt.isBenchmarking()) { - BlockHeight.new(ZcashNetwork.Mainnet, BenchmarkingBlockRangeFixture.new().start) - } else { - birthdayHeight.value - }, - // We use restore mode as this is always initialization with an older seed - walletInitMode = WalletInitMode.RestoreWallet, - alias = OLD_UI_SYNCHRONIZER_ALIAS - ) + val network = ZcashNetwork.fromResources(application) + val synchronizer = + Synchronizer.new( + application, + network, + lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network), + seed = seedBytes, + birthday = + if (BenchmarkingExt.isBenchmarking()) { + BlockHeight.new(ZcashNetwork.Mainnet, BenchmarkingBlockRangeFixture.new().start) + } else { + birthdayHeight.value + }, + // We use restore mode as this is always initialization with an older seed + walletInitMode = WalletInitMode.RestoreWallet, + alias = OLD_UI_SYNCHRONIZER_ALIAS + ) - send(InternalSynchronizerStatus.Available(synchronizer)) - awaitClose { - synchronizer.close() + send(InternalSynchronizerStatus.Available(synchronizer)) + awaitClose { + synchronizer.close() + } } } } - } // Note that seed and birthday shouldn't be changed once a synchronizer is first collected - val synchronizerFlow: StateFlow = synchronizerOrLockout.map { - when (it) { - is InternalSynchronizerStatus.Available -> it.synchronizer - is InternalSynchronizerStatus.Lockout -> null - } - }.stateIn( - viewModelScope, - SharingStarted.WhileSubscribed(DEFAULT_ANDROID_STATE_TIMEOUT.inWholeMilliseconds, 0), - initialValue = - null - ) + val synchronizerFlow: StateFlow = + synchronizerOrLockout.map { + when (it) { + is InternalSynchronizerStatus.Available -> it.synchronizer + is InternalSynchronizerStatus.Lockout -> null + } + }.stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(DEFAULT_ANDROID_STATE_TIMEOUT.inWholeMilliseconds, 0), + initialValue = + null + ) fun updateSeedPhrase(newPhrase: String?): Boolean { return if (isValidSeedPhrase(newPhrase)) { @@ -128,11 +132,12 @@ class SharedViewModel(application: Application) : AndroidViewModel(application) .filterIsInstance() .filter { it.id == lockoutId } .onFirst { - val didDelete = Synchronizer.erase( - appContext = getApplication(), - network = ZcashNetwork.fromResources(getApplication()), - alias = OLD_UI_SYNCHRONIZER_ALIAS - ) + val didDelete = + Synchronizer.erase( + appContext = getApplication(), + network = ZcashNetwork.fromResources(getApplication()), + alias = OLD_UI_SYNCHRONIZER_ALIAS + ) Twig.debug { "SDK erase result: $didDelete" } } @@ -162,6 +167,7 @@ class SharedViewModel(application: Application) : AndroidViewModel(application) private sealed class InternalSynchronizerStatus { class Available(val synchronizer: Synchronizer) : InternalSynchronizerStatus() + class Lockout(val id: UUID) : InternalSynchronizerStatus() } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/StrictModeHelper.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/StrictModeHelper.kt index 7ac42149..e1ffbedb 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/StrictModeHelper.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/StrictModeHelper.kt @@ -5,7 +5,6 @@ import android.os.StrictMode import cash.z.ecc.android.sdk.demoapp.util.AndroidApiVersion object StrictModeHelper { - fun enableStrictMode() { configureStrictMode() } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/WalletCoordinatorFactory.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/WalletCoordinatorFactory.kt index 8cb1c6b8..3b172e23 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/WalletCoordinatorFactory.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/WalletCoordinatorFactory.kt @@ -8,22 +8,24 @@ import cash.z.ecc.android.sdk.demoapp.util.LazyWithArgument import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow -private val lazy = LazyWithArgument { +private val lazy = + LazyWithArgument { /* * A flow of the user's stored wallet. Null indicates that no wallet has been stored. */ - val persistableWalletFlow = flow { - // EncryptedPreferenceSingleton.getInstance() is a suspending function, which is why we need - // the flow builder to provide a coroutine context. - val encryptedPreferenceProvider = EncryptedPreferenceSingleton.getInstance(it) + val persistableWalletFlow = + flow { + // EncryptedPreferenceSingleton.getInstance() is a suspending function, which is why we need + // the flow builder to provide a coroutine context. + val encryptedPreferenceProvider = EncryptedPreferenceSingleton.getInstance(it) - emitAll(EncryptedPreferenceKeys.PERSISTABLE_WALLET.observe(encryptedPreferenceProvider)) + emitAll(EncryptedPreferenceKeys.PERSISTABLE_WALLET.observe(encryptedPreferenceProvider)) + } + + WalletCoordinator( + context = it, + persistableWallet = persistableWalletFlow + ) } - WalletCoordinator( - context = it, - persistableWallet = persistableWalletFlow - ) -} - fun WalletCoordinator.Companion.getInstance(context: Context) = lazy.getInstance(context) diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt index b7f6f3df..32f00d3f 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt @@ -23,7 +23,6 @@ import kotlinx.coroutines.launch * that is used, update the `DemoConfig.seedWords` value. */ class GetAddressFragment : BaseDemoFragment() { - private lateinit var viewingKey: UnifiedFullViewingKey private fun displayAddress() { @@ -67,7 +66,10 @@ class GetAddressFragment : BaseDemoFragment() { reportTraceEvent(ProvideAddressBenchmarkTrace.Event.ADDRESS_SCREEN_START) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { super.onViewCreated(view, savedInstanceState) displayAddress() diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt index a0f892df..b9195f4c 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt @@ -34,7 +34,6 @@ import kotlinx.coroutines.launch */ @Suppress("TooManyFunctions") class GetBalanceFragment : BaseDemoFragment() { - override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBalanceBinding = FragmentGetBalanceBinding.inflate(layoutInflater) @@ -48,7 +47,10 @@ class GetBalanceFragment : BaseDemoFragment() { reportTraceEvent(SyncBlockchainBenchmarkTrace.Event.BALANCE_SCREEN_END) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { super.onViewCreated(view, savedInstanceState) val seedPhrase = sharedViewModel.seedPhrase.value @@ -110,25 +112,19 @@ class GetBalanceFragment : BaseDemoFragment() { } } - private fun onOrchardBalance( - orchardBalance: WalletBalance? - ) { + private fun onOrchardBalance(orchardBalance: WalletBalance?) { binding.orchardBalance.apply { text = orchardBalance.humanString() } } - private fun onSaplingBalance( - saplingBalance: WalletBalance? - ) { + private fun onSaplingBalance(saplingBalance: WalletBalance?) { binding.saplingBalance.apply { text = saplingBalance.humanString() } } - private fun onTransparentBalance( - transparentBalance: WalletBalance? - ) { + private fun onTransparentBalance(transparentBalance: WalletBalance?) { binding.transparentBalance.apply { text = transparentBalance.humanString() } @@ -136,26 +132,28 @@ class GetBalanceFragment : BaseDemoFragment() { binding.shield.apply { // TODO [#776]: Support variable fees // TODO [#776]: https://github.com/zcash/zcash-android-wallet-sdk/issues/776 - visibility = if ((transparentBalance?.available ?: Zatoshi(0)) > ZcashSdk.MINERS_FEE) { - View.VISIBLE - } else { - View.GONE - } + visibility = + if ((transparentBalance?.available ?: Zatoshi(0)) > ZcashSdk.MINERS_FEE) { + View.VISIBLE + } else { + View.GONE + } } } private fun onStatus(status: Synchronizer.Status) { Twig.debug { "Synchronizer status: $status" } // report benchmark event - val traceEvents = when (status) { - Synchronizer.Status.SYNCING -> { - SyncBlockchainBenchmarkTrace.Event.BLOCKCHAIN_SYNC_START + val traceEvents = + when (status) { + Synchronizer.Status.SYNCING -> { + SyncBlockchainBenchmarkTrace.Event.BLOCKCHAIN_SYNC_START + } + Synchronizer.Status.SYNCED -> { + SyncBlockchainBenchmarkTrace.Event.BLOCKCHAIN_SYNC_END + } + else -> null } - Synchronizer.Status.SYNCED -> { - SyncBlockchainBenchmarkTrace.Event.BLOCKCHAIN_SYNC_END - } - else -> null - } traceEvents?.let { reportTraceEvent(it) } binding.textStatus.text = "Status: $status" @@ -175,12 +173,13 @@ class GetBalanceFragment : BaseDemoFragment() { } @Suppress("MagicNumber") -private fun WalletBalance?.humanString() = if (null == this) { - "Calculating balance" -} else { - """ - Pending balance: ${pending.convertZatoshiToZecString(12)} - Available balance: ${available.convertZatoshiToZecString(12)} - Total balance: ${total.convertZatoshiToZecString(12)} - """.trimIndent() -} +private fun WalletBalance?.humanString() = + if (null == this) { + "Calculating balance" + } else { + """ + Pending balance: ${pending.convertZatoshiToZecString(12)} + Available balance: ${available.convertZatoshiToZecString(12)} + Total balance: ${total.convertZatoshiToZecString(12)} + """.trimIndent() + } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getblock/GetBlockFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getblock/GetBlockFragment.kt index 1b86b0d4..3d652a62 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getblock/GetBlockFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getblock/GetBlockFragment.kt @@ -7,15 +7,15 @@ import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockBinding import cash.z.ecc.android.sdk.demoapp.util.mainActivity +// TODO [#973]: Eliminate old UI demo-app +// TODO [#973]: https://github.com/zcash/zcash-android-wallet-sdk/issues/973 + /** * Retrieves a compact block from the lightwalletd server and displays basic information about it. * This demonstrates the basic ability to connect to the server, request a compact block and parse * the response. */ class GetBlockFragment : BaseDemoFragment() { - - // TODO [#973]: Eliminate old UI demo-app - // TODO [#973]: https://github.com/zcash/zcash-android-wallet-sdk/issues/973 /* var coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main) @@ -78,7 +78,10 @@ class GetBlockFragment : BaseDemoFragment() { // Android Lifecycle overrides // - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { super.onViewCreated(view, savedInstanceState) binding.buttonApply.setOnClickListener(::onApply) binding.buttonPrevious.setOnClickListener { @@ -92,7 +95,6 @@ class GetBlockFragment : BaseDemoFragment() { // // Base Fragment overrides // - - override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBlockBinding = - FragmentGetBlockBinding.inflate(layoutInflater) + @Suppress("MaxLineLength") + override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBlockBinding = FragmentGetBlockBinding.inflate(layoutInflater) } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getblockrange/GetBlockRangeFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getblockrange/GetBlockRangeFragment.kt index b78ccf5c..6e1d9399 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getblockrange/GetBlockRangeFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getblockrange/GetBlockRangeFragment.kt @@ -19,7 +19,6 @@ import kotlin.math.max * block ranges for instance, to find the block with the most shielded transactions in a range. */ class GetBlockRangeFragment : BaseDemoFragment() { - // TODO [#973]: Eliminate old UI demo-app // TODO [#973]: https://github.com/zcash/zcash-android-wallet-sdk/issues/973 @Suppress("MaxLineLength", "MagicNumber", "UNUSED_PARAMETER") @@ -75,16 +74,18 @@ class GetBlockRangeFragment : BaseDemoFragment() { private fun onApply(unused: View) { val network = ZcashNetwork.fromResources(requireApplicationContext()) - val start = max( - binding.textStartHeight.text.toString().toLongOrNull() - ?: network.saplingActivationHeight.value, - network.saplingActivationHeight.value - ) - val end = max( - binding.textEndHeight.text.toString().toLongOrNull() - ?: network.saplingActivationHeight.value, - network.saplingActivationHeight.value - ) + val start = + max( + binding.textStartHeight.text.toString().toLongOrNull() + ?: network.saplingActivationHeight.value, + network.saplingActivationHeight.value + ) + val end = + max( + binding.textEndHeight.text.toString().toLongOrNull() + ?: network.saplingActivationHeight.value, + network.saplingActivationHeight.value + ) if (start <= end) { @Suppress("TooGenericExceptionCaught") try { @@ -122,7 +123,10 @@ class GetBlockRangeFragment : BaseDemoFragment() { // Android Lifecycle overrides // - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { super.onViewCreated(view, savedInstanceState) binding.buttonApply.setOnClickListener(::onApply) } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getlatestheight/GetLatestHeightFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getlatestheight/GetLatestHeightFragment.kt index 6d30cfbe..35fd3ffc 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getlatestheight/GetLatestHeightFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getlatestheight/GetLatestHeightFragment.kt @@ -11,7 +11,6 @@ import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetLatestHeightBinding * instance. */ class GetLatestHeightFragment : BaseDemoFragment() { - // TODO [#973]: Eliminate old UI demo-app // TODO [#973]: https://github.com/zcash/zcash-android-wallet-sdk/issues/973 private fun displayLatestHeight() { diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt index d6716bb5..20ba6d7e 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt @@ -24,7 +24,6 @@ import kotlinx.coroutines.launch * HomeFragment. */ class GetPrivateKeyFragment : BaseDemoFragment() { - private lateinit var seedPhrase: String private lateinit var seed: ByteArray @@ -47,17 +46,19 @@ class GetPrivateKeyFragment : BaseDemoFragment() { lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { @Suppress("MagicNumber") - val spendingKey = DerivationTool.getInstance().deriveUnifiedSpendingKey( - seed, - ZcashNetwork.fromResources(requireApplicationContext()), - Account(5) - ) + val spendingKey = + DerivationTool.getInstance().deriveUnifiedSpendingKey( + seed, + ZcashNetwork.fromResources(requireApplicationContext()), + Account(5) + ) // derive the key that allows you to view but not spend transactions - val viewingKey = DerivationTool.getInstance().deriveUnifiedFullViewingKey( - spendingKey, - ZcashNetwork.fromResources(requireApplicationContext()) - ) + val viewingKey = + DerivationTool.getInstance().deriveUnifiedFullViewingKey( + spendingKey, + ZcashNetwork.fromResources(requireApplicationContext()) + ) // display the keys in the UI binding.textInfo.setText("Spending Key:\n$spendingKey\n\nViewing Key:\n$viewingKey") @@ -79,7 +80,10 @@ class GetPrivateKeyFragment : BaseDemoFragment() { return view } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { super.onViewCreated(view, savedInstanceState) displayKeys() } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/home/HomeFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/home/HomeFragment.kt index 546dae60..e345162e 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/home/HomeFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/home/HomeFragment.kt @@ -18,11 +18,12 @@ import kotlinx.coroutines.launch */ @Suppress("TooManyFunctions") class HomeFragment : BaseDemoFragment() { + override fun inflateBinding(layoutInflater: LayoutInflater) = FragmentHomeBinding.inflate(layoutInflater) - override fun inflateBinding(layoutInflater: LayoutInflater) = - FragmentHomeBinding.inflate(layoutInflater) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { super.onViewCreated(view, savedInstanceState) binding.textSeedPhrase.setOnClickListener(::onEditSeedPhrase) binding.buttonPaste.setOnClickListener(::onPasteSeedPhrase) diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt index 2288ac47..a15d66e4 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt @@ -111,7 +111,10 @@ class ListTransactionsFragment : BaseDemoFragment() { private val isSynced get() = status == Synchronizer.Status.SYNCED - override fun inflateBinding(layoutInflater: LayoutInflater): FragmentListUtxosBinding = - FragmentListUtxosBinding.inflate(layoutInflater) + @Suppress("MaxLineLength") + override fun inflateBinding(layoutInflater: LayoutInflater): FragmentListUtxosBinding = FragmentListUtxosBinding.inflate(layoutInflater) private fun initUi() { binding.inputAddress.setText(address) @@ -131,7 +131,10 @@ class ListUtxosFragment : BaseDemoFragment() { // private val now get() = System.currentTimeMillis() - private fun updateStatus(message: String, append: Boolean = true) { + private fun updateStatus( + message: String, + append: Boolean = true + ) { if (append) { binding.textStatus.text = "${binding.textStatus.text} $message" } else { @@ -140,7 +143,10 @@ class ListUtxosFragment : BaseDemoFragment() { Twig.debug { message } } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { super.onViewCreated(view, savedInstanceState) initUi() monitorStatus() diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/UtxoAdapter.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/UtxoAdapter.kt index ba7fbd68..de31e7ca 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/UtxoAdapter.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/UtxoAdapter.kt @@ -23,7 +23,6 @@ class UtxoAdapter : ListAdapter( ) = oldItem == newItem } ) { - override fun onCreateViewHolder( parent: ViewGroup, viewType: Int diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/UtxoViewHolder.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/UtxoViewHolder.kt index e1760afa..707a0dbf 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/UtxoViewHolder.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/UtxoViewHolder.kt @@ -25,7 +25,11 @@ class UtxoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { } @Suppress("MagicNumber") - private fun bindToHelper(amount: Zatoshi, minedHeight: BlockHeight?, time: Long?) { + private fun bindToHelper( + amount: Zatoshi, + minedHeight: BlockHeight?, + time: Long? + ) { amountText.text = amount.convertZatoshiToZecString() timeText.text = minedHeight?.let { time?.let { diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt index 439f565a..a9d8bc8b 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt @@ -34,7 +34,6 @@ import kotlinx.coroutines.launch */ @Suppress("TooManyFunctions") class SendFragment : BaseDemoFragment() { - private lateinit var amountInput: TextView private lateinit var addressInput: TextView @@ -67,12 +66,14 @@ class SendFragment : BaseDemoFragment() { // private fun initSendUi() { - amountInput = binding.inputAmount.apply { - setText(DemoConstants.SEND_AMOUNT.toZecString()) - } - addressInput = binding.inputAddress.apply { - setText(DemoConstants.TO_ADDRESS) - } + amountInput = + binding.inputAmount.apply { + setText(DemoConstants.SEND_AMOUNT.toZecString()) + } + addressInput = + binding.inputAddress.apply { + setText(DemoConstants.TO_ADDRESS) + } binding.buttonSend.setOnClickListener(::onSend) } @@ -130,10 +131,11 @@ class SendFragment : BaseDemoFragment() { private fun onBalance(balance: WalletBalance?) { this.balance = balance if (!isSyncing) { - binding.textBalance.text = """ + binding.textBalance.text = + """ Available balance: ${balance?.available.convertZatoshiToZecString(12)} Total balance: ${balance?.total.convertZatoshiToZecString(12)} - """.trimIndent() + """.trimIndent() } } @@ -191,7 +193,10 @@ class SendFragment : BaseDemoFragment() { return view } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { super.onViewCreated(view, savedInstanceState) initSendUi() monitorChanges() @@ -200,7 +205,6 @@ class SendFragment : BaseDemoFragment() { // // BaseDemoFragment overrides // - - override fun inflateBinding(layoutInflater: LayoutInflater): FragmentSendBinding = - FragmentSendBinding.inflate(layoutInflater) + @Suppress("MaxLineLength") + override fun inflateBinding(layoutInflater: LayoutInflater): FragmentSendBinding = FragmentSendBinding.inflate(layoutInflater) } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/fixture/WalletSnapshotFixture.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/fixture/WalletSnapshotFixture.kt index 01544e21..a5195425 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/fixture/WalletSnapshotFixture.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/fixture/WalletSnapshotFixture.kt @@ -10,7 +10,6 @@ import cash.z.ecc.android.sdk.model.Zatoshi @Suppress("MagicNumber") object WalletSnapshotFixture { - val STATUS = Synchronizer.Status.SYNCED val PROGRESS = PercentDecimal.ZERO_PERCENT val TRANSPARENT_BALANCE: WalletBalance = WalletBalance(Zatoshi(8), Zatoshi(1)) @@ -21,11 +20,12 @@ object WalletSnapshotFixture { @Suppress("LongParameterList") fun new( status: Synchronizer.Status = STATUS, - processorInfo: CompactBlockProcessor.ProcessorInfo = CompactBlockProcessor.ProcessorInfo( - null, - null, - null - ), + processorInfo: CompactBlockProcessor.ProcessorInfo = + CompactBlockProcessor.ProcessorInfo( + null, + null, + null + ), orchardBalance: WalletBalance = ORCHARD_BALANCE, saplingBalance: WalletBalance = SAPLING_BALANCE, transparentBalance: WalletBalance = TRANSPARENT_BALANCE, diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/AndroidPreferenceProvider.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/AndroidPreferenceProvider.kt index 51bdad44..49640aaa 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/AndroidPreferenceProvider.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/AndroidPreferenceProvider.kt @@ -25,8 +25,7 @@ import java.util.concurrent.Executors * For a given preference file, it is expected that only a single instance is constructed and that * this instance lives for the lifetime of the application. Constructing multiple instances will * potentially corrupt preference data and will leak resources. - */ -/* + * * Implementation note: EncryptedSharedPreferences are not thread-safe, so this implementation * confines them to a single background thread. */ @@ -34,13 +33,16 @@ class AndroidPreferenceProvider( private val sharedPreferences: SharedPreferences, private val dispatcher: CoroutineDispatcher ) : PreferenceProvider { - - override suspend fun hasKey(key: Key) = withContext(dispatcher) { - sharedPreferences.contains(key.key) - } + override suspend fun hasKey(key: Key) = + withContext(dispatcher) { + sharedPreferences.contains(key.key) + } @SuppressLint("ApplySharedPref") - override suspend fun putString(key: Key, value: String?) = withContext(dispatcher) { + override suspend fun putString( + key: Key, + value: String? + ) = withContext(dispatcher) { val editor = sharedPreferences.edit() editor.putString(key.key, value) @@ -50,65 +52,77 @@ class AndroidPreferenceProvider( Unit } - override suspend fun getString(key: Key) = withContext(dispatcher) { - sharedPreferences.getString(key.key, null) - } + override suspend fun getString(key: Key) = + withContext(dispatcher) { + sharedPreferences.getString(key.key, null) + } @OptIn(ExperimentalCoroutinesApi::class) - override fun observe(key: Key): Flow = callbackFlow { - val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> - // Callback on main thread + override fun observe(key: Key): Flow = + callbackFlow { + val listener = + SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> + // Callback on main thread + trySend(Unit) + } + sharedPreferences.registerOnSharedPreferenceChangeListener(listener) + + // Kickstart the emissions trySend(Unit) - } - sharedPreferences.registerOnSharedPreferenceChangeListener(listener) - // Kickstart the emissions - trySend(Unit) - - awaitClose { - sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener) - } - }.flowOn(dispatcher) + awaitClose { + sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener) + } + }.flowOn(dispatcher) companion object { - suspend fun newStandard(context: Context, filename: String): PreferenceProvider { + suspend fun newStandard( + context: Context, + filename: String + ): PreferenceProvider { /* * Because of this line, we don't want multiple instances of this object created * because we don't clean up the thread afterwards. */ val singleThreadedDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() - val sharedPreferences = withContext(singleThreadedDispatcher) { - context.getSharedPreferences(filename, Context.MODE_PRIVATE) - } + val sharedPreferences = + withContext(singleThreadedDispatcher) { + context.getSharedPreferences(filename, Context.MODE_PRIVATE) + } return AndroidPreferenceProvider(sharedPreferences, singleThreadedDispatcher) } - suspend fun newEncrypted(context: Context, filename: String): PreferenceProvider { + suspend fun newEncrypted( + context: Context, + filename: String + ): PreferenceProvider { /* * Because of this line, we don't want multiple instances of this object created * because we don't clean up the thread afterwards. */ val singleThreadedDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() - val mainKey = withContext(singleThreadedDispatcher) { - @Suppress("BlockingMethodInNonBlockingContext") - MasterKey.Builder(context).apply { - setKeyScheme(MasterKey.KeyScheme.AES256_GCM) - }.build() - } + val mainKey = + withContext(singleThreadedDispatcher) { + @Suppress("BlockingMethodInNonBlockingContext") + MasterKey.Builder(context).apply { + setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + }.build() + } - val sharedPreferences = withContext(singleThreadedDispatcher) { - @Suppress("BlockingMethodInNonBlockingContext") - EncryptedSharedPreferences.create( - context, - filename, - mainKey, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ) - } + val sharedPreferences = + withContext(singleThreadedDispatcher) { + @Suppress("BlockingMethodInNonBlockingContext") + EncryptedSharedPreferences.create( + context, + filename, + mainKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } return AndroidPreferenceProvider(sharedPreferences, singleThreadedDispatcher) } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/EncryptedPreferenceKeys.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/EncryptedPreferenceKeys.kt index b2945dc9..73731e8e 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/EncryptedPreferenceKeys.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/EncryptedPreferenceKeys.kt @@ -3,6 +3,5 @@ package cash.z.ecc.android.sdk.demoapp.preference import cash.z.ecc.android.sdk.demoapp.preference.model.entry.Key object EncryptedPreferenceKeys { - val PERSISTABLE_WALLET = PersistableWalletPreferenceDefault(Key("persistable_wallet")) } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/EncryptedPreferenceSingleton.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/EncryptedPreferenceSingleton.kt index 7b322bc4..4c8dba37 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/EncryptedPreferenceSingleton.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/EncryptedPreferenceSingleton.kt @@ -5,12 +5,12 @@ import cash.z.ecc.android.sdk.demoapp.preference.api.PreferenceProvider import cash.z.ecc.android.sdk.demoapp.util.SuspendingLazy object EncryptedPreferenceSingleton { - private const val PREF_FILENAME = "co.electriccoin.zcash.encrypted" - private val lazy = SuspendingLazy { - AndroidPreferenceProvider.newEncrypted(it, PREF_FILENAME) - } + private val lazy = + SuspendingLazy { + AndroidPreferenceProvider.newEncrypted(it, PREF_FILENAME) + } suspend fun getInstance(context: Context) = lazy.getInstance(context) } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/PersistableWalletPreferenceDefault.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/PersistableWalletPreferenceDefault.kt index 05bbf158..30bc10e3 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/PersistableWalletPreferenceDefault.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/PersistableWalletPreferenceDefault.kt @@ -9,7 +9,6 @@ import org.json.JSONObject data class PersistableWalletPreferenceDefault( override val key: Key ) : PreferenceDefault { - override suspend fun getValue(preferenceProvider: PreferenceProvider) = preferenceProvider.getString(key)?.let { PersistableWallet.from(JSONObject(it)) } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/api/PreferenceProvider.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/api/PreferenceProvider.kt index e4abc31a..3e8aa428 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/api/PreferenceProvider.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/api/PreferenceProvider.kt @@ -4,10 +4,12 @@ import cash.z.ecc.android.sdk.demoapp.preference.model.entry.Key import kotlinx.coroutines.flow.Flow interface PreferenceProvider { - suspend fun hasKey(key: Key): Boolean - suspend fun putString(key: Key, value: String?) + suspend fun putString( + key: Key, + value: String? + ) suspend fun getString(key: Key): String? diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/BooleanPreferenceDefault.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/BooleanPreferenceDefault.kt index 99166aba..6d153001 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/BooleanPreferenceDefault.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/BooleanPreferenceDefault.kt @@ -6,19 +6,22 @@ data class BooleanPreferenceDefault( override val key: Key, private val defaultValue: Boolean ) : PreferenceDefault { - @Suppress("SwallowedException") - override suspend fun getValue(preferenceProvider: PreferenceProvider) = preferenceProvider.getString(key)?.let { - try { - it.toBooleanStrict() - } catch (e: IllegalArgumentException) { - // TODO [#32]: Log coercion failure instead of just silently returning default - // TODO [#32]: https://github.com/zcash/zcash-android-wallet-sdk/issues/32 - defaultValue - } - } ?: defaultValue + override suspend fun getValue(preferenceProvider: PreferenceProvider) = + preferenceProvider.getString(key)?.let { + try { + it.toBooleanStrict() + } catch (e: IllegalArgumentException) { + // TODO [#32]: Log coercion failure instead of just silently returning default + // TODO [#32]: https://github.com/zcash/zcash-android-wallet-sdk/issues/32 + defaultValue + } + } ?: defaultValue - override suspend fun putValue(preferenceProvider: PreferenceProvider, newValue: Boolean) { + override suspend fun putValue( + preferenceProvider: PreferenceProvider, + newValue: Boolean + ) { preferenceProvider.putString(key, newValue.toString()) } } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/IntegerPreferenceDefault.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/IntegerPreferenceDefault.kt index 9b7eee7a..849c063f 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/IntegerPreferenceDefault.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/IntegerPreferenceDefault.kt @@ -6,18 +6,21 @@ data class IntegerPreferenceDefault( override val key: Key, private val defaultValue: Int ) : PreferenceDefault { + override suspend fun getValue(preferenceProvider: PreferenceProvider) = + preferenceProvider.getString(key)?.let { + try { + it.toInt() + } catch (e: NumberFormatException) { + // TODO [#32]: Log coercion failure instead of just silently returning default + // TODO [#32]: https://github.com/zcash/zcash-android-wallet-sdk/issues/32 + defaultValue + } + } ?: defaultValue - override suspend fun getValue(preferenceProvider: PreferenceProvider) = preferenceProvider.getString(key)?.let { - try { - it.toInt() - } catch (e: NumberFormatException) { - // TODO [#32]: Log coercion failure instead of just silently returning default - // TODO [#32]: https://github.com/zcash/zcash-android-wallet-sdk/issues/32 - defaultValue - } - } ?: defaultValue - - override suspend fun putValue(preferenceProvider: PreferenceProvider, newValue: Int) { + override suspend fun putValue( + preferenceProvider: PreferenceProvider, + newValue: Int + ) { preferenceProvider.putString(key, newValue.toString()) } } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/PreferenceDefault.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/PreferenceDefault.kt index 60b594ca..726d86b4 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/PreferenceDefault.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/PreferenceDefault.kt @@ -10,8 +10,7 @@ import kotlinx.coroutines.flow.map * multiple parts of the code can fetch the same preference without duplication or accidental * variation in default value. Clients define the key and default value together, rather than just * the key. - */ -/* + * * API note: the default value is not available through the public interface in order to prevent * clients from accidentally using the default value instead of the preference value. * @@ -21,7 +20,6 @@ import kotlinx.coroutines.flow.map * and perhaps many Integer values will also fit within the autoboxing cache. */ interface PreferenceDefault { - val key: Key /** @@ -34,14 +32,18 @@ interface PreferenceDefault { * @param preferenceProvider Provides actual preference values. * @param newValue New value to write. */ - suspend fun putValue(preferenceProvider: PreferenceProvider, newValue: T) + suspend fun putValue( + preferenceProvider: PreferenceProvider, + newValue: T + ) /** * @param preferenceProvider Provides actual preference values. * @return Flow that emits preference changes. Note that implementations should emit an initial value * indicating what was stored in the preferences, in addition to subsequent updates. */ - fun observe(preferenceProvider: PreferenceProvider): Flow = preferenceProvider.observe(key) - .map { getValue(preferenceProvider) } - .distinctUntilChanged() + fun observe(preferenceProvider: PreferenceProvider): Flow = + preferenceProvider.observe(key) + .map { getValue(preferenceProvider) } + .distinctUntilChanged() } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/StringPreferenceDefault.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/StringPreferenceDefault.kt index 35b41d34..fd58b321 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/StringPreferenceDefault.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/preference/model/entry/StringPreferenceDefault.kt @@ -6,11 +6,14 @@ data class StringPreferenceDefault( override val key: Key, private val defaultValue: String ) : PreferenceDefault { + override suspend fun getValue(preferenceProvider: PreferenceProvider) = + preferenceProvider.getString(key) + ?: defaultValue - override suspend fun getValue(preferenceProvider: PreferenceProvider) = preferenceProvider.getString(key) - ?: defaultValue - - override suspend fun putValue(preferenceProvider: PreferenceProvider, newValue: String) { + override suspend fun putValue( + preferenceProvider: PreferenceProvider, + newValue: String + ) { preferenceProvider.putString(key, newValue) } } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/BindCompLocalProvider.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/BindCompLocalProvider.kt index c3dce172..25d2973e 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/BindCompLocalProvider.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/BindCompLocalProvider.kt @@ -9,6 +9,7 @@ import cash.z.ecc.android.sdk.ext.collectWith import kotlinx.coroutines.flow.map @Composable +@Suppress("ktlint:standard:function-naming") fun ComponentActivity.BindCompLocalProvider(content: @Composable () -> Unit) { val screenTimeout = ScreenTimeout() observeScreenTimeoutFlag(screenTimeout) diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/FlowExt.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/FlowExt.kt index bc7af51d..a9531792 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/FlowExt.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/FlowExt.kt @@ -18,41 +18,43 @@ import kotlin.time.TimeSource fun Flow.throttle( duration: Duration, timeSource: TimeSource = TimeSource.Monotonic -): Flow = flow { - coroutineScope { - val context = coroutineContext - val mutex = Mutex() +): Flow = + flow { + coroutineScope { + val context = coroutineContext + val mutex = Mutex() - var timeMark = timeSource.markNow() - var delayEmit: Deferred? = null - var firstValue = true - var valueToEmit: T - collect { value -> - if (firstValue) { - firstValue = false - emit(value) - timeMark = timeSource.markNow() - return@collect - } - delayEmit?.cancel() - valueToEmit = value - - if (timeMark.elapsedNow() >= duration) { - mutex.withLock { - emit(valueToEmit) + var timeMark = timeSource.markNow() + var delayEmit: Deferred? = null + var firstValue = true + var valueToEmit: T + collect { value -> + if (firstValue) { + firstValue = false + emit(value) timeMark = timeSource.markNow() + return@collect } - } else { - delayEmit = async(Dispatchers.Default) { + delayEmit?.cancel() + valueToEmit = value + + if (timeMark.elapsedNow() >= duration) { mutex.withLock { - delay(duration) - withContext(context) { - emit(valueToEmit) - } + emit(valueToEmit) timeMark = timeSource.markNow() } + } else { + delayEmit = + async(Dispatchers.Default) { + mutex.withLock { + delay(duration) + withContext(context) { + emit(valueToEmit) + } + timeMark = timeSource.markNow() + } + } } } } } -} diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/ScreenTimeout.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/ScreenTimeout.kt index 4965dbb0..44577621 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/ScreenTimeout.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/common/ScreenTimeout.kt @@ -29,6 +29,7 @@ class ScreenTimeout { val LocalScreenTimeout = compositionLocalOf { ScreenTimeout() } @Composable +@Suppress("ktlint:standard:function-naming") fun DisableScreenTimeout() { val screenTimeout = LocalScreenTimeout.current DisposableEffect(screenTimeout) { diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/addresses/view/AddressesView.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/addresses/view/AddressesView.kt index 160bc02d..cbfbcf05 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/addresses/view/AddressesView.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/addresses/view/AddressesView.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.flow @Preview(name = "Addresses") @Composable +@Suppress("ktlint:standard:function-naming") private fun ComposablePreview() { MaterialTheme { // TODO [#1090]: Demo: Add Addresses and Transactions Compose Previews @@ -43,6 +44,7 @@ private fun ComposablePreview() { * @param copyToClipboard First string is a tag, the second string is the text to copy. */ @Composable +@Suppress("ktlint:standard:function-naming") fun Addresses( synchronizer: Synchronizer, copyToClipboard: (String, String) -> Unit, @@ -55,11 +57,12 @@ fun Addresses( ) { paddingValues -> // TODO [#846]: Slow addresses providing // TODO [#846]: https://github.com/zcash/zcash-android-wallet-sdk/issues/846 - val walletAddresses = flow { - emit(WalletAddresses.new(synchronizer)) - }.collectAsState( - initial = null - ).value + val walletAddresses = + flow { + emit(WalletAddresses.new(synchronizer)) + }.collectAsState( + initial = null + ).value if (null != walletAddresses) { AddressesMainContent( paddingValues = paddingValues, @@ -72,6 +75,7 @@ fun Addresses( @Composable @OptIn(ExperimentalMaterial3Api::class) +@Suppress("ktlint:standard:function-naming") private fun AddressesTopAppBar(onBack: () -> Unit) { TopAppBar( title = { Text(text = stringResource(id = R.string.menu_address)) }, @@ -89,6 +93,7 @@ private fun AddressesTopAppBar(onBack: () -> Unit) { } @Composable +@Suppress("ktlint:standard:function-naming") private fun AddressesMainContent( paddingValues: PaddingValues, addresses: WalletAddresses, diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/balance/view/BalanceView.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/balance/view/BalanceView.kt index 90826ae8..6ca5ec3b 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/balance/view/BalanceView.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/balance/view/BalanceView.kt @@ -31,6 +31,7 @@ import cash.z.ecc.android.sdk.model.toZecString @Preview(name = "Balance") @Composable +@Suppress("ktlint:standard:function-naming") private fun ComposablePreview() { MaterialTheme { Balance( @@ -44,6 +45,7 @@ private fun ComposablePreview() { } @Composable +@Suppress("ktlint:standard:function-naming") fun Balance( walletSnapshot: WalletSnapshot, sendState: SendState, @@ -69,6 +71,7 @@ fun Balance( @Composable @OptIn(ExperimentalMaterial3Api::class) +@Suppress("ktlint:standard:function-naming") private fun BalanceTopAppBar( onBack: () -> Unit, onRefresh: () -> Unit @@ -97,6 +100,7 @@ private fun BalanceTopAppBar( } @Composable +@Suppress("ktlint:standard:function-naming") private fun BalanceMainContent( paddingValues: PaddingValues, walletSnapshot: WalletSnapshot, diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/view/HomeView.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/view/HomeView.kt index 5afad436..354c0635 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/view/HomeView.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/view/HomeView.kt @@ -33,6 +33,7 @@ import cash.z.ecc.android.sdk.demoapp.ui.screen.home.viewmodel.WalletSnapshot @Preview(name = "Home") @Composable +@Suppress("ktlint:standard:function-naming") private fun ComposablePreviewHome() { MaterialTheme { Home( @@ -49,8 +50,8 @@ private fun ComposablePreviewHome() { } } -@Suppress("LongParameterList") @Composable +@Suppress("LongParameterList", "ktlint:standard:function-naming") fun Home( walletSnapshot: WalletSnapshot, isTestnet: Boolean, @@ -83,6 +84,7 @@ fun Home( @Composable @OptIn(ExperimentalMaterial3Api::class) +@Suppress("ktlint:standard:function-naming") private fun HomeTopAppBar( isTestnet: Boolean, goTestnetFaucet: () -> Unit, @@ -103,6 +105,7 @@ private fun HomeTopAppBar( } @Composable +@Suppress("ktlint:standard:function-naming") private fun DebugMenu( isTestnet: Boolean, goTestnetFaucet: () -> Unit, @@ -146,7 +149,7 @@ private fun DebugMenu( } @Composable -@Suppress("LongParameterList") +@Suppress("LongParameterList", "ktlint:standard:function-naming") private fun HomeMainContent( paddingValues: PaddingValues, walletSnapshot: WalletSnapshot, diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt index 53096fc8..b97eac3a 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletSnapshot.kt @@ -19,8 +19,9 @@ data class WalletSnapshot( // TODO [#776]: Support variable fees // TODO [#776]: https://github.com/zcash/zcash-android-wallet-sdk/issues/776 // Note: the wallet is effectively empty if it cannot cover the miner's fee - val hasFunds = saplingBalance.available.value > - (ZcashSdk.MINERS_FEE.value.toDouble() / Zatoshi.ZATOSHI_PER_ZEC) // 0.0001 + val hasFunds = + saplingBalance.available.value > + (ZcashSdk.MINERS_FEE.value.toDouble() / Zatoshi.ZATOSHI_PER_ZEC) // 0.0001 val hasSaplingBalance = saplingBalance.total.value > 0 val isSendEnabled: Boolean get() = status == Synchronizer.Status.SYNCED && hasFunds diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt index ef08727f..8431063d 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt @@ -66,68 +66,74 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) /** * Synchronizer that is retained long enough to survive configuration changes. */ - val synchronizer = walletCoordinator.synchronizer.stateIn( - viewModelScope, - SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), - null - ) - - val secretState: StateFlow = walletCoordinator.persistableWallet - .map { persistableWallet -> - if (null == persistableWallet) { - SecretState.None - } else { - SecretState.Ready(persistableWallet) - } - }.stateIn( - viewModelScope, - SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), - SecretState.Loading - ) - - val spendingKey = secretState - .filterIsInstance() - .map { it.persistableWallet } - .map { - val bip39Seed = withContext(Dispatchers.IO) { - Mnemonics.MnemonicCode(it.seedPhrase.joinToString()).toSeed() - } - DerivationTool.getInstance().deriveUnifiedSpendingKey( - seed = bip39Seed, - network = it.network, - account = Account.DEFAULT - ) - }.stateIn( + val synchronizer = + walletCoordinator.synchronizer.stateIn( viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), null ) + val secretState: StateFlow = + walletCoordinator.persistableWallet + .map { persistableWallet -> + if (null == persistableWallet) { + SecretState.None + } else { + SecretState.Ready(persistableWallet) + } + }.stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), + SecretState.Loading + ) + + val spendingKey = + secretState + .filterIsInstance() + .map { it.persistableWallet } + .map { + val bip39Seed = + withContext(Dispatchers.IO) { + Mnemonics.MnemonicCode(it.seedPhrase.joinToString()).toSeed() + } + DerivationTool.getInstance().deriveUnifiedSpendingKey( + seed = bip39Seed, + network = it.network, + account = Account.DEFAULT + ) + }.stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), + null + ) + @OptIn(ExperimentalCoroutinesApi::class) - val walletSnapshot: StateFlow = synchronizer - .flatMapLatest { - if (null == it) { - flowOf(null) - } else { - it.toWalletSnapshot() + val walletSnapshot: StateFlow = + synchronizer + .flatMapLatest { + if (null == it) { + flowOf(null) + } else { + it.toWalletSnapshot() + } } - } - .throttle(1.seconds) - .stateIn( - viewModelScope, - SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), - null - ) + .throttle(1.seconds) + .stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), + null + ) - val addresses: StateFlow = synchronizer - .filterNotNull() - .map { - WalletAddresses.new(it) - }.stateIn( - viewModelScope, - SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), - null - ) + val addresses: StateFlow = + synchronizer + .filterNotNull() + .map { + WalletAddresses.new(it) + }.stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), + null + ) private val mutableSendState = MutableStateFlow(SendState.None) @@ -136,8 +142,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) /** * Creates a wallet asynchronously and then persists it. Clients observe * [secretState] to see the side effects. This would be used for a user creating a new wallet. - */ - /* + * * Although waiting for the wallet to be written and then read back is slower, it is probably * safer because it 1. guarantees the wallet is written to disk and 2. has a single source of truth. */ @@ -146,12 +151,13 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) viewModelScope.launch { val network = ZcashNetwork.fromResources(application) - val newWallet = PersistableWallet.new( - application = application, - zcashNetwork = network, - endpoint = LightWalletEndpoint.defaultForNetwork(network), - walletInitMode = WalletInitMode.NewWallet - ) + val newWallet = + PersistableWallet.new( + application = application, + zcashNetwork = network, + endpoint = LightWalletEndpoint.defaultForNetwork(network), + walletInitMode = WalletInitMode.NewWallet + ) persistWallet(newWallet) } } @@ -268,7 +274,9 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) */ sealed class SecretState { object Loading : SecretState() + object None : SecretState() + class Ready(val persistableWallet: PersistableWallet) : SecretState() } @@ -276,22 +284,27 @@ sealed class SendState { object None : SendState() { override fun toString(): String = "None" } + object Sending : SendState() { override fun toString(): String = "Sending" } + class Sent(val localTxId: Long) : SendState() { override fun toString(): String = "Sent" } + class Error(val error: Throwable) : SendState() { override fun toString(): String = "Error ${error.message}" } } +// TODO [#529]: Localize Synchronizer Errors +// TODO [#529]: https://github.com/zcash/secant-android-wallet/issues/529 + /** * Represents all kind of Synchronizer errors */ -// TODO [#529]: Localize Synchronizer Errors -// TODO [#529]: https://github.com/zcash/secant-android-wallet/issues/529 + sealed class SynchronizerError { abstract fun getCauseMessage(): String? @@ -316,51 +329,59 @@ sealed class SynchronizerError { } } -private fun Synchronizer.toCommonError(): Flow = callbackFlow { - // just for initial default value emit - trySend(null) +private fun Synchronizer.toCommonError(): Flow = + callbackFlow { + // just for initial default value emit + trySend(null) - onCriticalErrorHandler = { - Twig.error { "WALLET - Error Critical: $it" } - trySend(SynchronizerError.Critical(it)) - false - } - onProcessorErrorHandler = { - Twig.error { "WALLET - Error Processor: $it" } - trySend(SynchronizerError.Processor(it)) - false - } - onSubmissionErrorHandler = { - Twig.error { "WALLET - Error Submission: $it" } - trySend(SynchronizerError.Submission(it)) - false - } - onSetupErrorHandler = { - Twig.error { "WALLET - Error Setup: $it" } - trySend(SynchronizerError.Setup(it)) - false - } - onChainErrorHandler = { x, y -> - Twig.error { "WALLET - Error Chain: $x, $y" } - trySend(SynchronizerError.Chain(x, y)) - } + onCriticalErrorHandler = { + Twig.error { "WALLET - Error Critical: $it" } + trySend(SynchronizerError.Critical(it)) + false + } + onProcessorErrorHandler = { + Twig.error { "WALLET - Error Processor: $it" } + trySend(SynchronizerError.Processor(it)) + false + } + onSubmissionErrorHandler = { + Twig.error { "WALLET - Error Submission: $it" } + trySend(SynchronizerError.Submission(it)) + false + } + onSetupErrorHandler = { + Twig.error { "WALLET - Error Setup: $it" } + trySend(SynchronizerError.Setup(it)) + false + } + onChainErrorHandler = { x, y -> + Twig.error { "WALLET - Error Chain: $x, $y" } + trySend(SynchronizerError.Chain(x, y)) + } - awaitClose { - // nothing to close here + awaitClose { + // nothing to close here + } } -} // No good way around needing magic numbers for the indices @Suppress("MagicNumber") private fun Synchronizer.toWalletSnapshot() = combine( - status, // 0 - processorInfo, // 1 - orchardBalances, // 2 - saplingBalances, // 3 - transparentBalances, // 4 - progress, // 5 - toCommonError() // 6 + // 0 + status, + // 1 + processorInfo, + // 2 + orchardBalances, + // 3 + saplingBalances, + // 4 + transparentBalances, + // 5 + progress, + // 6 + toCommonError() ) { flows -> val orchardBalance = flows[2] as WalletBalance? val saplingBalance = flows[3] as WalletBalance? diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/seed/view/ConfigureSeedView.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/seed/view/ConfigureSeedView.kt index d6f6e7ed..93f94ff8 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/seed/view/ConfigureSeedView.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/seed/view/ConfigureSeedView.kt @@ -26,6 +26,7 @@ import co.electriccoin.lightwallet.client.model.LightWalletEndpoint @Preview(name = "Seed") @Composable +@Suppress("ktlint:standard:function-naming") private fun ComposablePreview() { MaterialTheme { Seed( @@ -37,6 +38,7 @@ private fun ComposablePreview() { } @Composable +@Suppress("ktlint:standard:function-naming") fun Seed( zcashNetwork: ZcashNetwork, onExistingWallet: (PersistableWallet) -> Unit, @@ -56,6 +58,7 @@ fun Seed( @Composable @OptIn(ExperimentalMaterial3Api::class) +@Suppress("ktlint:standard:function-naming") private fun ConfigureSeedTopAppBar() { TopAppBar( title = { Text(text = stringResource(id = R.string.configure_seed)) } @@ -63,6 +66,7 @@ private fun ConfigureSeedTopAppBar() { } @Composable +@Suppress("ktlint:standard:function-naming") private fun ConfigureSeedMainContent( paddingValues: PaddingValues, zcashNetwork: ZcashNetwork, @@ -76,13 +80,14 @@ private fun ConfigureSeedMainContent( ) { Button( onClick = { - val newWallet = PersistableWallet( - network = zcashNetwork, - endpoint = LightWalletEndpoint.defaultForNetwork(zcashNetwork), - birthday = WalletFixture.Alice.getBirthday(zcashNetwork), - seedPhrase = SeedPhrase.new(WalletFixture.Alice.seedPhrase), - walletInitMode = WalletInitMode.RestoreWallet - ) + val newWallet = + PersistableWallet( + network = zcashNetwork, + endpoint = LightWalletEndpoint.defaultForNetwork(zcashNetwork), + birthday = WalletFixture.Alice.getBirthday(zcashNetwork), + seedPhrase = SeedPhrase.new(WalletFixture.Alice.seedPhrase), + walletInitMode = WalletInitMode.RestoreWallet + ) onExistingWallet(newWallet) } ) { @@ -90,13 +95,14 @@ private fun ConfigureSeedMainContent( } Button( onClick = { - val newWallet = PersistableWallet( - network = zcashNetwork, - endpoint = LightWalletEndpoint.defaultForNetwork(zcashNetwork), - birthday = WalletFixture.Ben.getBirthday(zcashNetwork), - seedPhrase = SeedPhrase.new(WalletFixture.Ben.seedPhrase), - walletInitMode = WalletInitMode.RestoreWallet - ) + val newWallet = + PersistableWallet( + network = zcashNetwork, + endpoint = LightWalletEndpoint.defaultForNetwork(zcashNetwork), + birthday = WalletFixture.Ben.getBirthday(zcashNetwork), + seedPhrase = SeedPhrase.new(WalletFixture.Ben.seedPhrase), + walletInitMode = WalletInitMode.RestoreWallet + ) onExistingWallet(newWallet) } ) { diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/send/view/SendView.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/send/view/SendView.kt index 6bf57e28..c38e4b7f 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/send/view/SendView.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/send/view/SendView.kt @@ -52,6 +52,7 @@ import cash.z.ecc.android.sdk.model.toZecString @Preview(name = "Send") @Composable +@Suppress("ktlint:standard:function-naming") private fun ComposablePreview() { MaterialTheme { Send( @@ -64,6 +65,7 @@ private fun ComposablePreview() { } @Composable +@Suppress("ktlint:standard:function-naming") fun Send( walletSnapshot: WalletSnapshot, sendState: SendState, @@ -84,6 +86,7 @@ fun Send( @Composable @OptIn(ExperimentalMaterial3Api::class) +@Suppress("ktlint:standard:function-naming") private fun SendTopAppBar(onBack: () -> Unit) { TopAppBar( title = { Text(text = stringResource(id = R.string.menu_send)) }, @@ -101,7 +104,7 @@ private fun SendTopAppBar(onBack: () -> Unit) { } @Composable -@Suppress("LongMethod") +@Suppress("LongMethod", "ktlint:standard:function-naming") private fun SendMainContent( paddingValues: PaddingValues, walletSnapshot: WalletSnapshot, @@ -227,20 +230,20 @@ private fun SendMainContent( Button( onClick = { - val zecSendValidation = ZecSendExt.new( - context, - recipientAddressString, - amountZecString, - memoString, - monetarySeparators - ) + val zecSendValidation = + ZecSendExt.new( + context, + recipientAddressString, + amountZecString, + memoString, + monetarySeparators + ) when (zecSendValidation) { is ZecSendExt.ZecSendValidation.Valid -> onSend(zecSendValidation.zecSend) is ZecSendExt.ZecSendValidation.Invalid -> validation = zecSendValidation.validationErrors } }, - // Needs actual validation enabled = amountZecString.isNotBlank() && recipientAddressString.isNotBlank() ) { diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/transactions/view/TransactionsView.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/transactions/view/TransactionsView.kt index 97f739fb..e6ba4eda 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/transactions/view/TransactionsView.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/transactions/view/TransactionsView.kt @@ -39,6 +39,7 @@ import kotlinx.coroutines.launch @Preview(name = "Transactions") @Composable +@Suppress("ktlint:standard:function-naming") private fun ComposablePreview() { MaterialTheme { // TODO [#1090]: Demo: Add Addresses and Transactions Compose Previews @@ -48,6 +49,7 @@ private fun ComposablePreview() { } @Composable +@Suppress("ktlint:standard:function-naming") fun Transactions( synchronizer: Synchronizer, onBack: () -> Unit @@ -64,11 +66,12 @@ fun Transactions( ) { paddingValues -> // TODO [#846]: Slow addresses providing // TODO [#846]: https://github.com/zcash/zcash-android-wallet-sdk/issues/846 - val walletAddresses = flow { - emit(WalletAddresses.new(synchronizer)) - }.collectAsState( - initial = null - ).value + val walletAddresses = + flow { + emit(WalletAddresses.new(synchronizer)) + }.collectAsState( + initial = null + ).value if (null != walletAddresses) { TransactionsMainContent( paddingValues = paddingValues, @@ -82,6 +85,7 @@ fun Transactions( @Composable @OptIn(ExperimentalMaterial3Api::class) +@Suppress("ktlint:standard:function-naming") private fun TransactionsTopAppBar( onBack: () -> Unit, onRefresh: () -> Unit @@ -110,6 +114,7 @@ private fun TransactionsTopAppBar( } @Composable +@Suppress("ktlint:standard:function-naming") private fun TransactionsMainContent( paddingValues: PaddingValues, synchronizer: Synchronizer, @@ -138,14 +143,16 @@ private fun TransactionsMainContent( } } }) { - val time = tx.minedHeight?.let { - tx.blockTimeEpochSeconds?.let { kotlinx.datetime.Instant.fromEpochSeconds(it) } ?: "Unknown" - } ?: "Pending" - val value = if (tx.isSentTransaction) { - -tx.netValue.value - } else { - tx.netValue.value - } + val time = + tx.minedHeight?.let { + tx.blockTimeEpochSeconds?.let { kotlinx.datetime.Instant.fromEpochSeconds(it) } ?: "Unknown" + } ?: "Pending" + val value = + if (tx.isSentTransaction) { + -tx.netValue.value + } else { + tx.netValue.value + } Text("$time, $value") } } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/util/AndroidApiVersion.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/util/AndroidApiVersion.kt index ee395e65..cf11a2cf 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/util/AndroidApiVersion.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/util/AndroidApiVersion.kt @@ -11,7 +11,9 @@ internal object AndroidApiVersion { * [sdk]. */ @ChecksSdkIntAtLeast(parameter = 0) - fun isAtLeast(@IntRange(from = Build.VERSION_CODES.BASE.toLong()) sdk: Int): Boolean { + fun isAtLeast( + @IntRange(from = Build.VERSION_CODES.BASE.toLong()) sdk: Int + ): Boolean { return Build.VERSION.SDK_INT >= sdk } diff --git a/gradle.properties b/gradle.properties index 49104881..10b0a943 100644 --- a/gradle.properties +++ b/gradle.properties @@ -94,7 +94,7 @@ FOOJAY_TOOLCHAIN_RESOLVER_VERSION=0.5.0 FULLADLE_VERSION=0.17.4 GRADLE_VERSIONS_PLUGIN_VERSION=0.50.0 KSP_VERSION=1.8.20-1.0.10 -KTLINT_VERSION=0.50.0 +KTLINT_VERSION=1.1.0 PROTOBUF_GRADLE_PLUGIN_VERSION=0.9.4 RUST_GRADLE_PLUGIN_VERSION=0.9.3 diff --git a/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/ext/BenchmarkingExtTest.kt b/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/ext/BenchmarkingExtTest.kt index 8b75985d..d116e459 100644 --- a/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/ext/BenchmarkingExtTest.kt +++ b/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/ext/BenchmarkingExtTest.kt @@ -7,7 +7,6 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue class BenchmarkingExtTest { - @Test @SmallTest fun check_build_config() { diff --git a/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/fixture/BlockRangeFixtureTest.kt b/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/fixture/BlockRangeFixtureTest.kt index ad7f4a92..a195cb6d 100644 --- a/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/fixture/BlockRangeFixtureTest.kt +++ b/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/fixture/BlockRangeFixtureTest.kt @@ -5,7 +5,6 @@ import kotlin.test.Test import kotlin.test.assertEquals class BlockRangeFixtureTest { - @Test @SmallTest fun compare_default_values() { diff --git a/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/fixture/StatusExceptionFixture.kt b/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/fixture/StatusExceptionFixture.kt index a7a7c353..55582c14 100644 --- a/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/fixture/StatusExceptionFixture.kt +++ b/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/fixture/StatusExceptionFixture.kt @@ -4,10 +4,7 @@ import io.grpc.Status import io.grpc.StatusRuntimeException object StatusExceptionFixture { - - fun new( - status: Status - ): StatusRuntimeException { + fun new(status: Status): StatusRuntimeException { return StatusRuntimeException(status) } } diff --git a/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/internal/ChannelFactoryTest.kt b/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/internal/ChannelFactoryTest.kt index f8edba26..945c878d 100644 --- a/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/internal/ChannelFactoryTest.kt +++ b/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/internal/ChannelFactoryTest.kt @@ -6,7 +6,6 @@ import kotlin.test.Ignore import kotlin.test.Test class ChannelFactoryTest { - private val channelFactory = AndroidChannelFactory(getAppContext()) @Test diff --git a/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/internal/GrpcStatusResolverTest.kt b/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/internal/GrpcStatusResolverTest.kt index a584e9de..7673a41e 100644 --- a/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/internal/GrpcStatusResolverTest.kt +++ b/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/internal/GrpcStatusResolverTest.kt @@ -10,7 +10,6 @@ import kotlin.test.assertNull import kotlin.test.assertTrue class GrpcStatusResolverTest { - @Test @SmallTest fun resolve_explicitly_caught_server_error_test() { diff --git a/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/internal/LightWalletClientImplTest.kt b/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/internal/LightWalletClientImplTest.kt index c1777f64..7ee60328 100644 --- a/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/internal/LightWalletClientImplTest.kt +++ b/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/internal/LightWalletClientImplTest.kt @@ -6,7 +6,6 @@ import kotlin.test.Ignore import kotlin.test.Test class LightWalletClientImplTest { - private val channelFactory = AndroidChannelFactory(getAppContext()) @Test diff --git a/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/test/Global.kt b/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/test/Global.kt index 483945a4..5aaed65a 100644 --- a/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/test/Global.kt +++ b/lightwallet-client-lib/src/androidTest/java/co/electriccoin/lightwallet/client/test/Global.kt @@ -6,7 +6,11 @@ import androidx.test.core.app.ApplicationProvider fun getAppContext(): Context = ApplicationProvider.getApplicationContext() -fun getStringResource(@StringRes resId: Int) = getAppContext().getString(resId) +fun getStringResource( + @StringRes resId: Int +) = getAppContext().getString(resId) -fun getStringResourceWithArgs(@StringRes resId: Int, vararg formatArgs: String) = - getAppContext().getString(resId, *formatArgs) +fun getStringResourceWithArgs( + @StringRes resId: Int, + vararg formatArgs: String +) = getAppContext().getString(resId, *formatArgs) diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/LightWalletClient.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/LightWalletClient.kt index 75a065fb..0abf6e08 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/LightWalletClient.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/LightWalletClient.kt @@ -19,7 +19,6 @@ import kotlinx.coroutines.flow.Flow * Client for interacting with lightwalletd. */ interface LightWalletClient { - /** * @return the full transaction info. */ @@ -116,5 +115,4 @@ interface LightWalletClient { fun LightWalletClient.Companion.new( context: Context, lightWalletEndpoint: LightWalletEndpoint -): LightWalletClient = - LightWalletClientImpl.new(AndroidChannelFactory(context), lightWalletEndpoint) +): LightWalletClient = LightWalletClientImpl.new(AndroidChannelFactory(context), lightWalletEndpoint) diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/fixture/BenchmarkingBlockRangeFixture.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/fixture/BenchmarkingBlockRangeFixture.kt index ca0d41fc..1f20dceb 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/fixture/BenchmarkingBlockRangeFixture.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/fixture/BenchmarkingBlockRangeFixture.kt @@ -6,7 +6,6 @@ import androidx.annotation.VisibleForTesting * Used for getting mocked blocks range for benchmarking purposes. */ object BenchmarkingBlockRangeFixture { - // Be aware that changing these bounds values in a broader range may result in a timeout reached in // SyncBlockchainBenchmark. So if changing these, don't forget to align also the test timeout in // waitForBalanceScreen() appropriately. diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/fixture/ListOfCompactBlocksFixture.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/fixture/ListOfCompactBlocksFixture.kt index 1b9f0050..c89f6a91 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/fixture/ListOfCompactBlocksFixture.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/fixture/ListOfCompactBlocksFixture.kt @@ -11,12 +11,10 @@ import kotlinx.coroutines.flow.asFlow */ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) object ListOfCompactBlocksFixture { - val DEFAULT_FILE_BLOCK_RANGE = FileBlockRangeFixture.new() - fun newSequence( - blocksHeightRange: ClosedRange = DEFAULT_FILE_BLOCK_RANGE - ): Sequence { + @Suppress("MaxLineLength") + fun newSequence(blocksHeightRange: ClosedRange = DEFAULT_FILE_BLOCK_RANGE): Sequence { val blocks = mutableListOf() for (blockHeight in blocksHeightRange.start.value..blocksHeightRange.endInclusive.value) { @@ -28,9 +26,8 @@ object ListOfCompactBlocksFixture { return blocks.asSequence() } - fun newFlow( - blocksHeightRange: ClosedRange = DEFAULT_FILE_BLOCK_RANGE - ): Flow { + @Suppress("MaxLineLength") + fun newFlow(blocksHeightRange: ClosedRange = DEFAULT_FILE_BLOCK_RANGE): Flow { return newSequence(blocksHeightRange).asFlow() } } diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/fixture/SingleCompactBlockFixture.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/fixture/SingleCompactBlockFixture.kt index 3ea8ca80..2d96c8ff 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/fixture/SingleCompactBlockFixture.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/fixture/SingleCompactBlockFixture.kt @@ -7,14 +7,15 @@ import java.nio.ByteBuffer * Used for getting single mocked compact block for processing and persisting purposes. */ internal object SingleCompactBlockFixture { - internal const val DEFAULT_HEIGHT = 500_000L internal const val DEFAULT_TIME = 0 internal const val DEFAULT_SAPLING_OUTPUT_COUNT = 1u internal const val DEFAULT_ORCHARD_OUTPUT_COUNT = 2u internal const val DEFAULT_HASH = DEFAULT_HEIGHT internal const val DEFAULT_BLOCK_BYTES = DEFAULT_HEIGHT + internal fun heightToFixtureData(height: Long) = BytesConversionHelper.longToBytes(height) + internal fun fixtureDataToHeight(byteArray: ByteArray) = BytesConversionHelper.bytesToLong(byteArray) @Suppress("LongParameterList") diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/Constants.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/Constants.kt index 421f02b3..6f3370f4 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/Constants.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/Constants.kt @@ -3,6 +3,7 @@ package co.electriccoin.lightwallet.client.internal internal object Constants { const val LOG_TAG = "LightWalletClient" // NON-NLS const val ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_COMMON = "Illegal argument provided:" // NON-NLS - const val ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_EMPTY = "$ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_COMMON " + - "can't be empty:" // NON-NLS + const val ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_EMPTY = + "$ILLEGAL_ARGUMENT_EXCEPTION_MESSAGE_COMMON " + + "can't be empty:" // NON-NLS } diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/DarksideApi.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/DarksideApi.kt index bb071205..ce46c9e9 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/DarksideApi.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/DarksideApi.kt @@ -24,7 +24,6 @@ class DarksideApi private constructor( private val channel: ManagedChannel, private val singleRequestTimeout: Duration = 10.seconds ) { - companion object { internal fun new( channelFactory: ChannelFactory, @@ -38,7 +37,8 @@ class DarksideApi private constructor( fun reset( saplingActivationHeight: BlockHeightUnsafe, - branchId: String = "e9ff75a6", // Canopy, + // Canopy + branchId: String = "e9ff75a6", chainName: String = "darksidemainnet" ) = apply { Darkside.DarksideMetaState.newBuilder() @@ -50,11 +50,15 @@ class DarksideApi private constructor( } } - fun stageBlocks(url: String) = apply { - createStub().stageBlocks(url.toUrl()) - } + fun stageBlocks(url: String) = + apply { + createStub().stageBlocks(url.toUrl()) + } - fun stageTransactions(url: String, targetHeight: BlockHeightUnsafe) = apply { + fun stageTransactions( + url: String, + targetHeight: BlockHeightUnsafe + ) = apply { createStub().stageTransactions( DarksideTransactionsURL.newBuilder().setHeight(targetHeight.value.toInt()).setUrl(url).build() ) @@ -71,7 +75,10 @@ class DarksideApi private constructor( ) } - fun stageTransactions(txs: Iterator?, tipHeight: BlockHeightUnsafe) { + fun stageTransactions( + txs: Iterator?, + tipHeight: BlockHeightUnsafe + ) { if (txs == null) { return } @@ -147,6 +154,7 @@ class DarksideApi private constructor( var completed = false var error: Throwable? = null + override fun onNext(value: Service.Empty?) { // No implementation } @@ -171,8 +179,7 @@ class DarksideApi private constructor( } } -private fun BlockHeightUnsafe.toHeight() = - Darkside.DarksideHeight.newBuilder().setHeight(this.value.toInt()).build() +private fun BlockHeightUnsafe.toHeight() = Darkside.DarksideHeight.newBuilder().setHeight(this.value.toInt()).build() fun DarksideApi.Companion.new( context: Context, diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/GrpcStatusResolver.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/GrpcStatusResolver.kt index 0b0229c2..472c38b1 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/GrpcStatusResolver.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/GrpcStatusResolver.kt @@ -8,7 +8,6 @@ import io.grpc.Status * This class provides conversion from GRPC Status to our predefined Server or Client error classes. */ object GrpcStatusResolver : ApiStatusResolver { - override fun resolveFailureFromStatus(throwable: Throwable): Response.Failure { val status = Status.fromThrowable(throwable) Log.w(Constants.LOG_TAG, "Networking error: ${status.code}: ${status.description}") diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/LightWalletClientImpl.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/LightWalletClientImpl.kt index 1b07580d..3b9ee334 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/LightWalletClientImpl.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/internal/LightWalletClientImpl.kt @@ -45,7 +45,6 @@ internal class LightWalletClientImpl private constructor( private val singleRequestTimeout: Duration = 10.seconds, private val streamingRequestTimeout: Duration = 90.seconds ) : LightWalletClient { - private var channel = channelFactory.newChannel(lightWalletEndpoint) override fun getBlockRange(heightRange: ClosedRange): Flow> { @@ -75,8 +74,9 @@ internal class LightWalletClientImpl private constructor( // for a more reliable benchmark results. Response.Success(BlockHeightUnsafe(BenchmarkingBlockRangeFixture.new().endInclusive)) } else { - val response = requireChannel().createStub(singleRequestTimeout) - .getLatestBlock(Service.ChainSpec.newBuilder().build()) + val response = + requireChannel().createStub(singleRequestTimeout) + .getLatestBlock(Service.ChainSpec.newBuilder().build()) val blockHeight = BlockHeightUnsafe(response.height) @@ -90,8 +90,9 @@ internal class LightWalletClientImpl private constructor( @Suppress("SwallowedException") override suspend fun getServerInfo(): Response { return try { - val lightdInfo = requireChannel().createStub(singleRequestTimeout) - .getLightdInfo(Service.Empty.newBuilder().build()) + val lightdInfo = + requireChannel().createStub(singleRequestTimeout) + .getLightdInfo(Service.Empty.newBuilder().build()) val lightwalletEndpointInfo = LightWalletEndpointInfoUnsafe.new(lightdInfo) @@ -107,9 +108,10 @@ internal class LightWalletClientImpl private constructor( " so this request was ignored on the client-side." // NON-NLS } - val request = Service.RawTransaction.newBuilder() - .setData(ByteString.copyFrom(spendTransaction)) - .build() + val request = + Service.RawTransaction.newBuilder() + .setData(ByteString.copyFrom(spendTransaction)) + .build() return try { val response = requireChannel().createStub().sendTransaction(request) @@ -182,10 +184,11 @@ internal class LightWalletClientImpl private constructor( "$tAddress." // NON-NLS } - val request = Service.TransparentAddressBlockFilter.newBuilder() - .setAddress(tAddress) - .setRange(blockHeightRange.toBlockRange()) - .build() + val request = + Service.TransparentAddressBlockFilter.newBuilder() + .setAddress(tAddress) + .setRange(blockHeightRange.toBlockRange()) + .build() return try { requireChannel().createStub(streamingRequestTimeout) @@ -247,11 +250,13 @@ internal class LightWalletClientImpl private constructor( // consider making it thread safe. private var stateCount = 0 private var state: ConnectivityState? = null + private fun requireChannel(): ManagedChannel { - state = channel.getState(false).let { new -> - if (state == new) stateCount++ else stateCount = 0 - new - } + state = + channel.getState(false).let { new -> + if (state == new) stateCount++ else stateCount = 0 + new + } channel.resetConnectBackoff() return channel } @@ -270,8 +275,7 @@ private fun Channel.createStub(timeoutSec: Duration = 60.seconds) = CompactTxStreamerGrpcKt.CompactTxStreamerCoroutineStub(this, CallOptions.DEFAULT) .withDeadlineAfter(timeoutSec.inWholeSeconds, TimeUnit.SECONDS) -private fun BlockHeightUnsafe.toBlockHeight(): Service.BlockID = - Service.BlockID.newBuilder().setHeight(value).build() +private fun BlockHeightUnsafe.toBlockHeight(): Service.BlockID = Service.BlockID.newBuilder().setHeight(value).build() private fun ClosedRange.toBlockRange(): Service.BlockRange = Service.BlockRange.newBuilder() diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/CompactBlockUnsafe.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/CompactBlockUnsafe.kt index 3368b453..48971ef1 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/CompactBlockUnsafe.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/CompactBlockUnsafe.kt @@ -20,7 +20,6 @@ class CompactBlockUnsafe( val compactBlockBytes: ByteArray ) { companion object { - fun new(compactBlock: CompactBlock): CompactBlockUnsafe { val outputCounts = getOutputsCounts(compactBlock.vtxList) return CompactBlockUnsafe( diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/GetAddressUtxosReplyUnsafe.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/GetAddressUtxosReplyUnsafe.kt index e27398be..2876f90e 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/GetAddressUtxosReplyUnsafe.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/GetAddressUtxosReplyUnsafe.kt @@ -14,7 +14,6 @@ class GetAddressUtxosReplyUnsafe( val height: Long ) { companion object { - fun new(getAddressUtxosReply: Service.GetAddressUtxosReply): GetAddressUtxosReplyUnsafe { return GetAddressUtxosReplyUnsafe( address = getAddressUtxosReply.address, diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/RawTransactionUnsafe.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/RawTransactionUnsafe.kt index 2571f3c4..b3b343d8 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/RawTransactionUnsafe.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/RawTransactionUnsafe.kt @@ -9,9 +9,10 @@ import cash.z.wallet.sdk.internal.rpc.Service.RawTransaction */ class RawTransactionUnsafe(val height: BlockHeightUnsafe, val data: ByteArray) { companion object { - fun new(rawTransaction: RawTransaction) = RawTransactionUnsafe( - BlockHeightUnsafe(rawTransaction.height), - rawTransaction.data.toByteArray() - ) + fun new(rawTransaction: RawTransaction) = + RawTransactionUnsafe( + BlockHeightUnsafe(rawTransaction.height), + rawTransaction.data.toByteArray() + ) } } diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/Response.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/Response.kt index 643d98e9..6c6e4a4d 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/Response.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/Response.kt @@ -10,12 +10,10 @@ sealed class Response { open val code: Int, open val description: String? ) : Response() { - /** * Use this function to convert Failure into Throwable object. */ - fun toThrowable() = - Throwable("Communication failure with details: $code${description?.let{": $it"} ?: "."}") // NON-NLS + fun toThrowable() = Throwable("Communication failure with details: $code${description?.let{": $it"} ?: "."}") /** * The client was not able to communicate with the server. diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/ShieldedProtocolEnum.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/ShieldedProtocolEnum.kt index 0d6e2c78..76cedf18 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/ShieldedProtocolEnum.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/ShieldedProtocolEnum.kt @@ -6,8 +6,9 @@ enum class ShieldedProtocolEnum { SAPLING, ORCHARD; - fun toProtocol() = when (this) { - SAPLING -> ShieldedProtocol.sapling - ORCHARD -> ShieldedProtocol.orchard - } + fun toProtocol() = + when (this) { + SAPLING -> ShieldedProtocol.sapling + ORCHARD -> ShieldedProtocol.orchard + } } diff --git a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/SubtreeRootUnsafe.kt b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/SubtreeRootUnsafe.kt index 49dde978..408d4cc2 100644 --- a/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/SubtreeRootUnsafe.kt +++ b/lightwallet-client-lib/src/main/java/co/electriccoin/lightwallet/client/model/SubtreeRootUnsafe.kt @@ -18,10 +18,11 @@ class SubtreeRootUnsafe( val completingBlockHeight: BlockHeightUnsafe ) { companion object { - fun new(subtreeRoot: SubtreeRoot) = SubtreeRootUnsafe( - rootHash = subtreeRoot.rootHash.toByteArray(), - completingBlockHash = subtreeRoot.completingBlockHash.toByteArray(), - completingBlockHeight = BlockHeightUnsafe(subtreeRoot.completingBlockHeight), - ) + fun new(subtreeRoot: SubtreeRoot) = + SubtreeRootUnsafe( + rootHash = subtreeRoot.rootHash.toByteArray(), + completingBlockHash = subtreeRoot.completingBlockHash.toByteArray(), + completingBlockHeight = BlockHeightUnsafe(subtreeRoot.completingBlockHeight), + ) } } 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 3f1740a1..9de0cb6a 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 @@ -16,12 +16,13 @@ class TreeStateUnsafe( time: Int, tree: String ): TreeStateUnsafe { - val treeState = TreeState.newBuilder() - .setHeight(height) - .setHash(hash) - .setTime(time) - .setSaplingTree(tree) - .build() + val treeState = + TreeState.newBuilder() + .setHeight(height) + .setHash(hash) + .setTime(time) + .setSaplingTree(tree) + .build() return new(treeState) } } diff --git a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/Global.kt b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/Global.kt index 466f57ce..dba6b248 100644 --- a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/Global.kt +++ b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/Global.kt @@ -7,9 +7,13 @@ import androidx.annotation.StringRes import androidx.test.core.app.ApplicationProvider import java.util.Locale -fun getStringResource(@StringRes resId: Int) = ApplicationProvider.getApplicationContext().getString(resId) +fun getStringResource( + @StringRes resId: Int +) = ApplicationProvider.getApplicationContext().getString(resId) -fun getStringResourceWithArgs(@StringRes resId: Int, formatArgs: Array) = - ApplicationProvider.getApplicationContext().getString(resId, *formatArgs) +fun getStringResourceWithArgs( + @StringRes resId: Int, + formatArgs: Array +) = ApplicationProvider.getApplicationContext().getString(resId, *formatArgs) fun isLocaleRTL(locale: Locale) = TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL diff --git a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/fixture/PersistableWalletFixture.kt b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/fixture/PersistableWalletFixture.kt index ba1b39e6..ec3733be 100644 --- a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/fixture/PersistableWalletFixture.kt +++ b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/fixture/PersistableWalletFixture.kt @@ -9,7 +9,6 @@ import cash.z.ecc.android.sdk.model.ZcashNetwork import co.electriccoin.lightwallet.client.model.LightWalletEndpoint object PersistableWalletFixture { - val NETWORK = ZcashNetwork.Testnet val ENDPOINT = LightWalletEndpoint.Testnet @@ -30,11 +29,12 @@ object PersistableWalletFixture { walletInitMode: WalletInitMode = WALLET_INIT_MODE ) = PersistableWallet(network, endpoint, birthday, seedPhrase, walletInitMode) - fun persistVersionOne() = PersistableWallet.toCustomJson( - version = PersistableWallet.VERSION_1, - network = NETWORK, - endpoint = null, - birthday = BIRTHDAY, - seed = SEED_PHRASE - ) + fun persistVersionOne() = + PersistableWallet.toCustomJson( + version = PersistableWallet.VERSION_1, + network = NETWORK, + endpoint = null, + birthday = BIRTHDAY, + seed = SEED_PHRASE + ) } diff --git a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/fixture/SeedPhraseFixture.kt b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/fixture/SeedPhraseFixture.kt index f46e4bd8..c9a53454 100644 --- a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/fixture/SeedPhraseFixture.kt +++ b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/fixture/SeedPhraseFixture.kt @@ -3,8 +3,9 @@ package cash.z.ecc.android.sdk.fixture import cash.z.ecc.android.sdk.model.SeedPhrase object SeedPhraseFixture { - @Suppress("MaxLineLength") - val SEED_PHRASE = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" + const val SEED_PHRASE = + "still champion voice habit trend flight survey between bitter process artefact blind " + + "carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" fun new(seedPhrase: String = SEED_PHRASE) = SeedPhrase.new(seedPhrase) } diff --git a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/FiatCurrencyConversionRateStateTest.kt b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/FiatCurrencyConversionRateStateTest.kt index d45a775f..926504b3 100644 --- a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/FiatCurrencyConversionRateStateTest.kt +++ b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/FiatCurrencyConversionRateStateTest.kt @@ -17,18 +17,20 @@ class FiatCurrencyConversionRateStateTest { fun future_near() { val zatoshi = ZatoshiFixture.new() - val frozenClock = FrozenClock( - CurrencyConversionFixture.TIMESTAMP - FiatCurrencyConversionRateState.FUTURE_CUTOFF_AGE_INCLUSIVE - ) + val frozenClock = + FrozenClock( + CurrencyConversionFixture.TIMESTAMP - FiatCurrencyConversionRateState.FUTURE_CUTOFF_AGE_INCLUSIVE + ) val currencyConversion = CurrencyConversionFixture.new() - val result = zatoshi.toFiatCurrencyState( - currencyConversion, - LocaleFixture.new(), - MonetarySeparatorsFixture.new(), - frozenClock - ) + val result = + zatoshi.toFiatCurrencyState( + currencyConversion, + LocaleFixture.new(), + MonetarySeparatorsFixture.new(), + frozenClock + ) assertIs(result) } @@ -38,20 +40,22 @@ class FiatCurrencyConversionRateStateTest { fun future_far() { val zatoshi = ZatoshiFixture.new() - val frozenClock = FrozenClock( - CurrencyConversionFixture.TIMESTAMP - - FiatCurrencyConversionRateState.FUTURE_CUTOFF_AGE_INCLUSIVE - - 1.seconds - ) + val frozenClock = + FrozenClock( + CurrencyConversionFixture.TIMESTAMP - + FiatCurrencyConversionRateState.FUTURE_CUTOFF_AGE_INCLUSIVE - + 1.seconds + ) val currencyConversion = CurrencyConversionFixture.new() - val result = zatoshi.toFiatCurrencyState( - currencyConversion, - LocaleFixture.new(), - MonetarySeparatorsFixture.new(), - frozenClock - ) + val result = + zatoshi.toFiatCurrencyState( + currencyConversion, + LocaleFixture.new(), + MonetarySeparatorsFixture.new(), + frozenClock + ) assertIs(result) } @@ -63,16 +67,18 @@ class FiatCurrencyConversionRateStateTest { val frozenClock = FrozenClock(CurrencyConversionFixture.TIMESTAMP) - val currencyConversion = CurrencyConversionFixture.new( - timestamp = CurrencyConversionFixture.TIMESTAMP - 1.seconds - ) + val currencyConversion = + CurrencyConversionFixture.new( + timestamp = CurrencyConversionFixture.TIMESTAMP - 1.seconds + ) - val result = zatoshi.toFiatCurrencyState( - currencyConversion, - LocaleFixture.new(), - MonetarySeparatorsFixture.new(), - frozenClock - ) + val result = + zatoshi.toFiatCurrencyState( + currencyConversion, + LocaleFixture.new(), + MonetarySeparatorsFixture.new(), + frozenClock + ) assertIs(result) } @@ -84,18 +90,21 @@ class FiatCurrencyConversionRateStateTest { val frozenClock = FrozenClock(CurrencyConversionFixture.TIMESTAMP) - val currencyConversion = CurrencyConversionFixture.new( - timestamp = CurrencyConversionFixture.TIMESTAMP - - FiatCurrencyConversionRateState.CURRENT_CUTOFF_AGE_INCLUSIVE - - 1.seconds - ) + val currencyConversion = + CurrencyConversionFixture.new( + timestamp = + CurrencyConversionFixture.TIMESTAMP - + FiatCurrencyConversionRateState.CURRENT_CUTOFF_AGE_INCLUSIVE - + 1.seconds + ) - val result = zatoshi.toFiatCurrencyState( - currencyConversion, - LocaleFixture.new(), - MonetarySeparatorsFixture.new(), - frozenClock - ) + val result = + zatoshi.toFiatCurrencyState( + currencyConversion, + LocaleFixture.new(), + MonetarySeparatorsFixture.new(), + frozenClock + ) assertIs(result) } @@ -107,18 +116,21 @@ class FiatCurrencyConversionRateStateTest { val frozenClock = FrozenClock(CurrencyConversionFixture.TIMESTAMP) - val currencyConversion = CurrencyConversionFixture.new( - timestamp = CurrencyConversionFixture.TIMESTAMP - - FiatCurrencyConversionRateState.STALE_CUTOFF_AGE_INCLUSIVE - - 1.seconds - ) + val currencyConversion = + CurrencyConversionFixture.new( + timestamp = + CurrencyConversionFixture.TIMESTAMP - + FiatCurrencyConversionRateState.STALE_CUTOFF_AGE_INCLUSIVE - + 1.seconds + ) - val result = zatoshi.toFiatCurrencyState( - currencyConversion, - LocaleFixture.new(), - MonetarySeparatorsFixture.new(), - frozenClock - ) + val result = + zatoshi.toFiatCurrencyState( + currencyConversion, + LocaleFixture.new(), + MonetarySeparatorsFixture.new(), + frozenClock + ) assertIs(result) } @@ -128,11 +140,12 @@ class FiatCurrencyConversionRateStateTest { fun null_conversion_rate() { val zatoshi = ZatoshiFixture.new() - val result = zatoshi.toFiatCurrencyState( - null, - LocaleFixture.new(), - MonetarySeparatorsFixture.new() - ) + val result = + zatoshi.toFiatCurrencyState( + null, + LocaleFixture.new(), + MonetarySeparatorsFixture.new() + ) assertIs(result) } diff --git a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/WalletAddressTest.kt b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/WalletAddressTest.kt index be207b28..13b985a9 100644 --- a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/WalletAddressTest.kt +++ b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/WalletAddressTest.kt @@ -9,10 +9,11 @@ import kotlin.test.assertEquals class WalletAddressTest { @Test @ExperimentalCoroutinesApi - fun unified_equals_different_instance() = runTest { - val one = WalletAddressFixture.unified() - val two = WalletAddressFixture.unified() + fun unified_equals_different_instance() = + runTest { + val one = WalletAddressFixture.unified() + val two = WalletAddressFixture.unified() - assertEquals(one, two) - } + assertEquals(one, two) + } } diff --git a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/WalletAddressesTest.kt b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/WalletAddressesTest.kt index c196f2f0..d3219723 100644 --- a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/WalletAddressesTest.kt +++ b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/WalletAddressesTest.kt @@ -11,11 +11,12 @@ import org.junit.Test class WalletAddressesTest { @Test @SmallTest - fun security() = runTest { - val walletAddresses = WalletAddressesFixture.new() - val actual = WalletAddressesFixture.new().toString() - assertFalse(actual.contains(walletAddresses.sapling.address)) - assertFalse(actual.contains(walletAddresses.transparent.address)) - assertFalse(actual.contains(walletAddresses.unified.address)) - } + fun security() = + runTest { + val walletAddresses = WalletAddressesFixture.new() + val actual = WalletAddressesFixture.new().toString() + assertFalse(actual.contains(walletAddresses.sapling.address)) + assertFalse(actual.contains(walletAddresses.transparent.address)) + assertFalse(actual.contains(walletAddresses.unified.address)) + } } diff --git a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/ZatoshiExtTest.kt b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/ZatoshiExtTest.kt index 3e754756..51781e17 100644 --- a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/ZatoshiExtTest.kt +++ b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/ZatoshiExtTest.kt @@ -46,15 +46,17 @@ class ZatoshiExtTest { @SmallTest fun rounded_zatoshi_to_fiat_conversion_test() { val roundedZatoshi = ZatoshiFixture.new(100_000_000L) - val roundedCurrencyConversion = CurrencyConversionFixture.new( - priceOfZec = 100.0 - ) + val roundedCurrencyConversion = + CurrencyConversionFixture.new( + priceOfZec = 100.0 + ) - val fiatString = roundedZatoshi.toFiatString( - roundedCurrencyConversion, - LocaleFixture.new(), - EN_US_SEPARATORS - ) + val fiatString = + roundedZatoshi.toFiatString( + roundedCurrencyConversion, + LocaleFixture.new(), + EN_US_SEPARATORS + ) fiatString.also { assertNotNull(it) diff --git a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/ZecStringExtTest.kt b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/ZecStringExtTest.kt index 2571a510..e019cab4 100644 --- a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/ZecStringExtTest.kt +++ b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/ZecStringExtTest.kt @@ -10,7 +10,6 @@ import kotlin.test.assertNotNull import kotlin.test.assertTrue class ZecStringExtTest { - companion object { private val EN_US_SEPARATORS = MonetarySeparatorsFixture.new() } @@ -38,13 +37,14 @@ class ZecStringExtTest { @Test @SmallTest fun check_continuous_regex_validity() { - val regexString = getStringResourceWithArgs( - R.string.co_electriccoin_zcash_zec_amount_regex_continuous_filter, - arrayOf( - EN_US_SEPARATORS.grouping, - EN_US_SEPARATORS.decimal + val regexString = + getStringResourceWithArgs( + R.string.co_electriccoin_zcash_zec_amount_regex_continuous_filter, + arrayOf( + EN_US_SEPARATORS.grouping, + EN_US_SEPARATORS.decimal + ) ) - ) assertNotNull(regexString) val regexAmountChecker = regexString.toRegex() @@ -58,13 +58,14 @@ class ZecStringExtTest { @Test @SmallTest fun check_confirm_regex_validity() { - val regexString = getStringResourceWithArgs( - R.string.co_electriccoin_zcash_zec_amount_regex_confirm_filter, - arrayOf( - EN_US_SEPARATORS.grouping, - EN_US_SEPARATORS.decimal + val regexString = + getStringResourceWithArgs( + R.string.co_electriccoin_zcash_zec_amount_regex_confirm_filter, + arrayOf( + EN_US_SEPARATORS.grouping, + EN_US_SEPARATORS.decimal + ) ) - ) assertNotNull(regexString) val regexAmountChecker = regexString.toRegex() diff --git a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/ZecStringTest.kt b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/ZecStringTest.kt index afc20d30..c43533d9 100644 --- a/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/ZecStringTest.kt +++ b/sdk-incubator-lib/src/androidTest/kotlin/cash/z/ecc/android/sdk/model/ZecStringTest.kt @@ -13,16 +13,17 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull class ZecStringTest { - companion object { private val EN_US_MONETARY_SEPARATORS = MonetarySeparatorsFixture.new() - private val context = run { - val applicationContext = ApplicationProvider.getApplicationContext() - val enUsConfiguration = Configuration(applicationContext.resources.configuration).apply { - setLocale(Locale.US) + private val context = + run { + val applicationContext = ApplicationProvider.getApplicationContext() + val enUsConfiguration = + Configuration(applicationContext.resources.configuration).apply { + setLocale(Locale.US) + } + applicationContext.createConfigurationContext(enUsConfiguration) } - applicationContext.createConfigurationContext(enUsConfiguration) - } } @Test diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/WalletCoordinator.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/WalletCoordinator.kt index 7bf3a1c9..6bb5713f 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/WalletCoordinator.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/WalletCoordinator.kt @@ -31,8 +31,7 @@ import java.util.UUID /** * @param persistableWallet flow of the user's stored wallet. Null indicates that no wallet has been stored. - */ -/* + * * One area where this class needs to change before it can be moved out of the incubator is that we need to be able to * start synchronization without necessarily decrypting the wallet. * @@ -43,7 +42,6 @@ class WalletCoordinator( context: Context, val persistableWallet: Flow ) { - private val applicationContext = context.applicationContext /* @@ -60,35 +58,39 @@ class WalletCoordinator( private sealed class InternalSynchronizerStatus { object NoWallet : InternalSynchronizerStatus() + class Available(val synchronizer: Synchronizer) : InternalSynchronizerStatus() + class Lockout(val id: UUID) : InternalSynchronizerStatus() } - private val synchronizerOrLockoutId: Flow> = persistableWallet - .combine(synchronizerLockoutId) { persistableWallet: PersistableWallet?, lockoutId: UUID? -> - if (null != lockoutId) { // this one needs to come first - flowOf(InternalSynchronizerStatus.Lockout(lockoutId)) - } else if (null == persistableWallet) { - flowOf(InternalSynchronizerStatus.NoWallet) - } else { - callbackFlow { - val closeableSynchronizer = Synchronizer.new( - context = context, - zcashNetwork = persistableWallet.network, - lightWalletEndpoint = persistableWallet.endpoint, - birthday = persistableWallet.birthday, - seed = persistableWallet.seedPhrase.toByteArray(), - walletInitMode = persistableWallet.walletInitMode, - ) + private val synchronizerOrLockoutId: Flow> = + persistableWallet + .combine(synchronizerLockoutId) { persistableWallet: PersistableWallet?, lockoutId: UUID? -> + if (null != lockoutId) { // this one needs to come first + flowOf(InternalSynchronizerStatus.Lockout(lockoutId)) + } else if (null == persistableWallet) { + flowOf(InternalSynchronizerStatus.NoWallet) + } else { + callbackFlow { + val closeableSynchronizer = + Synchronizer.new( + context = context, + zcashNetwork = persistableWallet.network, + lightWalletEndpoint = persistableWallet.endpoint, + birthday = persistableWallet.birthday, + seed = persistableWallet.seedPhrase.toByteArray(), + walletInitMode = persistableWallet.walletInitMode, + ) - trySend(InternalSynchronizerStatus.Available(closeableSynchronizer)) - awaitClose { - Twig.info { "Closing flow and stopping synchronizer" } - closeableSynchronizer.close() + trySend(InternalSynchronizerStatus.Available(closeableSynchronizer)) + awaitClose { + Twig.info { "Closing flow and stopping synchronizer" } + closeableSynchronizer.close() + } } } } - } /** * Synchronizer for the Zcash SDK. Emits null until a wallet secret is persisted. @@ -97,22 +99,23 @@ class WalletCoordinator( * cases, see [WalletViewModel]. */ @OptIn(ExperimentalCoroutinesApi::class) - val synchronizer: StateFlow = synchronizerOrLockoutId - .flatMapLatest { - it - } - .map { - when (it) { - is InternalSynchronizerStatus.Available -> it.synchronizer - is InternalSynchronizerStatus.Lockout -> null - InternalSynchronizerStatus.NoWallet -> null + val synchronizer: StateFlow = + synchronizerOrLockoutId + .flatMapLatest { + it } - } - .stateIn( - walletScope, - SharingStarted.WhileSubscribed(), - null - ) + .map { + when (it) { + is InternalSynchronizerStatus.Available -> it.synchronizer + is InternalSynchronizerStatus.Lockout -> null + InternalSynchronizerStatus.NoWallet -> null + } + } + .stateIn( + walletScope, + SharingStarted.WhileSubscribed(), + null + ) /** * Rescans the blockchain. @@ -154,10 +157,11 @@ class WalletCoordinator( .filter { it.id == lockoutId } .onFirst { synchronizerMutex.withLock { - val didDelete = Synchronizer.erase( - appContext = applicationContext, - network = zcashNetwork - ) + val didDelete = + Synchronizer.erase( + appContext = applicationContext, + network = zcashNetwork + ) Twig.info { "SDK erase result: $didDelete" } } } diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletAddressFixture.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletAddressFixture.kt index 2dc57bc1..b17c321d 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletAddressFixture.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletAddressFixture.kt @@ -12,6 +12,8 @@ object WalletAddressFixture { const val TRANSPARENT_ADDRESS_STRING = "t1QZMTZaU1EwXppCLL5dR6U9y2M4ph3CSPK" suspend fun unified() = WalletAddress.Unified.new(UNIFIED_ADDRESS_STRING) + suspend fun sapling() = WalletAddress.Sapling.new(SAPLING_ADDRESS_STRING) + suspend fun transparent() = WalletAddress.Transparent.new(TRANSPARENT_ADDRESS_STRING) } diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletAddressesFixture.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletAddressesFixture.kt index 75dabb51..eab20b0d 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletAddressesFixture.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletAddressesFixture.kt @@ -4,7 +4,6 @@ import cash.z.ecc.android.sdk.model.WalletAddress import cash.z.ecc.android.sdk.model.WalletAddresses object WalletAddressesFixture { - suspend fun new( unified: String = WalletAddressFixture.UNIFIED_ADDRESS_STRING, sapling: String = WalletAddressFixture.SAPLING_ADDRESS_STRING, diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt index f01b06d7..ad26d829 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt @@ -29,92 +29,93 @@ sealed class WalletFixture { @Suppress("MaxLineLength") object Ben : WalletFixture() { override val seedPhrase: String - get() = "kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree shock scan alpha just spot fluid toilet view dinner" + get() = + "kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree" + + " shock scan alpha just spot fluid toilet view dinner" // These birthdays were the latest checkpoint at the time this was implemented // Moving these forward will improve testing time, while leaving old transactions behind @Suppress("MagicNumber") - override fun getBirthday(zcashNetwork: ZcashNetwork) = when (zcashNetwork.id) { - ZcashNetwork.ID_TESTNET -> { - BlockHeight.new(zcashNetwork, 2170000L) + override fun getBirthday(zcashNetwork: ZcashNetwork) = + when (zcashNetwork.id) { + ZcashNetwork.ID_TESTNET -> { + BlockHeight.new(zcashNetwork, 2170000L) + } + ZcashNetwork.ID_MAINNET -> { + BlockHeight.new(zcashNetwork, 1935000L) + } + else -> error("Unknown network $zcashNetwork") } - ZcashNetwork.ID_MAINNET -> { - BlockHeight.new(zcashNetwork, 1935000L) + @Suppress("ktlint:standard:max-line-length") + override fun getAddresses(zcashNetwork: ZcashNetwork) = + when (zcashNetwork.id) { + ZcashNetwork.ID_TESTNET -> { + Addresses( + unified = + "utest1vergg5jkp4xy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzjanqtl8uqp5vln3zyy246ejtx86vqftp73j7jg9099jxafyjhfm6u956j3", + sapling = + "ztestsapling17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwu2syhnf", + transparent = "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE" + ) + } + ZcashNetwork.ID_MAINNET -> { + Addresses( + unified = + "u1lmy8anuylj33arxh3sx7ysq54tuw7zehsv6pdeeaqlrhkjhm3uvl9egqxqfd7hcsp3mszp6jxxx0gsw0ldp5wyu95r4mfzlueh8h5xhrjqgz7xtxp3hvw45dn4gfrz5j54ryg6reyf0", + sapling = + "zs1t06xldkqkayhp0lj98kunuq6gz3md0lw3r7q2x82rc94dy8z3hsjhuh6smpnlg9c2za3sq34w5m", + transparent = "t1JP7PHu72xHztsZiwH6cye4yvC9Prb3EvQ" + ) + } + else -> error("Unknown network $zcashNetwork") } - - else -> error("Unknown network $zcashNetwork") - } - - override fun getAddresses(zcashNetwork: ZcashNetwork) = when (zcashNetwork.id) { - ZcashNetwork.ID_TESTNET -> { - Addresses( - unified = - "utest1vergg5jkp4xy8sqfasw6s5zkdpnxvfxlxh35uuc3me7dp596y2r05t6dv9htwe3pf8ksrfr8ksca2lskzjanqtl8uqp5vln3zyy246ejtx86vqftp73j7jg9099jxafyjhfm6u956j3", - sapling = - "ztestsapling17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwu2syhnf", - transparent = "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE" - ) - } - - ZcashNetwork.ID_MAINNET -> { - Addresses( - unified = - "u1lmy8anuylj33arxh3sx7ysq54tuw7zehsv6pdeeaqlrhkjhm3uvl9egqxqfd7hcsp3mszp6jxxx0gsw0ldp5wyu95r4mfzlueh8h5xhrjqgz7xtxp3hvw45dn4gfrz5j54ryg6reyf0", - sapling = - "zs1t06xldkqkayhp0lj98kunuq6gz3md0lw3r7q2x82rc94dy8z3hsjhuh6smpnlg9c2za3sq34w5m", - transparent = "t1JP7PHu72xHztsZiwH6cye4yvC9Prb3EvQ" - ) - } - - else -> error("Unknown network $zcashNetwork") - } } @Suppress("MaxLineLength") object Alice : WalletFixture() { - override val seedPhrase: String - get() = "wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame" + get() = + "wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon" + + " grab despair throw review deal slush frame" // These birthdays were the latest checkpoint at the time this was implemented // Moving these forward will improve testing time, while leaving old transactions behind @Suppress("MagicNumber") - override fun getBirthday(zcashNetwork: ZcashNetwork) = when (zcashNetwork.id) { - ZcashNetwork.ID_TESTNET -> { - BlockHeight.new(zcashNetwork, 2170000L) + override fun getBirthday(zcashNetwork: ZcashNetwork) = + when (zcashNetwork.id) { + ZcashNetwork.ID_TESTNET -> { + BlockHeight.new(zcashNetwork, 2170000L) + } + ZcashNetwork.ID_MAINNET -> { + BlockHeight.new(zcashNetwork, 1935000L) + } + else -> error("Unknown network $zcashNetwork") } - ZcashNetwork.ID_MAINNET -> { - BlockHeight.new(zcashNetwork, 1935000L) + @Suppress("ktlint:standard:max-line-length") + override fun getAddresses(zcashNetwork: ZcashNetwork) = + when (zcashNetwork.id) { + ZcashNetwork.ID_TESTNET -> { + Addresses( + unified = + "utest16zd8zfx6n6few7mjsjpn6qtn8tlg6law7qnq33257855mdqekk7vru8lettx3vud4mh99elglddltmfjkduar69h7vy08h3xdq6zuls9pqq7quyuehjqwtthc3hfd8gshhw42dfr96e", + sapling = + "ztestsapling1zhqvuq8zdwa8nsnde7074kcfsat0w25n08jzuvz5skzcs6h9raxu898l48xwr8fmkny3zqqrgd9", + transparent = "tmCxJG72RWN66xwPtNgu4iKHpyysGrc7rEg" + ) + } + ZcashNetwork.ID_MAINNET -> { + Addresses( + unified = + "u1czzc8jcl50svfezmfc9xsxnh63p374nptqplt0yw2uekr7v9wprp84y6esys6derp6uvdcq6x6ykjrkpdyhjzneq5ud78h6j68n63hewg7xp9fpneuh64wgzt3d7mh6zh3qpqapzlc4", + sapling = + "zs15tzaulx5weua5c7l47l4pku2pw9fzwvvnsp4y80jdpul0y3nwn5zp7tmkcclqaca3mdjqjkl7hx", + transparent = "t1duiEGg7b39nfQee3XaTY4f5McqfyJKhBi" + ) + } + else -> error("Unknown network $zcashNetwork") } - - else -> error("Unknown network $zcashNetwork") - } - - override fun getAddresses(zcashNetwork: ZcashNetwork) = when (zcashNetwork.id) { - ZcashNetwork.ID_TESTNET -> { - Addresses( - unified = - "utest16zd8zfx6n6few7mjsjpn6qtn8tlg6law7qnq33257855mdqekk7vru8lettx3vud4mh99elglddltmfjkduar69h7vy08h3xdq6zuls9pqq7quyuehjqwtthc3hfd8gshhw42dfr96e", - sapling = - "ztestsapling1zhqvuq8zdwa8nsnde7074kcfsat0w25n08jzuvz5skzcs6h9raxu898l48xwr8fmkny3zqqrgd9", - transparent = "tmCxJG72RWN66xwPtNgu4iKHpyysGrc7rEg" - ) - } - - ZcashNetwork.ID_MAINNET -> { - Addresses( - unified = - "u1czzc8jcl50svfezmfc9xsxnh63p374nptqplt0yw2uekr7v9wprp84y6esys6derp6uvdcq6x6ykjrkpdyhjzneq5ud78h6j68n63hewg7xp9fpneuh64wgzt3d7mh6zh3qpqapzlc4", - sapling = - "zs15tzaulx5weua5c7l47l4pku2pw9fzwvvnsp4y80jdpul0y3nwn5zp7tmkcclqaca3mdjqjkl7hx", - transparent = "t1duiEGg7b39nfQee3XaTY4f5McqfyJKhBi" - ) - } - - else -> error("Unknown network $zcashNetwork") - } } } diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/FiatCurrencyConversionRateState.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/FiatCurrencyConversionRateState.kt index 8cc0c76b..92589f1f 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/FiatCurrencyConversionRateState.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/FiatCurrencyConversionRateState.kt @@ -16,10 +16,10 @@ sealed class FiatCurrencyConversionRateState { * @param formattedFiatValue A fiat value formatted as a localized string. E.g. $1.00. */ data class Stale(val formattedFiatValue: String) : FiatCurrencyConversionRateState() + object Unavailable : FiatCurrencyConversionRateState() companion object { - /** * Cutoff negative age. Some users may intentionally set their clock forward 10 minutes * because they're always late to things. This allows the app to mostly work for those users, diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/Locale.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/Locale.kt index 8c4280d2..633ce8df 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/Locale.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/Locale.kt @@ -15,17 +15,19 @@ fun Locale.toJavaLocale(): java.util.Locale { } fun java.util.Locale.toKotlinLocale(): Locale { - val resultCountry = if (country.isNullOrEmpty()) { - null - } else { - country - } + val resultCountry = + if (country.isNullOrEmpty()) { + null + } else { + country + } - val resultVariant = if (variant.isNullOrEmpty()) { - null - } else { - variant - } + val resultVariant = + if (variant.isNullOrEmpty()) { + null + } else { + variant + } return Locale(language, resultCountry, resultVariant) } diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/PersistableWallet.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/PersistableWallet.kt index c2c7b9da..93a2f5ba 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/PersistableWallet.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/PersistableWallet.kt @@ -31,25 +31,27 @@ data class PersistableWallet( val walletInitMode: WalletInitMode ) { init { - _walletInitMode = walletInitMode + walletInitModeHolder = walletInitMode } /** * @return Wallet serialized to JSON format, suitable for long-term encrypted storage. - */ - // Note: We're using a hand-crafted serializer so that we're less likely to have accidental - // breakage from reflection or annotation based methods, and so that we can carefully manage versioning. - fun toJson() = JSONObject().apply { - put(KEY_VERSION, VERSION_2) - put(KEY_NETWORK_ID, network.id) - put(KEY_ENDPOINT_HOST, endpoint.host) - put(KEY_ENDPOINT_PORT, endpoint.port) - put(KEY_ENDPOINT_IS_SECURE, endpoint.isSecure) - birthday?.let { - put(KEY_BIRTHDAY, it.value) + * + * Note: We're using a hand-crafted serializer so that we're less likely to have accidental + * breakage from reflection or annotation based methods, and so that we can carefully manage versioning. + **/ + fun toJson() = + JSONObject().apply { + put(KEY_VERSION, VERSION_2) + put(KEY_NETWORK_ID, network.id) + put(KEY_ENDPOINT_HOST, endpoint.host) + put(KEY_ENDPOINT_PORT, endpoint.port) + put(KEY_ENDPOINT_IS_SECURE, endpoint.isSecure) + birthday?.let { + put(KEY_BIRTHDAY, it.value) + } + put(KEY_SEED_PHRASE, seedPhrase.joinToString()) } - put(KEY_SEED_PHRASE, seedPhrase.joinToString()) - } // For security, intentionally override the toString method to reduce risk of accidentally logging secrets override fun toString() = "PersistableWallet" @@ -68,7 +70,7 @@ data class PersistableWallet( // Note: [walletInitMode] is excluded from the serialization to avoid persisting the wallet initialization mode // with the persistable wallet. - private var _walletInitMode: WalletInitMode = WalletInitMode.ExistingWallet + private var walletInitModeHolder: WalletInitMode = WalletInitMode.ExistingWallet fun from(jsonObject: JSONObject): PersistableWallet { // Common parameters @@ -95,21 +97,27 @@ data class PersistableWallet( endpoint = endpoint, birthday = birthday, seedPhrase = SeedPhrase.new(seedPhrase), - walletInitMode = _walletInitMode + walletInitMode = walletInitModeHolder ) } internal fun getVersion(jsonObject: JSONObject): Int { return jsonObject.getInt(KEY_VERSION) } + internal fun getSeedPhrase(jsonObject: JSONObject): String { return jsonObject.getString(KEY_SEED_PHRASE) } + internal fun getNetwork(jsonObject: JSONObject): ZcashNetwork { val networkId = jsonObject.getInt(KEY_NETWORK_ID) return ZcashNetwork.from(networkId) } - internal fun getBirthday(jsonObject: JSONObject, network: ZcashNetwork): BlockHeight? { + + internal fun getBirthday( + jsonObject: JSONObject, + network: ZcashNetwork + ): BlockHeight? { return if (jsonObject.has(KEY_BIRTHDAY)) { val birthdayBlockHeightLong = jsonObject.getLong(KEY_BIRTHDAY) BlockHeight.new(network, birthdayBlockHeightLong) @@ -117,6 +125,7 @@ data class PersistableWallet( null } } + internal fun getEndpoint(jsonObject: JSONObject): LightWalletEndpoint { return jsonObject.run { val host = getString(KEY_ENDPOINT_HOST) @@ -185,8 +194,9 @@ data class PersistableWallet( } // Using IO context because of https://github.com/zcash/kotlin-bip39/issues/13 -private suspend fun newMnemonic() = withContext(Dispatchers.IO) { - Mnemonics.MnemonicCode(cash.z.ecc.android.bip39.Mnemonics.WordCount.COUNT_24.toEntropy()).words -} +private suspend fun newMnemonic() = + withContext(Dispatchers.IO) { + Mnemonics.MnemonicCode(cash.z.ecc.android.bip39.Mnemonics.WordCount.COUNT_24.toEntropy()).words + } private suspend fun newSeedPhrase() = SeedPhrase(newMnemonic().map { it.concatToString() }) diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletAddresses.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletAddresses.kt index 9b2ab04a..67424731 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletAddresses.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletAddresses.kt @@ -12,17 +12,20 @@ data class WalletAddresses( companion object { suspend fun new(synchronizer: Synchronizer): WalletAddresses { - val unified = WalletAddress.Unified.new( - synchronizer.getUnifiedAddress(Account.DEFAULT) - ) + val unified = + WalletAddress.Unified.new( + synchronizer.getUnifiedAddress(Account.DEFAULT) + ) - val saplingAddress = WalletAddress.Sapling.new( - synchronizer.getSaplingAddress(Account.DEFAULT) - ) + val saplingAddress = + WalletAddress.Sapling.new( + synchronizer.getSaplingAddress(Account.DEFAULT) + ) - val transparentAddress = WalletAddress.Transparent.new( - synchronizer.getTransparentAddress(Account.DEFAULT) - ) + val transparentAddress = + WalletAddress.Transparent.new( + synchronizer.getTransparentAddress(Account.DEFAULT) + ) return WalletAddresses( unified = unified, diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZatoshiExt.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZatoshiExt.kt index ee99feff..f48f5a7c 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZatoshiExt.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZatoshiExt.kt @@ -45,14 +45,13 @@ fun Zatoshi.toFiatString( currencyConversion: CurrencyConversion, locale: Locale, monetarySeparators: MonetarySeparators -) = - convertZatoshiToZecDecimal() - .convertZecDecimalToFiatDecimal(BigDecimal(currencyConversion.priceOfZec)) - .convertFiatDecimalToFiatString( - Currency.getInstance(currencyConversion.fiatCurrency.code), - locale.toJavaLocale(), - monetarySeparators - ) +) = convertZatoshiToZecDecimal() + .convertZecDecimalToFiatDecimal(BigDecimal(currencyConversion.priceOfZec)) + .convertFiatDecimalToFiatString( + Currency.getInstance(currencyConversion.fiatCurrency.code), + locale.toJavaLocale(), + monetarySeparators + ) private fun Zatoshi.convertZatoshiToZecDecimal(): BigDecimal { return BigDecimal(value, MathContext.DECIMAL128).divide( diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSend.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSend.kt index af132d60..dcf61e6c 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSend.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSend.kt @@ -6,7 +6,10 @@ data class ZecSend(val destination: WalletAddress, val amount: Zatoshi, val memo companion object } -suspend fun Synchronizer.send(spendingKey: UnifiedSpendingKey, send: ZecSend) = sendToAddress( +suspend fun Synchronizer.send( + spendingKey: UnifiedSpendingKey, + send: ZecSend +) = sendToAddress( spendingKey, send.amount, send.destination.address, diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSendExt.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSendExt.kt index 1f5e2447..fdc758ac 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSendExt.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSendExt.kt @@ -4,7 +4,6 @@ import android.content.Context import kotlinx.coroutines.runBlocking object ZecSendExt { - fun new( context: Context, destinationString: String, @@ -18,13 +17,14 @@ object ZecSendExt { val amount = Zatoshi.fromZecString(context, zecString, monetarySeparators) val memo = Memo(memoString) - val validationErrors = buildSet { - if (null == amount) { - add(ZecSendValidation.Invalid.ValidationError.INVALID_AMOUNT) - } + val validationErrors = + buildSet { + if (null == amount) { + add(ZecSendValidation.Invalid.ValidationError.INVALID_AMOUNT) + } - // TODO [#342]: https://github.com/zcash/zcash-android-wallet-sdk/issues/342 - } + // TODO [#342]: https://github.com/zcash/zcash-android-wallet-sdk/issues/342 + } return if (validationErrors.isEmpty()) { ZecSendValidation.Valid(ZecSend(destination, amount!!, memo)) @@ -35,6 +35,7 @@ object ZecSendExt { sealed class ZecSendValidation { data class Valid(val zecSend: ZecSend) : ZecSendValidation() + data class Invalid(val validationErrors: Set) : ZecSendValidation() { enum class ValidationError { INVALID_ADDRESS, diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecString.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecString.kt index c738f13a..a997da5b 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecString.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecString.kt @@ -11,21 +11,21 @@ import java.text.ParseException import java.util.Locale object ZecString { - - fun allowedCharacters(monetarySeparators: MonetarySeparators) = buildSet { - add('0') - add('1') - add('2') - add('3') - add('4') - add('5') - add('6') - add('7') - add('8') - add('9') - add(monetarySeparators.decimal) - add(monetarySeparators.grouping) - } + fun allowedCharacters(monetarySeparators: MonetarySeparators) = + buildSet { + add('0') + add('1') + add('2') + add('3') + add('4') + add('5') + add('6') + add('7') + add('8') + add('9') + add(monetarySeparators.decimal) + add(monetarySeparators.grouping) + } } data class MonetarySeparators(val grouping: Char, val decimal: Char) { @@ -62,6 +62,7 @@ fun Zatoshi.toZecString() = convertZatoshiToZecString(DECIMALS, DECIMALS) * separator characters based on the user's current Locale. This should avoid unexpected surprises * while also localizing the separator format. */ + /** * @return [zecString] parsed into Zatoshi or null if parsing failed. */ @@ -75,26 +76,29 @@ fun Zatoshi.Companion.fromZecString( return null } - val symbols = DecimalFormatSymbols.getInstance(Locale.US).apply { - this.groupingSeparator = monetarySeparators.grouping - this.decimalSeparator = monetarySeparators.decimal - } + val symbols = + DecimalFormatSymbols.getInstance(Locale.US).apply { + this.groupingSeparator = monetarySeparators.grouping + this.decimalSeparator = monetarySeparators.decimal + } val localizedPattern = "#${monetarySeparators.grouping}##0${monetarySeparators.decimal}0#" // TODO [#321]: https://github.com/zcash/secant-android-wallet/issues/321 - val decimalFormat = DecimalFormat(localizedPattern, symbols).apply { - isParseBigDecimal = true - roundingMode = RoundingMode.HALF_EVEN // aka Bankers rounding - } + val decimalFormat = + DecimalFormat(localizedPattern, symbols).apply { + isParseBigDecimal = true + roundingMode = RoundingMode.HALF_EVEN // aka Bankers rounding + } // TODO [#343]: https://github.com/zcash/secant-android-wallet/issues/343 - val bigDecimal = try { - decimalFormat.parse(zecString) as BigDecimal - } catch (e: NumberFormatException) { - null - } catch (e: ParseException) { - null - } + val bigDecimal = + try { + decimalFormat.parse(zecString) as BigDecimal + } catch (e: NumberFormatException) { + null + } catch (e: ParseException) { + null + } @Suppress("SwallowedException") return try { diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecStringExt.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecStringExt.kt index 27e1ef46..ae29326e 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecStringExt.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecStringExt.kt @@ -4,7 +4,6 @@ import android.content.Context import cash.z.ecc.android.sdk.incubator.R object ZecStringExt { - private const val DIGITS_BETWEEN_GROUP_SEPARATORS = 3 /** @@ -23,7 +22,11 @@ object ZecStringExt { * * @return true in case of validation success, false otherwise */ - fun filterContinuous(context: Context, separators: MonetarySeparators, zecString: String): Boolean { + fun filterContinuous( + context: Context, + separators: MonetarySeparators, + zecString: String + ): Boolean { if (!context.getString( R.string.co_electriccoin_zcash_zec_amount_regex_continuous_filter, separators.grouping, @@ -43,7 +46,10 @@ object ZecStringExt { * * @return true in case of validation success, false otherwise */ - fun checkFor3Digits(separators: MonetarySeparators, zecString: String): Boolean { + fun checkFor3Digits( + separators: MonetarySeparators, + zecString: String + ): Boolean { if (zecString.count { it == separators.grouping } >= 2) { val groups = zecString.split(separators.grouping) for (i in 1 until (groups.size - 1)) { @@ -72,7 +78,11 @@ object ZecStringExt { * * @return true in case of validation success, false otherwise */ - fun filterConfirm(context: Context, separators: MonetarySeparators, zecString: String): Boolean { + fun filterConfirm( + context: Context, + separators: MonetarySeparators, + zecString: String + ): Boolean { if (zecString.isBlank() || zecString == separators.grouping.toString() || zecString == separators.decimal.toString() @@ -86,6 +96,6 @@ object ZecStringExt { separators.grouping, separators.decimal ).toRegex().matches(zecString) && checkFor3Digits(separators, zecString) - ) + ) } } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/AssetTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/AssetTest.kt index 5013c181..9471ce18 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/AssetTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/AssetTest.kt @@ -13,7 +13,6 @@ import org.junit.Assert.assertTrue import org.junit.Test class AssetTest { - @Test @SmallTest fun validate_mainnet_assets() { @@ -55,7 +54,10 @@ class AssetTest { } } - private fun assertFileContents(network: ZcashNetwork, files: Array?) { + private fun assertFileContents( + network: ZcashNetwork, + files: Array? + ) { files?.map { filename -> val filePath = "${CheckpointTool.checkpointDirectory(network)}/$filename" ApplicationProvider.getApplicationContext().assets.open(filePath) @@ -74,11 +76,12 @@ class AssetTest { assertTrue(jsonObject.has("time")) assertTrue(jsonObject.has("saplingTree")) - val expectedNetworkName = when (network) { - ZcashNetwork.Mainnet -> "main" - ZcashNetwork.Testnet -> "test" - else -> IllegalArgumentException("Unsupported network $network") - } + val expectedNetworkName = + when (network) { + ZcashNetwork.Mainnet -> "main" + ZcashNetwork.Testnet -> "test" + else -> IllegalArgumentException("Unsupported network $network") + } assertEquals("File: ${it.filename}", expectedNetworkName, jsonObject.getString("network")) assertEquals( @@ -94,11 +97,12 @@ class AssetTest { private data class JsonFile(val jsonObject: JSONObject, val filename: String) companion object { - fun listAssets(network: ZcashNetwork): Array? = runBlocking { - CheckpointTool.listCheckpointDirectoryContents( - ApplicationProvider.getApplicationContext(), - CheckpointTool.checkpointDirectory(network) - ) - } + fun listAssets(network: ZcashNetwork): Array? = + runBlocking { + CheckpointTool.listCheckpointDirectoryContents( + ApplicationProvider.getApplicationContext(), + CheckpointTool.checkpointDirectory(network) + ) + } } } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/annotation/MaintainedTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/annotation/MaintainedTest.kt index 8e5f225b..6b954f20 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/annotation/MaintainedTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/annotation/MaintainedTest.kt @@ -1,7 +1,6 @@ package cash.z.ecc.android.sdk.annotation enum class TestPurpose { - /** * These tests are explicitly designed to preserve behavior that we do not want to lose after * major upgrades or refactors. It is acceptable for these test to run long and require diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/db/DatabaseCoordinatorTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/db/DatabaseCoordinatorTest.kt index 84e79a50..07b080a6 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/db/DatabaseCoordinatorTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/db/DatabaseCoordinatorTest.kt @@ -17,7 +17,6 @@ import org.junit.Test import java.io.File class DatabaseCoordinatorTest { - private val dbCoordinator = DatabaseCoordinator.getInstance(getAppContext()) @Before @@ -30,123 +29,140 @@ class DatabaseCoordinatorTest { @Test @SmallTest - fun database_cache_root_directory_creation_test() = runTest { - val parentDirectory = File(DatabasePathFixture.new()) - val destinationDirectory = DatabaseCacheFilesRootFixture.newCacheRoot() - val expectedDirectoryPath = File(parentDirectory, destinationDirectory).path + fun database_cache_root_directory_creation_test() = + runTest { + val parentDirectory = File(DatabasePathFixture.new()) + val destinationDirectory = DatabaseCacheFilesRootFixture.newCacheRoot() + val expectedDirectoryPath = File(parentDirectory, destinationDirectory).path - dbCoordinator.fsBlockDbRoot( - DatabaseNameFixture.TEST_DB_NETWORK, - DatabaseNameFixture.TEST_DB_ALIAS - ).also { resultFile -> - assertEquals(expectedDirectoryPath, resultFile.absolutePath) + dbCoordinator.fsBlockDbRoot( + DatabaseNameFixture.TEST_DB_NETWORK, + DatabaseNameFixture.TEST_DB_ALIAS + ).also { resultFile -> + assertEquals(expectedDirectoryPath, resultFile.absolutePath) + } } - } @Test @SmallTest - fun database_data_file_creation_test() = runTest { - val directory = File(DatabasePathFixture.new()) - val fileName = DatabaseNameFixture.newDb(name = DatabaseCoordinator.DB_DATA_NAME) - val expectedFilePath = File(directory, fileName).path + fun database_data_file_creation_test() = + runTest { + val directory = File(DatabasePathFixture.new()) + val fileName = DatabaseNameFixture.newDb(name = DatabaseCoordinator.DB_DATA_NAME) + val expectedFilePath = File(directory, fileName).path - dbCoordinator.dataDbFile( - DatabaseNameFixture.TEST_DB_NETWORK, - DatabaseNameFixture.TEST_DB_ALIAS - ).also { resultFile -> - assertEquals(expectedFilePath, resultFile.absolutePath) + dbCoordinator.dataDbFile( + DatabaseNameFixture.TEST_DB_NETWORK, + DatabaseNameFixture.TEST_DB_ALIAS + ).also { resultFile -> + assertEquals(expectedFilePath, resultFile.absolutePath) + } } - } @Test @SmallTest - fun database_transactions_file_creation_test() = runTest { - val directory = File(DatabasePathFixture.new()) - val fileName = DatabaseNameFixture.newDb(name = DatabaseCoordinator.DB_PENDING_TRANSACTIONS_NAME) - val expectedFilePath = File(directory, fileName).path + fun database_transactions_file_creation_test() = + runTest { + val directory = File(DatabasePathFixture.new()) + val fileName = DatabaseNameFixture.newDb(name = DatabaseCoordinator.DB_PENDING_TRANSACTIONS_NAME) + val expectedFilePath = File(directory, fileName).path - dbCoordinator.pendingTransactionsDbFile( - DatabaseNameFixture.TEST_DB_NETWORK, - DatabaseNameFixture.TEST_DB_ALIAS - ).also { resultFile -> - assertEquals(expectedFilePath, resultFile.absolutePath) + dbCoordinator.pendingTransactionsDbFile( + DatabaseNameFixture.TEST_DB_NETWORK, + DatabaseNameFixture.TEST_DB_ALIAS + ).also { resultFile -> + assertEquals(expectedFilePath, resultFile.absolutePath) + } } - } @Suppress("LongMethod") @Test @SmallTest - fun data_database_files_move_test() = runTest { - val parentFile = File( - DatabasePathFixture.new( - baseFolderPath = DatabasePathFixture.DATABASE_DIR_PATH, - internalPath = "" - ) - ) + fun data_database_files_move_test() = + runTest { + val parentFile = + File( + DatabasePathFixture.new( + baseFolderPath = DatabasePathFixture.DATABASE_DIR_PATH, + internalPath = "" + ) + ) - val originalDbFile = getEmptyFile( - parent = parentFile, - fileName = DatabaseNameFixture.newDb( - name = DatabaseCoordinator.DB_DATA_NAME_LEGACY, - alias = DatabaseCoordinator.ALIAS_LEGACY - ) - ) + val originalDbFile = + getEmptyFile( + parent = parentFile, + fileName = + DatabaseNameFixture.newDb( + name = DatabaseCoordinator.DB_DATA_NAME_LEGACY, + alias = DatabaseCoordinator.ALIAS_LEGACY + ) + ) - val originalDbJournalFile = getEmptyFile( - parent = parentFile, - fileName = DatabaseNameFixture.newDbJournal( - name = DatabaseCoordinator.DB_DATA_NAME_LEGACY, - alias = DatabaseCoordinator.ALIAS_LEGACY - ) - ) + val originalDbJournalFile = + getEmptyFile( + parent = parentFile, + fileName = + DatabaseNameFixture.newDbJournal( + name = DatabaseCoordinator.DB_DATA_NAME_LEGACY, + alias = DatabaseCoordinator.ALIAS_LEGACY + ) + ) - val originalDbWalFile = getEmptyFile( - parent = parentFile, - fileName = DatabaseNameFixture.newDbWal( - name = DatabaseCoordinator.DB_DATA_NAME_LEGACY, - alias = DatabaseCoordinator.ALIAS_LEGACY - ) - ) + val originalDbWalFile = + getEmptyFile( + parent = parentFile, + fileName = + DatabaseNameFixture.newDbWal( + name = DatabaseCoordinator.DB_DATA_NAME_LEGACY, + alias = DatabaseCoordinator.ALIAS_LEGACY + ) + ) - val expectedDbFile = File( - DatabasePathFixture.new(), - DatabaseNameFixture.newDb(name = DatabaseCoordinator.DB_DATA_NAME) - ) - val expectedDbJournalFile = File( - DatabasePathFixture.new(), - DatabaseNameFixture.newDbJournal(name = DatabaseCoordinator.DB_DATA_NAME) - ) - val expectedDbWalFile = File( - DatabasePathFixture.new(), - DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_DATA_NAME) - ) + val expectedDbFile = + File( + DatabasePathFixture.new(), + DatabaseNameFixture.newDb(name = DatabaseCoordinator.DB_DATA_NAME) + ) + val expectedDbJournalFile = + File( + DatabasePathFixture.new(), + DatabaseNameFixture.newDbJournal(name = DatabaseCoordinator.DB_DATA_NAME) + ) + val expectedDbWalFile = + File( + DatabasePathFixture.new(), + DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_DATA_NAME) + ) - assertTrue(originalDbFile.exists()) - assertTrue(originalDbJournalFile.exists()) - assertTrue(originalDbWalFile.exists()) + assertTrue(originalDbFile.exists()) + assertTrue(originalDbJournalFile.exists()) + assertTrue(originalDbWalFile.exists()) - assertFalse(expectedDbFile.exists()) - assertFalse(expectedDbJournalFile.exists()) - assertFalse(expectedDbWalFile.exists()) + assertFalse(expectedDbFile.exists()) + assertFalse(expectedDbJournalFile.exists()) + assertFalse(expectedDbWalFile.exists()) - dbCoordinator.dataDbFile( - DatabaseNameFixture.TEST_DB_NETWORK, - DatabaseNameFixture.TEST_DB_ALIAS - ).also { resultFile -> - assertTrue(resultFile.exists()) - assertEquals(expectedDbFile.absolutePath, resultFile.absolutePath) + dbCoordinator.dataDbFile( + DatabaseNameFixture.TEST_DB_NETWORK, + DatabaseNameFixture.TEST_DB_ALIAS + ).also { resultFile -> + assertTrue(resultFile.exists()) + assertEquals(expectedDbFile.absolutePath, resultFile.absolutePath) - assertTrue(expectedDbFile.exists()) - assertTrue(expectedDbJournalFile.exists()) - assertTrue(expectedDbWalFile.exists()) + assertTrue(expectedDbFile.exists()) + assertTrue(expectedDbJournalFile.exists()) + assertTrue(expectedDbWalFile.exists()) - assertFalse(originalDbFile.exists()) - assertFalse(originalDbJournalFile.exists()) - assertFalse(originalDbWalFile.exists()) + assertFalse(originalDbFile.exists()) + assertFalse(originalDbJournalFile.exists()) + assertFalse(originalDbWalFile.exists()) + } } - } - private fun getEmptyFile(parent: File, fileName: String): File { + private fun getEmptyFile( + parent: File, + fileName: String + ): File { return File(parent, fileName).apply { assertTrue(parentFile != null) parentFile!!.mkdirs() @@ -159,39 +175,44 @@ class DatabaseCoordinatorTest { @Test @SmallTest - fun delete_data_database_files_test() = runTest { - val parentFile = File( - DatabasePathFixture.new( - baseFolderPath = DatabasePathFixture.NO_BACKUP_DIR_PATH, - internalPath = DatabasePathFixture.INTERNAL_DATABASE_PATH - ) - ) + fun delete_data_database_files_test() = + runTest { + val parentFile = + File( + DatabasePathFixture.new( + baseFolderPath = DatabasePathFixture.NO_BACKUP_DIR_PATH, + internalPath = DatabasePathFixture.INTERNAL_DATABASE_PATH + ) + ) - val dbFile = getEmptyFile( - parent = parentFile, - fileName = DatabaseNameFixture.newDb(name = DatabaseCoordinator.DB_DATA_NAME) - ) + val dbFile = + getEmptyFile( + parent = parentFile, + fileName = DatabaseNameFixture.newDb(name = DatabaseCoordinator.DB_DATA_NAME) + ) - val dbJournalFile = getEmptyFile( - parent = parentFile, - fileName = DatabaseNameFixture.newDbJournal(name = DatabaseCoordinator.DB_DATA_NAME) - ) + val dbJournalFile = + getEmptyFile( + parent = parentFile, + fileName = DatabaseNameFixture.newDbJournal(name = DatabaseCoordinator.DB_DATA_NAME) + ) - val dbWalFile = getEmptyFile( - parent = parentFile, - fileName = DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_DATA_NAME) - ) + val dbWalFile = + getEmptyFile( + parent = parentFile, + fileName = DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_DATA_NAME) + ) - assertTrue(dbFile.exists()) - assertTrue(dbJournalFile.exists()) - assertTrue(dbWalFile.exists()) + assertTrue(dbFile.exists()) + assertTrue(dbJournalFile.exists()) + assertTrue(dbWalFile.exists()) - dbCoordinator.deleteDatabases(DatabaseNameFixture.TEST_DB_NETWORK, DatabaseNameFixture.TEST_DB_ALIAS).also { - assertFalse(dbFile.exists()) - assertFalse(dbJournalFile.exists()) - assertFalse(dbWalFile.exists()) + dbCoordinator.deleteDatabases(DatabaseNameFixture.TEST_DB_NETWORK, DatabaseNameFixture.TEST_DB_ALIAS).also { + assertFalse(dbFile.exists()) + assertFalse(dbJournalFile.exists()) + assertFalse(dbWalFile.exists()) + } } - } /** * Note that this situation is just hypothetical, as the legacy database files should be placed only on one of @@ -200,125 +221,142 @@ class DatabaseCoordinatorTest { @Suppress("LongMethod") @Test @SmallTest - fun delete_all_legacy_database_files_test() = runTest { - // create older location legacy files - val olderLegacyParentFile = File( - DatabasePathFixture.new( - baseFolderPath = DatabasePathFixture.DATABASE_DIR_PATH, - internalPath = "" - ) - ) + fun delete_all_legacy_database_files_test() = + runTest { + // create older location legacy files + val olderLegacyParentFile = + File( + DatabasePathFixture.new( + baseFolderPath = DatabasePathFixture.DATABASE_DIR_PATH, + internalPath = "" + ) + ) - val olderLegacyDbFile = getEmptyFile( - parent = olderLegacyParentFile, - fileName = DatabaseNameFixture.newDb( - name = DatabaseCoordinator.DB_CACHE_OLDER_NAME_LEGACY, - network = DatabaseNameFixture.TEST_DB_NETWORK.networkName, - alias = DatabaseCoordinator.ALIAS_LEGACY - ) - ) + val olderLegacyDbFile = + getEmptyFile( + parent = olderLegacyParentFile, + fileName = + DatabaseNameFixture.newDb( + name = DatabaseCoordinator.DB_CACHE_OLDER_NAME_LEGACY, + network = DatabaseNameFixture.TEST_DB_NETWORK.networkName, + alias = DatabaseCoordinator.ALIAS_LEGACY + ) + ) - val olderLegacyDbJournalFile = getEmptyFile( - parent = olderLegacyParentFile, - fileName = DatabaseNameFixture.newDbJournal( - name = DatabaseCoordinator.DB_CACHE_OLDER_NAME_LEGACY, - network = DatabaseNameFixture.TEST_DB_NETWORK.networkName, - alias = DatabaseCoordinator.ALIAS_LEGACY - ) - ) + val olderLegacyDbJournalFile = + getEmptyFile( + parent = olderLegacyParentFile, + fileName = + DatabaseNameFixture.newDbJournal( + name = DatabaseCoordinator.DB_CACHE_OLDER_NAME_LEGACY, + network = DatabaseNameFixture.TEST_DB_NETWORK.networkName, + alias = DatabaseCoordinator.ALIAS_LEGACY + ) + ) - val olderLegacyDbWalFile = getEmptyFile( - parent = olderLegacyParentFile, - fileName = DatabaseNameFixture.newDbWal( - name = DatabaseCoordinator.DB_CACHE_OLDER_NAME_LEGACY, - network = DatabaseNameFixture.TEST_DB_NETWORK.networkName, - alias = DatabaseCoordinator.ALIAS_LEGACY - ) - ) + val olderLegacyDbWalFile = + getEmptyFile( + parent = olderLegacyParentFile, + fileName = + DatabaseNameFixture.newDbWal( + name = DatabaseCoordinator.DB_CACHE_OLDER_NAME_LEGACY, + network = DatabaseNameFixture.TEST_DB_NETWORK.networkName, + alias = DatabaseCoordinator.ALIAS_LEGACY + ) + ) - // create newer location legacy files - val newerLegacyParentFile = File( - DatabasePathFixture.new( - baseFolderPath = DatabasePathFixture.NO_BACKUP_DIR_PATH, - internalPath = DatabasePathFixture.INTERNAL_DATABASE_PATH - ) - ) + // create newer location legacy files + val newerLegacyParentFile = + File( + DatabasePathFixture.new( + baseFolderPath = DatabasePathFixture.NO_BACKUP_DIR_PATH, + internalPath = DatabasePathFixture.INTERNAL_DATABASE_PATH + ) + ) - val newerLegacyDbFile = getEmptyFile( - parent = newerLegacyParentFile, - fileName = DatabaseNameFixture.newDb( - name = DatabaseCoordinator.DB_CACHE_NEWER_NAME_LEGACY, - network = DatabaseNameFixture.TEST_DB_NETWORK.networkName, + val newerLegacyDbFile = + getEmptyFile( + parent = newerLegacyParentFile, + fileName = + DatabaseNameFixture.newDb( + name = DatabaseCoordinator.DB_CACHE_NEWER_NAME_LEGACY, + network = DatabaseNameFixture.TEST_DB_NETWORK.networkName, + alias = DatabaseNameFixture.TEST_DB_ALIAS + ) + ) + + val newerLegacyDbJournalFile = + getEmptyFile( + parent = newerLegacyParentFile, + fileName = + DatabaseNameFixture.newDbJournal( + name = DatabaseCoordinator.DB_CACHE_NEWER_NAME_LEGACY, + network = DatabaseNameFixture.TEST_DB_NETWORK.networkName, + alias = DatabaseNameFixture.TEST_DB_ALIAS + ) + ) + + val newerLegacyDbWalFile = + getEmptyFile( + parent = newerLegacyParentFile, + fileName = + DatabaseNameFixture.newDbWal( + name = DatabaseCoordinator.DB_CACHE_NEWER_NAME_LEGACY, + network = DatabaseNameFixture.TEST_DB_NETWORK.networkName, + alias = DatabaseNameFixture.TEST_DB_ALIAS + ) + ) + + // check all files in place + assertTrue(olderLegacyDbFile.exists()) + assertTrue(olderLegacyDbJournalFile.exists()) + assertTrue(olderLegacyDbWalFile.exists()) + + assertTrue(newerLegacyDbFile.exists()) + assertTrue(newerLegacyDbJournalFile.exists()) + assertTrue(newerLegacyDbWalFile.exists()) + + // once we access the latest file system blocks storage root directory, all the legacy database files should + // be removed + dbCoordinator.fsBlockDbRoot( + network = DatabaseNameFixture.TEST_DB_NETWORK, alias = DatabaseNameFixture.TEST_DB_ALIAS - ) - ) + ).also { + assertFalse(olderLegacyDbFile.exists()) + assertFalse(olderLegacyDbJournalFile.exists()) + assertFalse(olderLegacyDbWalFile.exists()) - val newerLegacyDbJournalFile = getEmptyFile( - parent = newerLegacyParentFile, - fileName = DatabaseNameFixture.newDbJournal( - name = DatabaseCoordinator.DB_CACHE_NEWER_NAME_LEGACY, - network = DatabaseNameFixture.TEST_DB_NETWORK.networkName, - alias = DatabaseNameFixture.TEST_DB_ALIAS - ) - ) - - val newerLegacyDbWalFile = getEmptyFile( - parent = newerLegacyParentFile, - fileName = DatabaseNameFixture.newDbWal( - name = DatabaseCoordinator.DB_CACHE_NEWER_NAME_LEGACY, - network = DatabaseNameFixture.TEST_DB_NETWORK.networkName, - alias = DatabaseNameFixture.TEST_DB_ALIAS - ) - ) - - // check all files in place - assertTrue(olderLegacyDbFile.exists()) - assertTrue(olderLegacyDbJournalFile.exists()) - assertTrue(olderLegacyDbWalFile.exists()) - - assertTrue(newerLegacyDbFile.exists()) - assertTrue(newerLegacyDbJournalFile.exists()) - assertTrue(newerLegacyDbWalFile.exists()) - - // once we access the latest file system blocks storage root directory, all the legacy database files should - // be removed - dbCoordinator.fsBlockDbRoot( - network = DatabaseNameFixture.TEST_DB_NETWORK, - alias = DatabaseNameFixture.TEST_DB_ALIAS - ).also { - assertFalse(olderLegacyDbFile.exists()) - assertFalse(olderLegacyDbJournalFile.exists()) - assertFalse(olderLegacyDbWalFile.exists()) - - assertFalse(newerLegacyDbFile.exists()) - assertFalse(newerLegacyDbJournalFile.exists()) - assertFalse(newerLegacyDbWalFile.exists()) + assertFalse(newerLegacyDbFile.exists()) + assertFalse(newerLegacyDbJournalFile.exists()) + assertFalse(newerLegacyDbWalFile.exists()) + } } - } @Test @SmallTest - fun data_db_path() = runTest { - val coordinator = DatabaseCoordinator.getInstance(ApplicationProvider.getApplicationContext()) - val dataDbFile = coordinator.dataDbFile(ZcashNetwork.Testnet, "TestWallet") - assertTrue( - "Invalid DataDB file", - dataDbFile.absolutePath.endsWith( - "no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_DATA_NAME}" + fun data_db_path() = + runTest { + val coordinator = DatabaseCoordinator.getInstance(ApplicationProvider.getApplicationContext()) + val dataDbFile = coordinator.dataDbFile(ZcashNetwork.Testnet, "TestWallet") + assertTrue( + "Invalid DataDB file", + dataDbFile.absolutePath.endsWith( + "no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_DATA_NAME}" + ) ) - ) - } + } @Test @SmallTest - fun cache_path() = runTest { - val coordinator = DatabaseCoordinator.getInstance(ApplicationProvider.getApplicationContext()) - val cache = coordinator.fsBlockDbRoot(ZcashNetwork.Testnet, "TestWallet") - assertTrue( - "Invalid CacheDB file", - cache.absolutePath.endsWith( - "no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_FS_BLOCK_DB_ROOT_NAME}" + fun cache_path() = + runTest { + val coordinator = DatabaseCoordinator.getInstance(ApplicationProvider.getApplicationContext()) + val cache = coordinator.fsBlockDbRoot(ZcashNetwork.Testnet, "TestWallet") + assertTrue( + "Invalid CacheDB file", + cache.absolutePath.endsWith( + "no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_FS_BLOCK_DB_ROOT_NAME}" + ) ) - ) - } + } } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/db/NoBackupContextWrapperTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/db/NoBackupContextWrapperTest.kt index 429a5dc7..702676b3 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/db/NoBackupContextWrapperTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/db/NoBackupContextWrapperTest.kt @@ -10,7 +10,6 @@ import org.junit.Test import java.io.File class NoBackupContextWrapperTest { - private val databaseParentDir = File(DatabasePathFixture.new()) private val noBackupContextWrapper = NoBackupContextWrapper(getAppContext(), databaseParentDir) diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/FileExtTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/FileExtTest.kt index b7e599c2..36bc050e 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/FileExtTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/FileExtTest.kt @@ -15,7 +15,6 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue class FileExtTest { - private val testFile = File(getAppContext().filesDir, "test_file") @Before @@ -27,32 +26,34 @@ class FileExtTest { @Test @SmallTest @OptIn(ExperimentalCoroutinesApi::class) - fun check_empty_file_sha1_result() = runTest { - testFile.apply { - createNewFileSuspend() - assertTrue(existsSuspend()) - assertEquals( - expected = "da39a3ee5e6b4b0d3255bfef95601890afd80709", - actual = getSha1Hash(), - message = "SHA1 hashes are not the same." - ) + fun check_empty_file_sha1_result() = + runTest { + testFile.apply { + createNewFileSuspend() + assertTrue(existsSuspend()) + assertEquals( + expected = "da39a3ee5e6b4b0d3255bfef95601890afd80709", + actual = getSha1Hash(), + message = "SHA1 hashes are not the same." + ) + } } - } @Test @SmallTest @OptIn(ExperimentalCoroutinesApi::class) - fun check_not_empty_file_sha1_result() = runTest { - testFile.apply { - createNewFileSuspend() - assertTrue(existsSuspend()) - writeText("Hey! It compiles! Ship it!") - assertTrue(length() > 0) - assertEquals( - expected = "28756ec5d3a73f1e8993bdd46de74b79453ff21c", - actual = getSha1Hash(), - message = "SHA1 hashes are not the same." - ) + fun check_not_empty_file_sha1_result() = + runTest { + testFile.apply { + createNewFileSuspend() + assertTrue(existsSuspend()) + writeText("Hey! It compiles! Ship it!") + assertTrue(length() > 0) + assertEquals( + expected = "28756ec5d3a73f1e8993bdd46de74b79453ff21c", + actual = getSha1Hash(), + message = "SHA1 hashes are not the same." + ) + } } - } } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/TestExtensions.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/TestExtensions.kt index b3f2968a..d0216077 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/TestExtensions.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/TestExtensions.kt @@ -10,9 +10,10 @@ import kotlin.test.assertNotNull object BlockExplorer { suspend fun fetchLatestHeight(): Long { val url = URL("https://api.blockchair.com/zcash/blocks?limit=1") - val connection = withContext(Dispatchers.IO) { - url.openConnection() - } as HttpURLConnection + val connection = + withContext(Dispatchers.IO) { + url.openConnection() + } as HttpURLConnection val body = connection.inputStream.bufferedReader().readText() assertNotNull(body, "Body can not be null.") return JSONObject(body).getJSONArray("data").getJSONObject(0).getLong("id") @@ -20,30 +21,32 @@ object BlockExplorer { } object Transactions { - @Suppress("MaxLineLength") - val outbound = arrayOf( - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/t-shielded-spend.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/c9e35e6ff444b071d63bf9bab6480409d6361760445c8a28d24179adb35c2495.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/72a29d7db511025da969418880b749f7fc0fc910cdb06f52193b5fa5c0401d9d.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/ff6ea36765dc29793775c7aa71de19fca039c5b5b873a0497866e9c4bc48af01.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/34e507cab780546f980176f3ff2695cd404917508c7e5ee18cc1d2ff3858cb08.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/6edf869063eccff3345676b0fed9f1aa6988fb2524e3d9ca7420a13cfadcd76c.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/de97394ae220c28a33ba78b944e82dabec8cb404a4407650b134b3d5950358c0.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/4eaa902279f8380914baf5bcc470d8b7c11d84fda809f67f517a7cb48912b87b.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/73c5edf8ffba774d99155121ccf07e67fbcf14284458f7e732751fea60d3bcbc.txt" - ) + @Suppress("MaxLineLength", "ktlint:standard:max-line-length") + val outbound = + arrayOf( + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/t-shielded-spend.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/c9e35e6ff444b071d63bf9bab6480409d6361760445c8a28d24179adb35c2495.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/72a29d7db511025da969418880b749f7fc0fc910cdb06f52193b5fa5c0401d9d.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/ff6ea36765dc29793775c7aa71de19fca039c5b5b873a0497866e9c4bc48af01.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/34e507cab780546f980176f3ff2695cd404917508c7e5ee18cc1d2ff3858cb08.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/6edf869063eccff3345676b0fed9f1aa6988fb2524e3d9ca7420a13cfadcd76c.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/de97394ae220c28a33ba78b944e82dabec8cb404a4407650b134b3d5950358c0.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/4eaa902279f8380914baf5bcc470d8b7c11d84fda809f67f517a7cb48912b87b.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/73c5edf8ffba774d99155121ccf07e67fbcf14284458f7e732751fea60d3bcbc.txt" + ) - @Suppress("MaxLineLength") - val inbound = arrayOf( - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/8f064d23c66dc36e32445e5f3b50e0f32ac3ddb78cff21fb521eb6c19c07c99a.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/d2e7be14bbb308f9d4d68de424d622cbf774226d01cd63cc6f155fafd5cd212c.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e6566be3a4f9a80035dab8e1d97e40832a639e3ea938fb7972ea2f8482ff51ce.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e9527891b5d43d1ac72f2c0a3ac18a33dc5a0529aec04fa600616ed35f8123f8.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/4dcc95dd0a2f1f51bd64bb9f729b423c6de1690664a1b6614c75925e781662f7.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/75f2cdd2ff6a94535326abb5d9e663d53cbfa5f31ebb24b4d7e420e9440d41a2.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/7690c8ec740c1be3c50e2aedae8bf907ac81141ae8b6a134c1811706c73f49a6.txt", - "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/71935e29127a7de0b96081f4c8a42a9c11584d83adedfaab414362a6f3d965cf.txt" - ) + @Suppress("MaxLineLength", "ktlint:standard:max-line-length") + val inbound = + arrayOf( + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/8f064d23c66dc36e32445e5f3b50e0f32ac3ddb78cff21fb521eb6c19c07c99a.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/d2e7be14bbb308f9d4d68de424d622cbf774226d01cd63cc6f155fafd5cd212c.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e6566be3a4f9a80035dab8e1d97e40832a639e3ea938fb7972ea2f8482ff51ce.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e9527891b5d43d1ac72f2c0a3ac18a33dc5a0529aec04fa600616ed35f8123f8.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/4dcc95dd0a2f1f51bd64bb9f729b423c6de1690664a1b6614c75925e781662f7.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/75f2cdd2ff6a94535326abb5d9e663d53cbfa5f31ebb24b4d7e420e9440d41a2.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/7690c8ec740c1be3c50e2aedae8bf907ac81141ae8b6a134c1811706c73f49a6.txt", + "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/71935e29127a7de0b96081f4c8a42a9c11584d83adedfaab414362a6f3d965cf.txt" + ) } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt index f018d95d..f49e5ee7 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/fixture/WalletFixture.kt @@ -8,8 +8,9 @@ import cash.z.ecc.android.sdk.model.ZcashNetwork object WalletFixture { val NETWORK = ZcashNetwork.Mainnet - const val SEED_PHRASE = "kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month" + - " tree shock scan alpha just spot fluid toilet view dinner" + const val SEED_PHRASE = + "kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month" + + " tree shock scan alpha just spot fluid toilet view dinner" suspend fun getUnifiedSpendingKey( seed: String = SEED_PHRASE, diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt index 05aadfb1..76fe8b05 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt @@ -35,76 +35,82 @@ import org.junit.Test import java.util.concurrent.CountDownLatch class TestnetIntegrationTest : ScopedTest() { - var stopWatch = CountDownLatch(1) val saplingActivation = synchronizer.network.saplingActivationHeight @Test @Ignore("This test is broken") - fun testLatestBlockTest() = runTest { - val service = LightWalletClient.new( - context, - lightWalletEndpoint - ) - val height = service.getLatestBlockHeight() - assertTrue(height is Response.Success) - assertTrue((height as Response.Success).result.value > saplingActivation.value) - } + fun testLatestBlockTest() = + runTest { + val service = + LightWalletClient.new( + context, + lightWalletEndpoint + ) + val height = service.getLatestBlockHeight() + assertTrue(height is Response.Success) + assertTrue((height as Response.Success).result.value > saplingActivation.value) + } @Test fun testLoadBirthday() { - val (height) = runBlocking { - CheckpointTool.loadNearest( - context, - synchronizer.network, - saplingActivation + 1 - ) - } + val (height) = + runBlocking { + CheckpointTool.loadNearest( + context, + synchronizer.network, + saplingActivation + 1 + ) + } assertEquals(saplingActivation, height) } @Test @Ignore("This test is broken") - fun getAddress() = runBlocking { - assertEquals(address, synchronizer.getUnifiedAddress(Account.DEFAULT)) - } + fun getAddress() = + runBlocking { + assertEquals(address, synchronizer.getUnifiedAddress(Account.DEFAULT)) + } // This is an extremely slow test; it is disabled so that we can get CI set up @Test @LargeTest @Ignore("This test is extremely slow") - fun testBalance() = runBlocking { - var availableBalance: Zatoshi? = null - synchronizer.saplingBalances.onFirst { - availableBalance = it?.available - } + fun testBalance() = + runBlocking { + var availableBalance: Zatoshi? = null + synchronizer.saplingBalances.onFirst { + availableBalance = it?.available + } - synchronizer.status.filter { it == SYNCED }.onFirst { - delay(100) - } + synchronizer.status.filter { it == SYNCED }.onFirst { + delay(100) + } - assertTrue( - availableBalance!!.value > 0 - ) - } + assertTrue( + availableBalance!!.value > 0 + ) + } @Test @Ignore("This test is broken") - fun testSpend() = runBlocking { - var success = false - synchronizer.saplingBalances.filterNotNull().onEach { - success = sendFunds() - }.first() - log("asserting $success") - assertTrue(success) - } + fun testSpend() = + runBlocking { + var success = false + synchronizer.saplingBalances.filterNotNull().onEach { + success = sendFunds() + }.first() + log("asserting $success") + assertTrue(success) + } private suspend fun sendFunds(): Boolean { - val spendingKey = DerivationTool.getInstance().deriveUnifiedSpendingKey( - seed, - synchronizer.network, - Account.DEFAULT - ) + val spendingKey = + DerivationTool.getInstance().deriveUnifiedSpendingKey( + seed, + synchronizer.network, + Account.DEFAULT + ) log("sending to address") synchronizer.sendToAddress( spendingKey, @@ -119,13 +125,14 @@ class TestnetIntegrationTest : ScopedTest() { Twig.debug { "\n---\n[TESTLOG]: $message\n---\n" } } + @Suppress("UnusedPrivateProperty") companion object { - val lightWalletEndpoint = LightWalletEndpoint("lightwalletd.testnet.z.cash", 9087, true) - private const val birthdayHeight = 963150L - private const val targetHeight = 663250 - private const val 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" + private const val BIRTHDAY_HEIGHT = 963150L + private const val TARGET_HEIGHT = 663250 + private const val SEED_PHRASE = + "still champion voice habit trend flight survey between bitter process" + + " artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" val seed = "cash.z.ecc.android.sdk.integration.IntegrationTest.seed.value.64bytes".toByteArray() val address = "zs1m30y59wxut4zk9w24d6ujrdnfnl42hpy0ugvhgyhr8s0guszutqhdj05c7j472dndjstulph74m" val toAddress = "zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0" @@ -136,16 +143,17 @@ class TestnetIntegrationTest : ScopedTest() { @JvmStatic @BeforeClass fun startUp() { - synchronizer = Synchronizer.newBlocking( - context, - ZcashNetwork.Testnet, - lightWalletEndpoint = - lightWalletEndpoint, - seed = seed, - birthday = BlockHeight.new(ZcashNetwork.Testnet, birthdayHeight), - // Using existing wallet init mode as simplification for the test - walletInitMode = WalletInitMode.ExistingWallet - ) + synchronizer = + Synchronizer.newBlocking( + context, + ZcashNetwork.Testnet, + lightWalletEndpoint = + lightWalletEndpoint, + seed = seed, + birthday = BlockHeight.new(ZcashNetwork.Testnet, BIRTHDAY_HEIGHT), + // Using existing wallet init mode as simplification for the test + walletInitMode = WalletInitMode.ExistingWallet + ) } } } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/service/ChangeServiceTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/service/ChangeServiceTest.kt index 0f7409ea..c4af8bdb 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/service/ChangeServiceTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/service/ChangeServiceTest.kt @@ -30,7 +30,6 @@ import kotlin.test.Ignore @RunWith(AndroidJUnit4::class) @SmallTest class ChangeServiceTest : ScopedTest() { - val network = ZcashNetwork.Mainnet val lightWalletEndpoint = LightWalletEndpoint.Mainnet private val eccEndpoint = LightWalletEndpoint("lightwalletd.electriccoin.co", 9087, true) @@ -63,19 +62,21 @@ class ChangeServiceTest : ScopedTest() { @Test @OptIn(ExperimentalCoroutinesApi::class) @Ignore - fun testSanityCheck() = runTest { - // Test the result, only if there is no server communication problem. - runCatching { - service.getLatestBlockHeight() - }.onFailure { - Twig.debug(it) { "Failed to retrieve data" } - }.onSuccess { - assertTrue(it is Response.Success) + fun testSanityCheck() = + runTest { + // Test the result, only if there is no server communication problem. + runCatching { + service.getLatestBlockHeight() + }.onFailure { + Twig.debug(it) { "Failed to retrieve data" } + }.onSuccess { + assertTrue(it is Response.Success) - assertTrue( - (it as Response.Success).result.value > network.saplingActivationHeight - .value - ) + assertTrue( + (it as Response.Success).result.value > + network.saplingActivationHeight + .value + ) + } } - } } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/CheckpointTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/CheckpointTest.kt index a5fb00c1..b21d8235 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/CheckpointTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/CheckpointTest.kt @@ -39,13 +39,14 @@ class CheckpointTest { @Test @SmallTest fun parse_height_as_long_that_would_overflow_int() { - val jsonString = JSONObject().apply { - put(Checkpoint.KEY_VERSION, Checkpoint.VERSION_1) - put(Checkpoint.KEY_HEIGHT, UInt.MAX_VALUE.toLong()) - put(Checkpoint.KEY_HASH, CheckpointFixture.HASH) - put(Checkpoint.KEY_EPOCH_SECONDS, CheckpointFixture.EPOCH_SECONDS) - put(Checkpoint.KEY_TREE, CheckpointFixture.TREE) - }.toString() + val jsonString = + JSONObject().apply { + put(Checkpoint.KEY_VERSION, Checkpoint.VERSION_1) + put(Checkpoint.KEY_HEIGHT, UInt.MAX_VALUE.toLong()) + put(Checkpoint.KEY_HASH, CheckpointFixture.HASH) + put(Checkpoint.KEY_EPOCH_SECONDS, CheckpointFixture.EPOCH_SECONDS) + put(Checkpoint.KEY_TREE, CheckpointFixture.TREE) + }.toString() Checkpoint.from(CheckpointFixture.NETWORK, jsonString).also { assertEquals(UInt.MAX_VALUE.toLong(), it.height.value) diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolBasicTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolBasicTest.kt index 61e21c76..64d01d33 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolBasicTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolBasicTest.kt @@ -9,7 +9,6 @@ import cash.z.ecc.android.sdk.internal.ext.listFilesSuspend import cash.z.ecc.android.sdk.test.getAppContext import cash.z.ecc.fixture.SaplingParamToolFixture import cash.z.ecc.fixture.SaplingParamsFixture -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.Assert @@ -23,7 +22,6 @@ import kotlin.test.assertNotNull import kotlin.test.assertTrue class SaplingParamToolBasicTest { - @Before fun setup() { // clear the param files @@ -35,152 +33,175 @@ class SaplingParamToolBasicTest { @Test @SmallTest - @OptIn(ExperimentalCoroutinesApi::class) - fun init_sapling_param_tool_test() = runTest { - val spendSaplingParams = SaplingParamsFixture.new() - val outputSaplingParams = SaplingParamsFixture.new( - SaplingParamsFixture.DESTINATION_DIRECTORY, - SaplingParamsFixture.OUTPUT_FILE_NAME, - SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE, - SaplingParamsFixture.OUTPUT_FILE_HASH - ) + fun init_sapling_param_tool_test() = + runTest { + val spendSaplingParams = SaplingParamsFixture.new() + val outputSaplingParams = + SaplingParamsFixture.new( + SaplingParamsFixture.DESTINATION_DIRECTORY, + SaplingParamsFixture.OUTPUT_FILE_NAME, + SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE, + SaplingParamsFixture.OUTPUT_FILE_HASH + ) - val saplingParamTool = SaplingParamTool( - SaplingParamToolProperties( - emptyList(), - SaplingParamsFixture - .DESTINATION_DIRECTORY, - SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY + val saplingParamTool = + SaplingParamTool( + SaplingParamToolProperties( + emptyList(), + SaplingParamsFixture + .DESTINATION_DIRECTORY, + SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY + ) + ) + + // We inject params files to let the ensureParams() finish successfully without executing its extended + // operation + // like fetchParams, etc. + SaplingParamsFixture.createFile(File(spendSaplingParams.destinationDirectory, spendSaplingParams.fileName)) + SaplingParamsFixture.createFile( + File( + outputSaplingParams.destinationDirectory, + outputSaplingParams.fileName + ) ) - ) - // we inject params files to let the ensureParams() finish successfully without executing its extended operation - // like fetchParams, etc. - SaplingParamsFixture.createFile(File(spendSaplingParams.destinationDirectory, spendSaplingParams.fileName)) - SaplingParamsFixture.createFile(File(outputSaplingParams.destinationDirectory, outputSaplingParams.fileName)) - - saplingParamTool.ensureParams(spendSaplingParams.destinationDirectory) - } + saplingParamTool.ensureParams(spendSaplingParams.destinationDirectory) + } @Test @SmallTest - @OptIn(ExperimentalCoroutinesApi::class) - fun init_and_get_params_destination_dir_test() = runTest { - val destDir = SaplingParamTool.new(getAppContext()).properties.paramsDirectory + fun init_and_get_params_destination_dir_test() = + runTest { + val destDir = SaplingParamTool.new(getAppContext()).properties.paramsDirectory - assertNotNull(destDir) - assertEquals( - SaplingParamsFixture.DESTINATION_DIRECTORY.absolutePath, - destDir.absolutePath, - "Failed to validate init operation's destination directory." - ) - } + assertNotNull(destDir) + assertEquals( + SaplingParamsFixture.DESTINATION_DIRECTORY.absolutePath, + destDir.absolutePath, + "Failed to validate init operation's destination directory." + ) + } @Test @MediumTest - @OptIn(ExperimentalCoroutinesApi::class) - fun move_files_from_legacy_destination_test() = runTest { - SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY.mkdirs() - val spendFile = File(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, SaplingParamsFixture.SPEND_FILE_NAME) - val outputFile = File(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, SaplingParamsFixture.OUTPUT_FILE_NAME) + fun move_files_from_legacy_destination_test() = + runTest { + SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY.mkdirs() + val spendFile = + File( + SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, + SaplingParamsFixture.SPEND_FILE_NAME + ) + val outputFile = + File( + SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, + SaplingParamsFixture.OUTPUT_FILE_NAME + ) - // now we inject params files to the legacy location to be "moved" to the preferred location - SaplingParamsFixture.createFile(spendFile) - SaplingParamsFixture.createFile(outputFile) + // Now we inject params files to the legacy location to be "moved" to the preferred location + SaplingParamsFixture.createFile(spendFile) + SaplingParamsFixture.createFile(outputFile) - assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, spendFile)) - assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, outputFile)) - assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, spendFile)) - assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, outputFile)) + assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, spendFile)) + assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, outputFile)) + assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, spendFile)) + assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, outputFile)) - // we need to use modified array of sapling parameters to pass through the SHA1 hashes validation - val destDir = SaplingParamTool.initAndGetParamsDestinationDir( - SaplingParamToolFixture.new( - saplingParamsFiles = listOf( - SaplingParameters( - SaplingParamToolFixture.PARAMS_DIRECTORY, - SaplingParamTool.SPEND_PARAM_FILE_NAME, - SaplingParamTool.SPEND_PARAM_FILE_MAX_BYTES_SIZE, - spendFile.getSha1Hash() - ), - SaplingParameters( - SaplingParamToolFixture.PARAMS_DIRECTORY, - SaplingParamTool.OUTPUT_PARAM_FILE_NAME, - SaplingParamTool.OUTPUT_PARAM_FILE_MAX_BYTES_SIZE, - outputFile.getSha1Hash() + // We need to use modified array of sapling parameters to pass through the SHA1 hashes validation + val destDir = + SaplingParamTool.initAndGetParamsDestinationDir( + SaplingParamToolFixture.new( + saplingParamsFiles = + listOf( + SaplingParameters( + SaplingParamToolFixture.PARAMS_DIRECTORY, + SaplingParamTool.SPEND_PARAM_FILE_NAME, + SaplingParamTool.SPEND_PARAM_FILE_MAX_BYTES_SIZE, + spendFile.getSha1Hash() + ), + SaplingParameters( + SaplingParamToolFixture.PARAMS_DIRECTORY, + SaplingParamTool.OUTPUT_PARAM_FILE_NAME, + SaplingParamTool.OUTPUT_PARAM_FILE_MAX_BYTES_SIZE, + outputFile.getSha1Hash() + ) + ) ) ) + + assertEquals( + SaplingParamsFixture.DESTINATION_DIRECTORY.absolutePath, + destDir.absolutePath ) - ) - assertEquals( - SaplingParamsFixture.DESTINATION_DIRECTORY.absolutePath, - destDir.absolutePath - ) + assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, spendFile)) + assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, outputFile)) + assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, spendFile)) + assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, outputFile)) + } - assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, spendFile)) - assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, outputFile)) - assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, spendFile)) - assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, outputFile)) - } - - private suspend fun isFileInPlace(directory: File, file: File): Boolean { + private suspend fun isFileInPlace( + directory: File, + file: File + ): Boolean { return directory.listFilesSuspend()?.any { it.name == file.name } ?: false } @Test @MediumTest - @OptIn(ExperimentalCoroutinesApi::class) - fun ensure_params_exception_thrown_test() = runTest { - val saplingParamTool = SaplingParamTool( - SaplingParamToolFixture.new( - saplingParamsFiles = listOf( - SaplingParameters( - SaplingParamToolFixture.PARAMS_DIRECTORY, - "test_file_1", - SaplingParamTool.SPEND_PARAM_FILE_MAX_BYTES_SIZE, - SaplingParamTool.SPEND_PARAM_FILE_SHA1_HASH - ), - SaplingParameters( - SaplingParamToolFixture.PARAMS_DIRECTORY, - "test_file_0", - SaplingParamTool.OUTPUT_PARAM_FILE_MAX_BYTES_SIZE, - SaplingParamTool.OUTPUT_PARAM_FILE_SHA1_HASH + fun ensure_params_exception_thrown_test() = + runTest { + val saplingParamTool = + SaplingParamTool( + SaplingParamToolFixture.new( + saplingParamsFiles = + listOf( + SaplingParameters( + SaplingParamToolFixture.PARAMS_DIRECTORY, + "test_file_1", + SaplingParamTool.SPEND_PARAM_FILE_MAX_BYTES_SIZE, + SaplingParamTool.SPEND_PARAM_FILE_SHA1_HASH + ), + SaplingParameters( + SaplingParamToolFixture.PARAMS_DIRECTORY, + "test_file_0", + SaplingParamTool.OUTPUT_PARAM_FILE_MAX_BYTES_SIZE, + SaplingParamTool.OUTPUT_PARAM_FILE_SHA1_HASH + ) + ) ) ) - ) - ) - // now we inject params files to the preferred location to pass through the check missing files phase - SaplingParamsFixture.createFile( - File( - saplingParamTool.properties.saplingParams[0].destinationDirectory, - saplingParamTool.properties.saplingParams[0].fileName + // Now we inject params files to the preferred location to pass through the check missing files phase + SaplingParamsFixture.createFile( + File( + saplingParamTool.properties.saplingParams[0].destinationDirectory, + saplingParamTool.properties.saplingParams[0].fileName + ) ) - ) - SaplingParamsFixture.createFile( - File( - saplingParamTool.properties.saplingParams[1].destinationDirectory, - saplingParamTool.properties.saplingParams[1].fileName + SaplingParamsFixture.createFile( + File( + saplingParamTool.properties.saplingParams[1].destinationDirectory, + saplingParamTool.properties.saplingParams[1].fileName + ) ) - ) - // the ensure params block should fail in validation phase, because we use a different params file names - assertFailsWith { - saplingParamTool.ensureParams(SaplingParamToolFixture.PARAMS_DIRECTORY) + // The ensure params block should fail in validation phase, because we use a different params file names + assertFailsWith { + saplingParamTool.ensureParams(SaplingParamToolFixture.PARAMS_DIRECTORY) + } } - } - @OptIn(ExperimentalCoroutinesApi::class) @Test @SmallTest - fun sapling_params_path() = runTest { - val paramsDir = SaplingParamTool.new(ApplicationProvider.getApplicationContext()).properties.paramsDirectory - Assert.assertTrue( - "Invalid CacheDB params dir", - paramsDir.endsWith( - "no_backup/co.electricoin.zcash" + fun sapling_params_path() = + runTest { + val paramsDir = SaplingParamTool.new(ApplicationProvider.getApplicationContext()).properties.paramsDirectory + Assert.assertTrue( + "Invalid CacheDB params dir", + paramsDir.endsWith( + "no_backup/co.electricoin.zcash" + ) ) - ) - } + } } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolIntegrationTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolIntegrationTest.kt index 03e56931..88ffef60 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolIntegrationTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolIntegrationTest.kt @@ -29,15 +29,15 @@ import kotlin.test.assertTrue ) @RunWith(AndroidJUnit4::class) class SaplingParamToolIntegrationTest { - private val spendSaplingParams = SaplingParamsFixture.new() - private val outputSaplingParams = SaplingParamsFixture.new( - SaplingParamsFixture.DESTINATION_DIRECTORY, - SaplingParamsFixture.OUTPUT_FILE_NAME, - SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE, - SaplingParamsFixture.OUTPUT_FILE_HASH - ) + private val outputSaplingParams = + SaplingParamsFixture.new( + SaplingParamsFixture.DESTINATION_DIRECTORY, + SaplingParamsFixture.OUTPUT_FILE_NAME, + SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE, + SaplingParamsFixture.OUTPUT_FILE_HASH + ) @Before fun setup() { @@ -50,218 +50,242 @@ class SaplingParamToolIntegrationTest { @Test @LargeTest - fun test_files_exists() = runBlocking { - val saplingParamTool = SaplingParamTool.new(getAppContext()) + fun test_files_exists() = + runBlocking { + val saplingParamTool = SaplingParamTool.new(getAppContext()) - saplingParamTool.fetchParams(spendSaplingParams) - saplingParamTool.fetchParams(outputSaplingParams) - - val result = saplingParamTool.validate( - SaplingParamsFixture.DESTINATION_DIRECTORY - ) - - assertTrue(result) - } - - @Test - @LargeTest - fun output_file_exists() = runBlocking { - val saplingParamTool = SaplingParamTool.new(getAppContext()) - - saplingParamTool.fetchParams(spendSaplingParams) - File(spendSaplingParams.destinationDirectory, spendSaplingParams.fileName).delete() - - val result = saplingParamTool.validate(spendSaplingParams.destinationDirectory) - - assertFalse(result, "Validation should fail as the spend param file is missing.") - } - - @Test - @LargeTest - fun spend_file_exists() = runBlocking { - val saplingParamTool = SaplingParamTool.new(getAppContext()) - - saplingParamTool.fetchParams(outputSaplingParams) - File(outputSaplingParams.destinationDirectory, outputSaplingParams.fileName).delete() - - val result = saplingParamTool.validate(outputSaplingParams.destinationDirectory) - - assertFalse(result, "Validation should fail as the output param file is missing.") - } - - @Test - @LargeTest - fun check_all_files_fetched() = runBlocking { - val expectedSpendFile = File( - SaplingParamsFixture.DESTINATION_DIRECTORY, - SaplingParamsFixture.SPEND_FILE_NAME - ) - val expectedOutputFile = File( - SaplingParamsFixture.DESTINATION_DIRECTORY, - SaplingParamsFixture.OUTPUT_FILE_NAME - ) - - val saplingParamTool = SaplingParamTool.new(getAppContext()) - - saplingParamTool.ensureParams(SaplingParamsFixture.DESTINATION_DIRECTORY) - - val actualFiles = SaplingParamsFixture.DESTINATION_DIRECTORY.listFilesSuspend() - assertNotNull(actualFiles) - - assertContains(actualFiles, expectedSpendFile) - - assertContains(actualFiles, expectedOutputFile) - } - - @Test - @LargeTest - fun check_correct_spend_param_file_size() = runBlocking { - val saplingParamTool = SaplingParamTool.new(getAppContext()) - - saplingParamTool.fetchParams(spendSaplingParams) - - val expectedSpendFile = File( - SaplingParamsFixture.DESTINATION_DIRECTORY, - SaplingParamsFixture.SPEND_FILE_NAME - ) - - assertTrue(expectedSpendFile.length() < SaplingParamsFixture.SPEND_FILE_MAX_SIZE) - assertFalse(expectedSpendFile.length() < SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE) - } - - @Test - @LargeTest - fun check_correct_output_param_file_size() = runBlocking { - val saplingParamTool = SaplingParamTool.new(getAppContext()) - - saplingParamTool.fetchParams(outputSaplingParams) - - val expectedOutputFile = File( - SaplingParamsFixture.DESTINATION_DIRECTORY, - SaplingParamsFixture.OUTPUT_FILE_NAME - ) - - assertTrue(expectedOutputFile.length() < SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE) - assertFalse(expectedOutputFile.length() > SaplingParamsFixture.SPEND_FILE_MAX_SIZE) - } - - @Test - @LargeTest - @OptIn(ExperimentalCoroutinesApi::class) - fun fetch_params_uninitialized_test() = runTest { - val saplingParamTool = SaplingParamTool.new(getAppContext()) - - SaplingParamsFixture.DESTINATION_DIRECTORY.delete() - - assertFailsWith { saplingParamTool.fetchParams(spendSaplingParams) - } - - assertFalse(saplingParamTool.validate(SaplingParamsFixture.DESTINATION_DIRECTORY)) - } - - @Test - @LargeTest - @OptIn(ExperimentalCoroutinesApi::class) - fun fetch_params_incorrect_hash_test() = runTest { - val saplingParamTool = SaplingParamTool.new(getAppContext()) - - assertFailsWith { - saplingParamTool.fetchParams( - SaplingParamsFixture.new( - fileName = SaplingParamsFixture.OUTPUT_FILE_NAME, - fileMaxSize = SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE, - fileHash = "test_hash_which_causes_failure_of_validation" - ) - ) - } - - assertFalse(saplingParamTool.validate(SaplingParamsFixture.DESTINATION_DIRECTORY)) - } - - @Test - @LargeTest - @OptIn(ExperimentalCoroutinesApi::class) - fun fetch_params_incorrect_max_file_size_test() = runTest { - val saplingParamTool = SaplingParamTool.new(getAppContext()) - - assertFailsWith { - saplingParamTool.fetchParams( - SaplingParamsFixture.new( - fileName = SaplingParamsFixture.OUTPUT_FILE_NAME, - fileHash = SaplingParamsFixture.OUTPUT_FILE_HASH, - fileMaxSize = 0 - ) - ) - } - - assertFalse(saplingParamTool.validate(SaplingParamsFixture.DESTINATION_DIRECTORY)) - } - - @Test - @LargeTest - @OptIn(ExperimentalCoroutinesApi::class) - fun fetch_param_manual_recover_test_from_fetch_params_exception() = runTest { - val saplingParamTool = SaplingParamTool.new(getAppContext()) - - SaplingParamsFixture.DESTINATION_DIRECTORY.delete() // will cause the FetchParamsException - - val exception = assertFailsWith { saplingParamTool.fetchParams(outputSaplingParams) + + val result = + saplingParamTool.validate( + SaplingParamsFixture.DESTINATION_DIRECTORY + ) + + assertTrue(result) } - assertEquals(outputSaplingParams.fileName, exception.parameters.fileName) + @Test + @LargeTest + fun output_file_exists() = + runBlocking { + val saplingParamTool = SaplingParamTool.new(getAppContext()) - val expectedOutputFile = File( - SaplingParamsFixture.DESTINATION_DIRECTORY, - SaplingParamsFixture.OUTPUT_FILE_NAME - ) + saplingParamTool.fetchParams(spendSaplingParams) + File(spendSaplingParams.destinationDirectory, spendSaplingParams.fileName).delete() - assertFalse(expectedOutputFile.exists()) + val result = saplingParamTool.validate(spendSaplingParams.destinationDirectory) - // to set up the missing deleted folder - SaplingParamTool.initAndGetParamsDestinationDir(saplingParamTool.properties) + assertFalse(result, "Validation should fail as the spend param file is missing.") + } - // re-try with parameters returned by the exception - saplingParamTool.fetchParams(exception.parameters) + @Test + @LargeTest + fun spend_file_exists() = + runBlocking { + val saplingParamTool = SaplingParamTool.new(getAppContext()) - assertTrue(expectedOutputFile.exists()) - } + saplingParamTool.fetchParams(outputSaplingParams) + File(outputSaplingParams.destinationDirectory, outputSaplingParams.fileName).delete() + + val result = saplingParamTool.validate(outputSaplingParams.destinationDirectory) + + assertFalse(result, "Validation should fail as the output param file is missing.") + } + + @Test + @LargeTest + fun check_all_files_fetched() = + runBlocking { + val expectedSpendFile = + File( + SaplingParamsFixture.DESTINATION_DIRECTORY, + SaplingParamsFixture.SPEND_FILE_NAME + ) + val expectedOutputFile = + File( + SaplingParamsFixture.DESTINATION_DIRECTORY, + SaplingParamsFixture.OUTPUT_FILE_NAME + ) + + val saplingParamTool = SaplingParamTool.new(getAppContext()) + + saplingParamTool.ensureParams(SaplingParamsFixture.DESTINATION_DIRECTORY) + + val actualFiles = SaplingParamsFixture.DESTINATION_DIRECTORY.listFilesSuspend() + assertNotNull(actualFiles) + + assertContains(actualFiles, expectedSpendFile) + + assertContains(actualFiles, expectedOutputFile) + } + + @Test + @LargeTest + fun check_correct_spend_param_file_size() = + runBlocking { + val saplingParamTool = SaplingParamTool.new(getAppContext()) + + saplingParamTool.fetchParams(spendSaplingParams) + + val expectedSpendFile = + File( + SaplingParamsFixture.DESTINATION_DIRECTORY, + SaplingParamsFixture.SPEND_FILE_NAME + ) + + assertTrue(expectedSpendFile.length() < SaplingParamsFixture.SPEND_FILE_MAX_SIZE) + assertFalse(expectedSpendFile.length() < SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE) + } + + @Test + @LargeTest + fun check_correct_output_param_file_size() = + runBlocking { + val saplingParamTool = SaplingParamTool.new(getAppContext()) + + saplingParamTool.fetchParams(outputSaplingParams) + + val expectedOutputFile = + File( + SaplingParamsFixture.DESTINATION_DIRECTORY, + SaplingParamsFixture.OUTPUT_FILE_NAME + ) + + assertTrue(expectedOutputFile.length() < SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE) + assertFalse(expectedOutputFile.length() > SaplingParamsFixture.SPEND_FILE_MAX_SIZE) + } @Test @LargeTest @OptIn(ExperimentalCoroutinesApi::class) - fun fetch_param_manual_recover_test_from_validate_params_exception() = runTest { - val saplingParamTool = SaplingParamTool.new(getAppContext()) + fun fetch_params_uninitialized_test() = + runTest { + val saplingParamTool = SaplingParamTool.new(getAppContext()) - val expectedOutputFile = File( - SaplingParamsFixture.DESTINATION_DIRECTORY, - SaplingParamsFixture.OUTPUT_FILE_NAME - ) + SaplingParamsFixture.DESTINATION_DIRECTORY.delete() - val outputSaplingParams = SaplingParamsFixture.new( - SaplingParamsFixture.DESTINATION_DIRECTORY, - SaplingParamsFixture.OUTPUT_FILE_NAME, - SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE, - SaplingParamsFixture.SPEND_FILE_HASH // will cause the ValidateParamsException - ) + assertFailsWith { + saplingParamTool.fetchParams(spendSaplingParams) + } - val exception = assertFailsWith { - saplingParamTool.fetchParams(outputSaplingParams) + assertFalse(saplingParamTool.validate(SaplingParamsFixture.DESTINATION_DIRECTORY)) } - assertFalse(expectedOutputFile.exists()) + @Test + @LargeTest + @OptIn(ExperimentalCoroutinesApi::class) + fun fetch_params_incorrect_hash_test() = + runTest { + val saplingParamTool = SaplingParamTool.new(getAppContext()) - val fixedOutputSaplingParams = SaplingParamsFixture.new( - destinationDirectoryPath = exception.parameters.destinationDirectory, - fileName = exception.parameters.fileName, - fileMaxSize = exception.parameters.fileMaxSizeBytes, - fileHash = SaplingParamsFixture.OUTPUT_FILE_HASH // fixed file hash - ) + assertFailsWith { + saplingParamTool.fetchParams( + SaplingParamsFixture.new( + fileName = SaplingParamsFixture.OUTPUT_FILE_NAME, + fileMaxSize = SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE, + fileHash = "test_hash_which_causes_failure_of_validation" + ) + ) + } - // re-try with fixed parameters - saplingParamTool.fetchParams(fixedOutputSaplingParams) + assertFalse(saplingParamTool.validate(SaplingParamsFixture.DESTINATION_DIRECTORY)) + } - assertTrue(expectedOutputFile.exists()) - } + @Test + @LargeTest + @OptIn(ExperimentalCoroutinesApi::class) + fun fetch_params_incorrect_max_file_size_test() = + runTest { + val saplingParamTool = SaplingParamTool.new(getAppContext()) + + assertFailsWith { + saplingParamTool.fetchParams( + SaplingParamsFixture.new( + fileName = SaplingParamsFixture.OUTPUT_FILE_NAME, + fileHash = SaplingParamsFixture.OUTPUT_FILE_HASH, + fileMaxSize = 0 + ) + ) + } + + assertFalse(saplingParamTool.validate(SaplingParamsFixture.DESTINATION_DIRECTORY)) + } + + @Test + @LargeTest + @OptIn(ExperimentalCoroutinesApi::class) + fun fetch_param_manual_recover_test_from_fetch_params_exception() = + runTest { + val saplingParamTool = SaplingParamTool.new(getAppContext()) + + SaplingParamsFixture.DESTINATION_DIRECTORY.delete() // will cause the FetchParamsException + + val exception = + assertFailsWith { + saplingParamTool.fetchParams(outputSaplingParams) + } + + assertEquals(outputSaplingParams.fileName, exception.parameters.fileName) + + val expectedOutputFile = + File( + SaplingParamsFixture.DESTINATION_DIRECTORY, + SaplingParamsFixture.OUTPUT_FILE_NAME + ) + + assertFalse(expectedOutputFile.exists()) + + // to set up the missing deleted folder + SaplingParamTool.initAndGetParamsDestinationDir(saplingParamTool.properties) + + // re-try with parameters returned by the exception + saplingParamTool.fetchParams(exception.parameters) + + assertTrue(expectedOutputFile.exists()) + } + + @Test + @LargeTest + @OptIn(ExperimentalCoroutinesApi::class) + fun fetch_param_manual_recover_test_from_validate_params_exception() = + runTest { + val saplingParamTool = SaplingParamTool.new(getAppContext()) + + val expectedOutputFile = + File( + SaplingParamsFixture.DESTINATION_DIRECTORY, + SaplingParamsFixture.OUTPUT_FILE_NAME + ) + + val outputSaplingParams = + SaplingParamsFixture.new( + SaplingParamsFixture.DESTINATION_DIRECTORY, + SaplingParamsFixture.OUTPUT_FILE_NAME, + SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE, + // Will cause the ValidateParamsException + SaplingParamsFixture.SPEND_FILE_HASH + ) + + val exception = + assertFailsWith { + saplingParamTool.fetchParams(outputSaplingParams) + } + + assertFalse(expectedOutputFile.exists()) + + val fixedOutputSaplingParams = + SaplingParamsFixture.new( + destinationDirectoryPath = exception.parameters.destinationDirectory, + fileName = exception.parameters.fileName, + fileMaxSize = exception.parameters.fileMaxSizeBytes, + // Fixed file hash + fileHash = SaplingParamsFixture.OUTPUT_FILE_HASH + ) + + // re-try with fixed parameters + saplingParamTool.fetchParams(fixedOutputSaplingParams) + + assertTrue(expectedOutputFile.exists()) + } } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SdkSynchronizerTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SdkSynchronizerTest.kt index d41fcb67..db1e863c 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SdkSynchronizerTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SdkSynchronizerTest.kt @@ -15,71 +15,72 @@ import kotlin.test.Test import kotlin.test.assertFailsWith class SdkSynchronizerTest { - @Test @SmallTest @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) - fun cannot_instantiate_in_parallel() = runTest { - // Random alias so that repeated invocations of this test will have a clean starting state - val alias = UUID.randomUUID().toString() + fun cannot_instantiate_in_parallel() = + runTest { + // Random alias so that repeated invocations of this test will have a clean starting state + val alias = UUID.randomUUID().toString() - // In the future, inject fake networking component so that it doesn't require hitting the network - Synchronizer.new( - InstrumentationRegistry.getInstrumentation().context, - ZcashNetwork.Mainnet, - alias, - LightWalletEndpoint.defaultForNetwork(ZcashNetwork.Mainnet), - Mnemonics.MnemonicCode(WalletFixture.SEED_PHRASE).toEntropy(), - birthday = null, - // Using existing wallet init mode as simplification for the test - walletInitMode = WalletInitMode.ExistingWallet - ).use { - assertFailsWith { - Synchronizer.new( - InstrumentationRegistry.getInstrumentation().context, - ZcashNetwork.Mainnet, - alias, - LightWalletEndpoint.defaultForNetwork(ZcashNetwork.Mainnet), - Mnemonics.MnemonicCode(WalletFixture.SEED_PHRASE).toEntropy(), - birthday = null, - // Using existing wallet init mode as simplification for the test - walletInitMode = WalletInitMode.ExistingWallet - ) + // In the future, inject fake networking component so that it doesn't require hitting the network + Synchronizer.new( + InstrumentationRegistry.getInstrumentation().context, + ZcashNetwork.Mainnet, + alias, + LightWalletEndpoint.defaultForNetwork(ZcashNetwork.Mainnet), + Mnemonics.MnemonicCode(WalletFixture.SEED_PHRASE).toEntropy(), + birthday = null, + // Using existing wallet init mode as simplification for the test + walletInitMode = WalletInitMode.ExistingWallet + ).use { + assertFailsWith { + Synchronizer.new( + InstrumentationRegistry.getInstrumentation().context, + ZcashNetwork.Mainnet, + alias, + LightWalletEndpoint.defaultForNetwork(ZcashNetwork.Mainnet), + Mnemonics.MnemonicCode(WalletFixture.SEED_PHRASE).toEntropy(), + birthday = null, + // Using existing wallet init mode as simplification for the test + walletInitMode = WalletInitMode.ExistingWallet + ) + } } } - } @Test @SmallTest @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) - fun can_instantiate_in_serial() = runTest { - // Random alias so that repeated invocations of this test will have a clean starting state - val alias = UUID.randomUUID().toString() + fun can_instantiate_in_serial() = + runTest { + // Random alias so that repeated invocations of this test will have a clean starting state + val alias = UUID.randomUUID().toString() - // TODO [#1094]: Consider fake SDK sync related components - // TODO [#1094]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1094 - // In the future, inject fake networking component so that it doesn't require hitting the network - Synchronizer.new( - InstrumentationRegistry.getInstrumentation().context, - ZcashNetwork.Mainnet, - alias, - LightWalletEndpoint.defaultForNetwork(ZcashNetwork.Mainnet), - Mnemonics.MnemonicCode(WalletFixture.SEED_PHRASE).toEntropy(), - birthday = null, - // Using existing wallet init mode as simplification for the test - walletInitMode = WalletInitMode.ExistingWallet - ).use {} + // TODO [#1094]: Consider fake SDK sync related components + // TODO [#1094]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1094 + // In the future, inject fake networking component so that it doesn't require hitting the network + Synchronizer.new( + InstrumentationRegistry.getInstrumentation().context, + ZcashNetwork.Mainnet, + alias, + LightWalletEndpoint.defaultForNetwork(ZcashNetwork.Mainnet), + Mnemonics.MnemonicCode(WalletFixture.SEED_PHRASE).toEntropy(), + birthday = null, + // Using existing wallet init mode as simplification for the test + walletInitMode = WalletInitMode.ExistingWallet + ).use {} - // Second instance should succeed because first one was closed - Synchronizer.new( - InstrumentationRegistry.getInstrumentation().context, - ZcashNetwork.Mainnet, - alias, - LightWalletEndpoint.defaultForNetwork(ZcashNetwork.Mainnet), - Mnemonics.MnemonicCode(WalletFixture.SEED_PHRASE).toEntropy(), - birthday = null, - // Using existing wallet init mode as simplification for the test - walletInitMode = WalletInitMode.ExistingWallet - ).use {} - } + // Second instance should succeed because first one was closed + Synchronizer.new( + InstrumentationRegistry.getInstrumentation().context, + ZcashNetwork.Mainnet, + alias, + LightWalletEndpoint.defaultForNetwork(ZcashNetwork.Mainnet), + Mnemonics.MnemonicCode(WalletFixture.SEED_PHRASE).toEntropy(), + birthday = null, + // Using existing wallet init mode as simplification for the test + walletInitMode = WalletInitMode.ExistingWallet + ).use {} + } } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/storage/block/FileCompactBlockRepositoryTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/storage/block/FileCompactBlockRepositoryTest.kt index 3b597419..5242f46d 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/storage/block/FileCompactBlockRepositoryTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/storage/block/FileCompactBlockRepositoryTest.kt @@ -28,246 +28,272 @@ import kotlin.test.assertNull import kotlin.test.assertTrue class FileCompactBlockRepositoryTest { - @Before - fun setup() = runTest { - val blocksDirectory = FilePathFixture.newBlocksDir() - if (blocksDirectory.existsSuspend()) { - blocksDirectory.deleteRecursivelySuspend() + fun setup() = + runTest { + val blocksDirectory = FilePathFixture.newBlocksDir() + if (blocksDirectory.existsSuspend()) { + blocksDirectory.deleteRecursivelySuspend() + } + + blocksDirectory.mkdirsSuspend() } - blocksDirectory.mkdirsSuspend() - } - @After - fun tearDown() = runTest { - FilePathFixture.newBlocksDir().deleteRecursivelySuspend() - } + fun tearDown() = + runTest { + FilePathFixture.newBlocksDir().deleteRecursivelySuspend() + } private fun getMockedFileCompactBlockRepository( backend: TypesafeBackend, rootBlocksDirectory: File - ): FileCompactBlockRepository = runBlocking { - FileCompactBlockRepository( - rootBlocksDirectory, - backend - ) - } - - @Test - fun getLatestHeightTest() = runTest { - val rustBackend = FakeRustBackendFixture().new() - val blockRepository = getMockedFileCompactBlockRepository( - TypesafeBackendImpl(rustBackend), - FilePathFixture.newBlocksDir() - ) - - val blocks = ListOfCompactBlocksFixture.newFlow() - - blockRepository.write(blocks) - - assertEquals(blocks.last().height, blockRepository.getLatestHeight()?.value) - } - - @Test - fun findCompactBlockTest() = runTest { - val network = ZcashNetwork.Testnet - val rustBackend = FakeRustBackendFixture().new() - val blockRepository = getMockedFileCompactBlockRepository( - TypesafeBackendImpl(rustBackend), - FilePathFixture.newBlocksDir() - ) - - val blocks = ListOfCompactBlocksFixture.newFlow() - - blockRepository.write(blocks) - - val firstPersistedBlock = blockRepository.findCompactBlock( - BlockHeight.new(network, blocks.first().height) - ) - val lastPersistedBlock = blockRepository.findCompactBlock( - BlockHeight.new(network, blocks.last().height) - ) - val notPersistedBlockHeight = BlockHeight.new( - network, - blockHeight = blocks.last().height + 1 - ) - - assertNotNull(firstPersistedBlock) - assertNotNull(lastPersistedBlock) - - assertEquals(blocks.first().height, firstPersistedBlock.height) - assertEquals(blocks.last().height, blockRepository.getLatestHeight()?.value) - assertNull(blockRepository.findCompactBlock(notPersistedBlockHeight)) - } - - @Test - fun writeBlocksTest() = runTest { - val rustBackend = FakeRustBackendFixture().new() - val blockRepository = getMockedFileCompactBlockRepository( - TypesafeBackendImpl(rustBackend), - FilePathFixture.newBlocksDir() - ) - - assertTrue { rustBackend.metadata.isEmpty() } - - val blocks = ListOfCompactBlocksFixture.newFlow() - val persistedBlocks = blockRepository.write(blocks) - - assertEquals(blocks.count(), persistedBlocks.size) - assertEquals(blocks.count(), rustBackend.metadata.size) - } - - @Test - fun writeFewBlocksTest() = runTest { - val rustBackend = FakeRustBackendFixture().new() - val blockRepository = getMockedFileCompactBlockRepository( - TypesafeBackendImpl(rustBackend), - FilePathFixture.newBlocksDir() - ) - - assertTrue { rustBackend.metadata.isEmpty() } - - // prepare a list of blocks to be persisted, which has smaller size than buffer size - val reducedBlocksList = ListOfCompactBlocksFixture.newFlow().apply { - val reduced = drop(count() / 2) - assertTrue { reduced.count() < FileCompactBlockRepository.BLOCKS_METADATA_BUFFER_SIZE } + ): FileCompactBlockRepository = + runBlocking { + FileCompactBlockRepository( + rootBlocksDirectory, + backend + ) } - val persistedBlocks = blockRepository.write(reducedBlocksList) - - assertEquals(reducedBlocksList.count(), persistedBlocks.size) - assertEquals(reducedBlocksList.count(), rustBackend.metadata.size) - } - @Test - fun writeBlocksAndCheckStorageTest() = runTest { - val rustBackend = FakeRustBackendFixture().new() - val rootBlocksDirectory = FilePathFixture.newBlocksDir() - val blockRepository = getMockedFileCompactBlockRepository( - TypesafeBackendImpl(rustBackend), - FilePathFixture.newBlocksDir() - ) + fun getLatestHeightTest() = + runTest { + val rustBackend = FakeRustBackendFixture().new() + val blockRepository = + getMockedFileCompactBlockRepository( + TypesafeBackendImpl(rustBackend), + FilePathFixture.newBlocksDir() + ) - assertTrue { rootBlocksDirectory.exists() } - assertTrue { rootBlocksDirectory.list()!!.isEmpty() } + val blocks = ListOfCompactBlocksFixture.newFlow() - val blocks = ListOfCompactBlocksFixture.newFlow() + blockRepository.write(blocks) - val persistedBlocks = blockRepository.write(blocks) - - assertTrue { rootBlocksDirectory.exists() } - assertEquals(blocks.count(), persistedBlocks.size) - assertEquals(blocks.count(), rootBlocksDirectory.list()!!.size) - } - - @Test - fun deleteCompactBlockFilesTest() = runTest { - val rustBackend = FakeRustBackendFixture().new() - val blocksDirectory = FilePathFixture.newBlocksDir() - val parentDirectory = blocksDirectory.parentFile!! - - val blockRepository = getMockedFileCompactBlockRepository( - TypesafeBackendImpl(rustBackend), - FilePathFixture.newBlocksDir() - ) - - val testedBlocksRange = ListOfCompactBlocksFixture.DEFAULT_FILE_BLOCK_RANGE - val blocks = ListOfCompactBlocksFixture.newFlow(testedBlocksRange) - - val persistedBlocks = blockRepository.write(blocks) - - parentDirectory.also { - assertTrue(it.existsSuspend()) - assertTrue(it.listSuspend()!!.contains(FilePathFixture.DEFAULT_BLOCKS_DIR_NAME)) + assertEquals(blocks.last().height, blockRepository.getLatestHeight()?.value) } - blocksDirectory.also { - assertTrue(it.existsSuspend()) + @Test + fun findCompactBlockTest() = + runTest { + val network = ZcashNetwork.Testnet + val rustBackend = FakeRustBackendFixture().new() + val blockRepository = + getMockedFileCompactBlockRepository( + TypesafeBackendImpl(rustBackend), + FilePathFixture.newBlocksDir() + ) + + val blocks = ListOfCompactBlocksFixture.newFlow() + + blockRepository.write(blocks) + + val firstPersistedBlock = + blockRepository.findCompactBlock( + BlockHeight.new(network, blocks.first().height) + ) + val lastPersistedBlock = + blockRepository.findCompactBlock( + BlockHeight.new(network, blocks.last().height) + ) + val notPersistedBlockHeight = + BlockHeight.new( + network, + blockHeight = blocks.last().height + 1 + ) + + assertNotNull(firstPersistedBlock) + assertNotNull(lastPersistedBlock) + + assertEquals(blocks.first().height, firstPersistedBlock.height) + assertEquals(blocks.last().height, blockRepository.getLatestHeight()?.value) + assertNull(blockRepository.findCompactBlock(notPersistedBlockHeight)) + } + + @Test + fun writeBlocksTest() = + runTest { + val rustBackend = FakeRustBackendFixture().new() + val blockRepository = + getMockedFileCompactBlockRepository( + TypesafeBackendImpl(rustBackend), + FilePathFixture.newBlocksDir() + ) + + assertTrue { rustBackend.metadata.isEmpty() } + + val blocks = ListOfCompactBlocksFixture.newFlow() + val persistedBlocks = blockRepository.write(blocks) + assertEquals(blocks.count(), persistedBlocks.size) + assertEquals(blocks.count(), rustBackend.metadata.size) } - blockRepository.deleteAllCompactBlockFiles() - - parentDirectory.also { - assertTrue(it.existsSuspend()) - assertTrue(it.listSuspend()!!.contains(FilePathFixture.DEFAULT_BLOCKS_DIR_NAME)) - } - - blocksDirectory.also { blocksDir -> - assertTrue(blocksDir.existsSuspend()) - assertTrue(blocksDir.listSuspend()!!.isEmpty()) - } - } - @Test - fun rewindToTest() = runTest { - val rustBackend = FakeRustBackendFixture().new() - val blockRepository = getMockedFileCompactBlockRepository( - TypesafeBackendImpl(rustBackend), - FilePathFixture.newBlocksDir() - ) + fun writeFewBlocksTest() = + runTest { + val rustBackend = FakeRustBackendFixture().new() + val blockRepository = + getMockedFileCompactBlockRepository( + TypesafeBackendImpl(rustBackend), + FilePathFixture.newBlocksDir() + ) - val testedBlocksRange = ListOfCompactBlocksFixture.DEFAULT_FILE_BLOCK_RANGE + assertTrue { rustBackend.metadata.isEmpty() } - val blocks = ListOfCompactBlocksFixture.newFlow(testedBlocksRange) - blockRepository.write(blocks) + // prepare a list of blocks to be persisted, which has smaller size than buffer size + val reducedBlocksList = + ListOfCompactBlocksFixture.newFlow().apply { + val reduced = drop(count() / 2) + assertTrue { reduced.count() < FileCompactBlockRepository.BLOCKS_METADATA_BUFFER_SIZE } + } - val blocksRangeMiddleValue = testedBlocksRange.run { - start.value.plus(endInclusive.value).div(2) + val persistedBlocks = blockRepository.write(reducedBlocksList) + + assertEquals(reducedBlocksList.count(), persistedBlocks.size) + assertEquals(reducedBlocksList.count(), rustBackend.metadata.size) } - val rewindHeight: Long = blocksRangeMiddleValue - blockRepository.rewindTo(BlockHeight(rewindHeight)) - - // suppose to be 0 - val keptMetadataAboveRewindHeight = rustBackend.metadata - .filter { it.height > rewindHeight } - - assertTrue { keptMetadataAboveRewindHeight.isEmpty() } - - val expectedRewoundMetadataCount = - (testedBlocksRange.endInclusive.value - blocksRangeMiddleValue).toInt() - - assertEquals(expectedRewoundMetadataCount, blocks.count() - rustBackend.metadata.size) - - val expectedKeptMetadataCount = - (blocks.count() - expectedRewoundMetadataCount) - - assertEquals(expectedKeptMetadataCount, rustBackend.metadata.size) - - val keptMetadataBelowRewindHeight = rustBackend.metadata - .filter { it.height <= rewindHeight } - - assertEquals(expectedKeptMetadataCount, keptMetadataBelowRewindHeight.size) - } @Test - fun createTemporaryFileTest() = runTest { - val blocksDir = FilePathFixture.newBlocksDir() - val blocks = ListOfCompactBlocksFixture.newSequence() - val block = blocks.first() + fun writeBlocksAndCheckStorageTest() = + runTest { + val rustBackend = FakeRustBackendFixture().new() + val rootBlocksDirectory = FilePathFixture.newBlocksDir() + val blockRepository = + getMockedFileCompactBlockRepository( + TypesafeBackendImpl(rustBackend), + FilePathFixture.newBlocksDir() + ) - val file = block.createTemporaryFile(blocksDir) + assertTrue { rootBlocksDirectory.exists() } + assertTrue { rootBlocksDirectory.list()!!.isEmpty() } - assertTrue { file.existsSuspend() } - } + val blocks = ListOfCompactBlocksFixture.newFlow() + + val persistedBlocks = blockRepository.write(blocks) + + assertTrue { rootBlocksDirectory.exists() } + assertEquals(blocks.count(), persistedBlocks.size) + assertEquals(blocks.count(), rootBlocksDirectory.list()!!.size) + } @Test - fun finalizeFileTest() = runTest { - val blocksDir = FilePathFixture.newBlocksDir() - val blocks = ListOfCompactBlocksFixture.newSequence() - val block = blocks.first() + fun deleteCompactBlockFilesTest() = + runTest { + val rustBackend = FakeRustBackendFixture().new() + val blocksDirectory = FilePathFixture.newBlocksDir() + val parentDirectory = blocksDirectory.parentFile!! - val tempFile = block.createTemporaryFile(blocksDir) + val blockRepository = + getMockedFileCompactBlockRepository( + TypesafeBackendImpl(rustBackend), + FilePathFixture.newBlocksDir() + ) - val finalizedFile = File( - tempFile.absolutePath.dropLast(FileCompactBlockRepository.TEMPORARY_FILENAME_SUFFIX.length) - ) - assertFalse { finalizedFile.existsSuspend() } + val testedBlocksRange = ListOfCompactBlocksFixture.DEFAULT_FILE_BLOCK_RANGE + val blocks = ListOfCompactBlocksFixture.newFlow(testedBlocksRange) - tempFile.finalizeFile() - assertTrue { finalizedFile.existsSuspend() } + val persistedBlocks = blockRepository.write(blocks) - assertFalse { tempFile.existsSuspend() } - } + parentDirectory.also { + assertTrue(it.existsSuspend()) + assertTrue(it.listSuspend()!!.contains(FilePathFixture.DEFAULT_BLOCKS_DIR_NAME)) + } + + blocksDirectory.also { + assertTrue(it.existsSuspend()) + assertEquals(blocks.count(), persistedBlocks.size) + } + + blockRepository.deleteAllCompactBlockFiles() + + parentDirectory.also { + assertTrue(it.existsSuspend()) + assertTrue(it.listSuspend()!!.contains(FilePathFixture.DEFAULT_BLOCKS_DIR_NAME)) + } + + blocksDirectory.also { blocksDir -> + assertTrue(blocksDir.existsSuspend()) + assertTrue(blocksDir.listSuspend()!!.isEmpty()) + } + } + + @Test + fun rewindToTest() = + runTest { + val rustBackend = FakeRustBackendFixture().new() + val blockRepository = + getMockedFileCompactBlockRepository( + TypesafeBackendImpl(rustBackend), + FilePathFixture.newBlocksDir() + ) + + val testedBlocksRange = ListOfCompactBlocksFixture.DEFAULT_FILE_BLOCK_RANGE + + val blocks = ListOfCompactBlocksFixture.newFlow(testedBlocksRange) + blockRepository.write(blocks) + + val blocksRangeMiddleValue = + testedBlocksRange.run { + start.value.plus(endInclusive.value).div(2) + } + val rewindHeight: Long = blocksRangeMiddleValue + blockRepository.rewindTo(BlockHeight(rewindHeight)) + + // suppose to be 0 + val keptMetadataAboveRewindHeight = + rustBackend.metadata + .filter { it.height > rewindHeight } + + assertTrue { keptMetadataAboveRewindHeight.isEmpty() } + + val expectedRewoundMetadataCount = + (testedBlocksRange.endInclusive.value - blocksRangeMiddleValue).toInt() + + assertEquals(expectedRewoundMetadataCount, blocks.count() - rustBackend.metadata.size) + + val expectedKeptMetadataCount = + (blocks.count() - expectedRewoundMetadataCount) + + assertEquals(expectedKeptMetadataCount, rustBackend.metadata.size) + + val keptMetadataBelowRewindHeight = + rustBackend.metadata + .filter { it.height <= rewindHeight } + + assertEquals(expectedKeptMetadataCount, keptMetadataBelowRewindHeight.size) + } + + @Test + fun createTemporaryFileTest() = + runTest { + val blocksDir = FilePathFixture.newBlocksDir() + val blocks = ListOfCompactBlocksFixture.newSequence() + val block = blocks.first() + + val file = block.createTemporaryFile(blocksDir) + + assertTrue { file.existsSuspend() } + } + + @Test + fun finalizeFileTest() = + runTest { + val blocksDir = FilePathFixture.newBlocksDir() + val blocks = ListOfCompactBlocksFixture.newSequence() + val block = blocks.first() + + val tempFile = block.createTemporaryFile(blocksDir) + + val finalizedFile = + File( + tempFile.absolutePath.dropLast(FileCompactBlockRepository.TEMPORARY_FILENAME_SUFFIX.length) + ) + assertFalse { finalizedFile.existsSuspend() } + + tempFile.finalizeFile() + assertTrue { finalizedFile.existsSuspend() } + + assertFalse { tempFile.existsSuspend() } + } } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/BranchIdTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/BranchIdTest.kt index c08feb61..056864ff 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/BranchIdTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/BranchIdTest.kt @@ -26,7 +26,6 @@ class BranchIdTest internal constructor( private val branchHex: String, private val backend: TypesafeBackend ) { - @Test fun testBranchId_Hex() { val branchId = backend.getBranchIdForHeight(height) @@ -49,7 +48,6 @@ class BranchIdTest internal constructor( } companion object { - @JvmStatic @Parameterized.Parameters @Suppress("LongMethod") @@ -58,28 +56,30 @@ class BranchIdTest internal constructor( // is an abnormal use of the SDK because this really should run at the rust level // However, due to quirks on certain devices, we created this test at the Android level, // as a sanity check - val testnetBackend = runBlocking { - TypesafeBackendImpl( - RustBackend.new( - File(""), - File(""), - File(""), - File(""), - ZcashNetwork.Testnet.id, + val testnetBackend = + runBlocking { + TypesafeBackendImpl( + RustBackend.new( + File(""), + File(""), + File(""), + File(""), + ZcashNetwork.Testnet.id, + ) ) - ) - } - val mainnetBackend = runBlocking { - TypesafeBackendImpl( - RustBackend.new( - File(""), - File(""), - File(""), - File(""), - ZcashNetwork.Mainnet.id, + } + val mainnetBackend = + runBlocking { + TypesafeBackendImpl( + RustBackend.new( + File(""), + File(""), + File(""), + File(""), + ZcashNetwork.Mainnet.id, + ) ) - ) - } + } return listOf( // Mainnet Cases arrayOf( @@ -110,7 +110,6 @@ class BranchIdTest internal constructor( "e9ff75a6", mainnetBackend ), - // Testnet Cases arrayOf( "Sapling", diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/TransparentTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/TransparentTest.kt index b2e569c8..0e204b42 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/TransparentTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/TransparentTest.kt @@ -21,30 +21,35 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @SmallTest class TransparentTest(val expected: Expected, val network: ZcashNetwork) { - @Test - fun deriveUnifiedFullViewingKeysFromSeedTest() = runBlocking { - val ufvks = RustDerivationTool.new().deriveUnifiedFullViewingKeysTypesafe( - SEED, - network = network, - numberOfAccounts = - Derivation.DEFAULT_NUMBER_OF_ACCOUNTS - ) - assertEquals(1, ufvks.size) - val ufvk = ufvks.first() - assertEquals(expected.uAddr, RustDerivationTool.new().deriveUnifiedAddress(ufvk.encoding, network = network)) - // TODO: If we need this, change DerivationTool to derive from the UFVK instead of the public key. - // assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPublicKey(ufvk.encoding, - // network = network)) - } + fun deriveUnifiedFullViewingKeysFromSeedTest() = + runBlocking { + val ufvks = + RustDerivationTool.new().deriveUnifiedFullViewingKeysTypesafe( + SEED, + network = network, + numberOfAccounts = + Derivation.DEFAULT_NUMBER_OF_ACCOUNTS + ) + assertEquals(1, ufvks.size) + val ufvk = ufvks.first() + assertEquals( + expected.uAddr, + RustDerivationTool.new().deriveUnifiedAddress(ufvk.encoding, network = network) + ) + // TODO: If we need this, change DerivationTool to derive from the UFVK instead of the public key. + // assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPublicKey(ufvk.encoding, + // network = network)) + } companion object { - const val PHRASE = "deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp" + - " warrior drink narrow normal lunch behind salt deal person" + const val PHRASE = + "deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp" + + " warrior drink narrow normal lunch behind salt deal person" val MNEMONIC = MnemonicCode(PHRASE) val SEED = MNEMONIC.toSeed() - @Suppress("MaxLineLength") + @Suppress("MaxLineLength", "ktlint:standard:max-line-length") object ExpectedMainnet : Expected { override val tAddr = "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4" override val zAddr = "zs1yc4sgtfwwzz6xfsy2xsradzr6m4aypgxhfw2vcn3hatrh5ryqsr08sgpemlg39vdh9kfupx20py" @@ -54,7 +59,7 @@ class TransparentTest(val expected: Expected, val network: ZcashNetwork) { override val tpk = "03b1d7fb28d17c125b504d06b1530097e0a3c76ada184237e3bc0925041230a5af" } - @Suppress("MaxLineLength") + @Suppress("MaxLineLength", "ktlint:standard:max-line-length") object ExpectedTestnet : Expected { override val tAddr = "tm9v3KTsjXK8XWSqiwFjic6Vda6eHY9Mjjq" override val zAddr = "ztestsapling1wn3tw9w5rs55x5yl586gtk72e8hcfdq8zsnjzcu8p7ghm8lrx54axc74mvm335q7lmy3g0sqje6" @@ -71,10 +76,11 @@ class TransparentTest(val expected: Expected, val network: ZcashNetwork) { @JvmStatic @Parameterized.Parameters - fun data() = listOf( - arrayOf(ExpectedTestnet, ZcashNetwork.Testnet), - arrayOf(ExpectedMainnet, ZcashNetwork.Mainnet) - ) + fun data() = + listOf( + arrayOf(ExpectedTestnet, ZcashNetwork.Testnet), + arrayOf(ExpectedMainnet, ZcashNetwork.Mainnet) + ) } interface Expected { diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/PercentDecimalTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/PercentDecimalTest.kt index 2788add8..4f3d8fc6 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/PercentDecimalTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/PercentDecimalTest.kt @@ -5,7 +5,6 @@ import org.junit.Test import kotlin.test.assertTrue class PercentDecimalTest { - @Test(expected = IllegalArgumentException::class) @SmallTest fun require_greater_than_zero() { diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKeyTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKeyTest.kt index ca0131a4..5b5bf15a 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKeyTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKeyTest.kt @@ -12,40 +12,43 @@ class UnifiedSpendingKeyTest { @Test @SmallTest @OptIn(ExperimentalCoroutinesApi::class) - fun factory_copies_bytes() = runTest { - val spendingKey = WalletFixture.getUnifiedSpendingKey() - val expected = spendingKey.copyBytes().copyOf() + fun factory_copies_bytes() = + runTest { + val spendingKey = WalletFixture.getUnifiedSpendingKey() + val expected = spendingKey.copyBytes().copyOf() - val bytes = spendingKey.copyBytes() - val newSpendingKey = UnifiedSpendingKey.new(spendingKey.account, bytes) - bytes.clear() + val bytes = spendingKey.copyBytes() + val newSpendingKey = UnifiedSpendingKey.new(spendingKey.account, bytes) + bytes.clear() - assertContentEquals(expected, newSpendingKey.getOrThrow().copyBytes()) - } + assertContentEquals(expected, newSpendingKey.getOrThrow().copyBytes()) + } @Test @SmallTest @OptIn(ExperimentalCoroutinesApi::class) - fun get_copies_bytes() = runTest { - val spendingKey = WalletFixture.getUnifiedSpendingKey() + fun get_copies_bytes() = + runTest { + val spendingKey = WalletFixture.getUnifiedSpendingKey() - val expected = spendingKey.copyBytes() - val newSpendingKey = UnifiedSpendingKey.new(spendingKey.account, expected) + val expected = spendingKey.copyBytes() + val newSpendingKey = UnifiedSpendingKey.new(spendingKey.account, expected) - newSpendingKey.getOrThrow().copyBytes().clear() + newSpendingKey.getOrThrow().copyBytes().clear() - assertContentEquals(expected, newSpendingKey.getOrThrow().copyBytes()) - } + assertContentEquals(expected, newSpendingKey.getOrThrow().copyBytes()) + } @Test @SmallTest @OptIn(ExperimentalCoroutinesApi::class) - fun toString_does_not_leak() = runTest { - assertEquals( - "UnifiedSpendingKey(account=Account(value=0))", - WalletFixture.getUnifiedSpendingKey().toString() - ) - } + fun toString_does_not_leak() = + runTest { + assertEquals( + "UnifiedSpendingKey(account=Account(value=0))", + WalletFixture.getUnifiedSpendingKey().toString() + ) + } } private fun ByteArray.clear() { diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/ShieldFundsSample.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/ShieldFundsSample.kt index c770f305..ab1a9b13 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/ShieldFundsSample.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/ShieldFundsSample.kt @@ -12,9 +12,9 @@ import org.junit.Test * Samples related to shielding funds. */ class ShieldFundsSample { - - val SEED_PHRASE = "still champion voice habit trend flight survey between bitter process artefact blind carbon" + - " truly provide dizzy crush flush breeze blouse charge solid fish spread" + val 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" // "deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow // normal lunch behind salt deal person" @@ -27,12 +27,13 @@ class ShieldFundsSample { */ @Test @Ignore("This test is broken") - fun constructT2Z() = runBlocking { - val wallet = TestWallet(TestWallet.Backups.DEV_WALLET, ZcashNetwork.Mainnet) + fun constructT2Z() = + runBlocking { + val wallet = TestWallet(TestWallet.Backups.DEV_WALLET, ZcashNetwork.Mainnet) - Assert.assertEquals("foo", "${wallet.unifiedAddress} ${wallet.transparentAddress}") + Assert.assertEquals("foo", "${wallet.unifiedAddress} ${wallet.transparentAddress}") // wallet.shieldFunds() - Assert.assertEquals(Zatoshi(5), wallet.synchronizer.saplingBalances.value?.available) - } + Assert.assertEquals(Zatoshi(5), wallet.synchronizer.saplingBalances.value?.available) + } } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/TransparentRestoreSample.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/TransparentRestoreSample.kt index 2f05e964..6f802b9a 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/TransparentRestoreSample.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/sample/TransparentRestoreSample.kt @@ -20,8 +20,7 @@ import org.junit.Test * the same data. */ class TransparentRestoreSample { - - val TX_VALUE = Zatoshi(ZcashSdk.MINERS_FEE.value / 2) + val txValue = Zatoshi(ZcashSdk.MINERS_FEE.value / 2) // val walletA = SimpleWallet(SEED_PHRASE, "WalletA") @@ -36,71 +35,78 @@ class TransparentRestoreSample { // DerivationTool.deriveTransparentAddress(Mnemonics.MnemonicCode(RANDOM_PHRASE).toSeed(), Testnet) // @Test - fun sendZ2Texternal() = runBlocking { - val extWallet = TestWallet(TestWallet.Backups.ALICE, alias = "WalletE") - extWallet.sync() + fun sendZ2Texternal() = + runBlocking { + val extWallet = TestWallet(TestWallet.Backups.ALICE, alias = "WalletE") + extWallet.sync() // extWallet.send(542, walletSandbox.transparentAddress, "External funds memo is lost, though") - delay(1000) - } + delay(1000) + } // @Test - fun sendZ2T() = runBlocking { + fun sendZ2T() = + runBlocking { // walletSandbox.sync() // walletZ2T.send(543, externalTransparentAddress, "External funds memo is lost, though") - delay(1000) - } + delay(1000) + } // @Test - fun autoShield() = runBlocking { - val wallet = TestWallet(TestWallet.Backups.SAMPLE_WALLET, alias = "WalletC") - wallet.sync() - val tbalance = wallet.transparentBalance() - val address = wallet.transparentAddress + fun autoShield() = + runBlocking { + val wallet = TestWallet(TestWallet.Backups.SAMPLE_WALLET, alias = "WalletC") + wallet.sync() + val tbalance = wallet.transparentBalance() + val address = wallet.transparentAddress - Assert.assertTrue( - "Not enough funds to run sample. Expected some Zatoshi but found ${tbalance.available}. " + - "Try adding funds to $address", - tbalance.available.value > 0 - ) + Assert.assertTrue( + "Not enough funds to run sample. Expected some Zatoshi but found ${tbalance.available}. " + + "Try adding funds to $address", + tbalance.available.value > 0 + ) // wallet.shieldFunds() - } + } // @Test - fun cli() = runBlocking { + fun cli() = + runBlocking { // val wallet = SimpleWallet(SEED_PHRASE, "WalletCli") // wallet.sync() // wallet.rewindToHeight(1343500).join(45_000) - val wallet = TestWallet(TestWallet.Backups.SAMPLE_WALLET, alias = "WalletC") + val wallet = TestWallet(TestWallet.Backups.SAMPLE_WALLET, alias = "WalletC") // wallet.sync().rewindToHeight(1339178).join(10000) - wallet.sync().rewindToHeight(BlockHeight.new(ZcashNetwork.Testnet, 1339178)).send( - "ztestsapling17zazsl8rryl8kjaqxnr2r29rw9d9a2mud37ugapm0s8gmyv0ue43h9lqwmhdsp3nu9dazeqfs6l", - "is send broken?" - ).join(5) - } + wallet.sync().rewindToHeight(BlockHeight.new(ZcashNetwork.Testnet, 1339178)).send( + "ztestsapling17zazsl8rryl8kjaqxnr2r29rw9d9a2mud37ugapm0s8gmyv0ue43h9lqwmhdsp3nu9dazeqfs6l", + "is send broken?" + ).join(5) + } // This test is extremely slow and doesn't assert anything, so the benefit of this test is unclear // It is disabled to allow moving forward with configuring CI. @Test @LargeTest @Ignore("This test is extremely slow") - fun kris() = runBlocking { - val wallet0 = TestWallet( - TestWallet.Backups.SAMPLE_WALLET.seedPhrase, - "tmpabc", - ZcashNetwork.Testnet, - startHeight = BlockHeight.new( - ZcashNetwork.Testnet, - 1330190 - ) - ) + fun kris() = + runBlocking { + val wallet0 = + TestWallet( + TestWallet.Backups.SAMPLE_WALLET.seedPhrase, + "tmpabc", + ZcashNetwork.Testnet, + startHeight = + BlockHeight.new( + ZcashNetwork.Testnet, + 1330190 + ) + ) // val wallet1 = SimpleWallet(WALLET0_PHRASE, "Wallet1") - wallet0.sync() // .shieldFunds() + wallet0.sync() // .shieldFunds() // .send(amount = 1543L, memo = "") - .join() + .join() // wallet1.sync().join(5_000L) - } + } /* @@ -113,36 +119,40 @@ class TransparentRestoreSample { /** * Sanity check that the wallet has enough funds for the test */ -// @Test - fun hasFunds() = runBlocking { - val walletSandbox = TestWallet( - TestWallet.Backups.SAMPLE_WALLET.seedPhrase, - "WalletC", - ZcashNetwork.Testnet, - startHeight = BlockHeight.new( - ZcashNetwork.Testnet, - 1330190 - ) - ) - // val job = walletA.walletScope.launch { - // walletA.sync() - // } - walletSandbox.sync() - // job.join() - delay(500) + @Test + @Ignore("Was disabled by its author") + fun hasFunds() = + runBlocking { + val walletSandbox = + TestWallet( + TestWallet.Backups.SAMPLE_WALLET.seedPhrase, + "WalletC", + ZcashNetwork.Testnet, + startHeight = + BlockHeight.new( + ZcashNetwork.Testnet, + 1330190 + ) + ) + // val job = walletA.walletScope.launch { + // walletA.sync() + // } + walletSandbox.sync() + // job.join() + delay(500) - // val value = walletA.available - // val address = walletA.shieldedAddress - // Assert.assertTrue("Not enough funds to run sample. Expected at least $TX_VALUE Zatoshi but found $value. Try adding funds to $address", value >= TX_VALUE) + // val value = walletA.available + // val address = walletA.shieldedAddress + // Assert.assertTrue("Not enough funds to run sample. Expected at least $TX_VALUE Zatoshi but found $value. Try adding funds to $address", value >= TX_VALUE) - // send z->t - // walletA.send(TX_VALUE, walletA.transparentAddress, "${TransparentRestoreSample::class.java.simpleName} z->t") + // send z->t + // walletA.send(TX_VALUE, walletA.transparentAddress, "${TransparentRestoreSample::class.java.simpleName} z->t") - walletSandbox.rewindToHeight(BlockHeight.new(ZcashNetwork.Testnet, 1339178)) - delay(500) - // walletB.sync() - // rewind database B to height then rescan - } + walletSandbox.rewindToHeight(BlockHeight.new(ZcashNetwork.Testnet, 1339178)) + delay(500) + // walletB.sync() + // rewind database B to height then rescan + } // // when startHeight is null, it will use the latest checkpoint // class SimpleWallet( diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/test/Global.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/test/Global.kt index 10ba6fd1..e644d07e 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/test/Global.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/test/Global.kt @@ -12,14 +12,19 @@ import kotlin.test.assertNotNull fun getAppContext(): Context = ApplicationProvider.getApplicationContext() -fun getStringResource(@StringRes resId: Int) = getAppContext().getString(resId) +fun getStringResource( + @StringRes resId: Int +) = getAppContext().getString(resId) -fun getStringResourceWithArgs(@StringRes resId: Int, vararg formatArgs: String) = - getAppContext().getString(resId, *formatArgs) +fun getStringResourceWithArgs( + @StringRes resId: Int, + vararg formatArgs: String +) = getAppContext().getString(resId, *formatArgs) -fun readFileLinesInFlow(filePathName: String) = flow { - val testFile = javaClass.getResourceAsStream(filePathName) - assertNotNull(testFile, "Test file read failure.") +fun readFileLinesInFlow(filePathName: String) = + flow { + val testFile = javaClass.getResourceAsStream(filePathName) + assertNotNull(testFile, "Test file read failure.") - emitAll(testFile.bufferedReader().lineSequence().asFlow()) -}.flowOn(Dispatchers.IO) + emitAll(testFile.bufferedReader().lineSequence().asFlow()) + }.flowOn(Dispatchers.IO) diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/test/ScopedTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/test/ScopedTest.kt index d2617958..a296ebf1 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/test/ScopedTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/test/ScopedTest.kt @@ -26,21 +26,27 @@ open class ScopedTest(val defaultTimeout: Long = 2000L) { @Before fun start() { - testScope = CoroutineScope( - Job(classScope.coroutineContext[Job]!!) + newFixedThreadPoolContext( - 5, - this.javaClass.simpleName + testScope = + CoroutineScope( + Job(classScope.coroutineContext[Job]!!) + + newFixedThreadPoolContext( + 5, + this.javaClass.simpleName + ) ) - ) } @After - fun end() = runBlocking { - testScope.cancel() - testScope.coroutineContext[Job]?.join() - } + fun end() = + runBlocking { + testScope.cancel() + testScope.coroutineContext[Job]?.join() + } - fun timeout(duration: Long, block: suspend () -> Unit) = timeoutWith(testScope, duration, block) + fun timeout( + duration: Long, + block: suspend () -> Unit + ) = timeoutWith(testScope, duration, block) companion object { @JvmStatic @@ -49,20 +55,26 @@ open class ScopedTest(val defaultTimeout: Long = 2000L) { @BeforeClass @JvmStatic fun createScope() { - classScope = CoroutineScope( - SupervisorJob() + newFixedThreadPoolContext(2, this::class.java.simpleName) - ) + classScope = + CoroutineScope( + SupervisorJob() + newFixedThreadPoolContext(2, this::class.java.simpleName) + ) } @AfterClass @JvmStatic - fun destroyScope() = runBlocking { - classScope.cancel() - classScope.coroutineContext[Job]?.join() - } + fun destroyScope() = + runBlocking { + classScope.cancel() + classScope.coroutineContext[Job]?.join() + } @JvmStatic - fun timeoutWith(scope: CoroutineScope, duration: Long, block: suspend () -> Unit) { + fun timeoutWith( + scope: CoroutineScope, + duration: Long, + block: suspend () -> Unit + ) { scope.launch { delay(duration) val message = "ERROR: Test timed out after ${duration}ms" diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/tool/CheckpointToolTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/tool/CheckpointToolTest.kt index 45dd6091..d8b56643 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/tool/CheckpointToolTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/tool/CheckpointToolTest.kt @@ -31,14 +31,15 @@ class CheckpointToolTest { val directory = "co.electriccoin.zcash/checkpoint/goodnet" val context = ApplicationProvider.getApplicationContext() - val birthday = runBlocking { - CheckpointTool.getFirstValidWalletBirthday( - context, - ZcashNetwork.Mainnet, - directory, - listOf("1300000.json", "1290000.json") - ) - } + val birthday = + runBlocking { + CheckpointTool.getFirstValidWalletBirthday( + context, + ZcashNetwork.Mainnet, + directory, + listOf("1300000.json", "1290000.json") + ) + } assertEquals(1300000, birthday.height.value) } @@ -51,14 +52,15 @@ class CheckpointToolTest { val directory = "co.electriccoin.zcash/checkpoint/badnet" val context = ApplicationProvider.getApplicationContext() - val birthday = runBlocking { - CheckpointTool.getFirstValidWalletBirthday( - context, - ZcashNetwork.Mainnet, - directory, - listOf("1300000.json", "1290000.json") - ) - } + val birthday = + runBlocking { + CheckpointTool.getFirstValidWalletBirthday( + context, + ZcashNetwork.Mainnet, + directory, + listOf("1300000.json", "1290000.json") + ) + } assertEquals(1290000, birthday.height.value) } } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt index 73759313..ef9b1907 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt @@ -14,7 +14,6 @@ import org.junit.Test @ExperimentalCoroutinesApi class AddressGeneratorUtil { - private val mnemonics = SimpleMnemonics() @Test @@ -27,15 +26,16 @@ class AddressGeneratorUtil { } @Test - fun generateAddresses() = runBlocking { - readFileLinesInFlow("/utils/seeds.txt") - .map { seedPhrase -> - mnemonics.toSeed(seedPhrase.toCharArray()) - }.map { seed -> - RustDerivationTool.new().deriveUnifiedAddress(seed, ZcashNetwork.Mainnet, Account.DEFAULT) - }.collect { address -> - println("xrxrx2\t$address") - assertTrue(address.startsWith("u1")) - } - } + fun generateAddresses() = + runBlocking { + readFileLinesInFlow("/utils/seeds.txt") + .map { seedPhrase -> + mnemonics.toSeed(seedPhrase.toCharArray()) + }.map { seed -> + RustDerivationTool.new().deriveUnifiedAddress(seed, ZcashNetwork.Mainnet, Account.DEFAULT) + }.collect { address -> + println("xrxrx2\t$address") + assertTrue(address.startsWith("u1")) + } + } } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/BalancePrinterUtil.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/BalancePrinterUtil.kt index 59805a03..9bdc4082 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/BalancePrinterUtil.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/BalancePrinterUtil.kt @@ -26,7 +26,6 @@ import org.junit.Test */ @ExperimentalCoroutinesApi class BalancePrinterUtil { - private val network = ZcashNetwork.Mainnet private val downloadBatchSize = 9_000 private val birthdayHeight = BlockHeight.new(network, 523240) @@ -54,13 +53,14 @@ class BalancePrinterUtil { birthday = runBlocking { CheckpointTool.loadNearest(context, network, birthdayHeight) } } - private fun cacheBlocks() = runBlocking { + private fun cacheBlocks() = + runBlocking { // twig("downloading compact blocks...") // val latestBlockHeight = downloader.getLatestBlockHeight() // val lastDownloaded = downloader.getLastDownloadedHeight() // val blockRange = (Math.max(birthday, lastDownloaded))..latestBlockHeight // downloadNewBlocks(blockRange) - } + } private suspend fun deleteDb(dbName: String) { context.getDatabasePath(dbName).absoluteFile.deleteSuspend() @@ -68,13 +68,14 @@ class BalancePrinterUtil { @Test @Ignore("This test is broken") - fun printBalances() = runBlocking { - readFileLinesInFlow("/utils/seeds.txt") - .map { seedPhrase -> - Twig.debug { "checking balance for: $seedPhrase" } - mnemonics.toSeed(seedPhrase.toCharArray()) - }.collect { seed -> - // TODO: clear the dataDb but leave the cacheDb + fun printBalances() = + runBlocking { + readFileLinesInFlow("/utils/seeds.txt") + .map { seedPhrase -> + Twig.debug { "checking balance for: $seedPhrase" } + mnemonics.toSeed(seedPhrase.toCharArray()) + }.collect { seed -> + // TODO: clear the dataDb but leave the cacheDb /* what I need to do right now @@ -90,17 +91,19 @@ class BalancePrinterUtil { - I might need to consider how state is impacting this design - can we be more stateless and thereby improve the flexibility of this code?!!! */ - synchronizer?.close() - synchronizer = Synchronizer.new( - context, - network, - lightWalletEndpoint = LightWalletEndpoint - .defaultForNetwork(network), - seed = seed, - birthday = birthdayHeight, - // Using existing wallet init mode as simplification for the test - walletInitMode = WalletInitMode.ExistingWallet - ) + synchronizer?.close() + synchronizer = + Synchronizer.new( + context, + network, + lightWalletEndpoint = + LightWalletEndpoint + .defaultForNetwork(network), + seed = seed, + birthday = birthdayHeight, + // Using existing wallet init mode as simplification for the test + walletInitMode = WalletInitMode.ExistingWallet + ) // deleteDb(dataDbPath) // initWallet(seed) @@ -113,8 +116,8 @@ class BalancePrinterUtil { // twig("found available: $available") // twig("xrxrx2\t$seed\t$total\t$available") // println("xrxrx2\t$seed\t$total\t$available") - } - } + } + } // @Test // fun printBalances() = runBlocking { diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/DataDbScannerUtil.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/DataDbScannerUtil.kt index f570a852..171e360b 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/DataDbScannerUtil.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/DataDbScannerUtil.kt @@ -47,7 +47,8 @@ class DataDbScannerUtil { // cacheBlocks() } - private fun cacheBlocks() = runBlocking { + private fun cacheBlocks() = + runBlocking { // val latestBlockHeight = downloader.getLatestBlockHeight() // val lastDownloaded = downloader.getLastDownloadedHeight() // val blockRange = (Math.max(birthday, lastDownloaded))..latestBlockHeight @@ -55,7 +56,7 @@ class DataDbScannerUtil { // val error = validateNewBlocks(blockRange) // twig("validation completed with result $error") // assertEquals(-1, error) - } + } private fun deleteDb(dbName: String) { context.getDatabasePath(dbName).absoluteFile.delete() @@ -64,19 +65,22 @@ class DataDbScannerUtil { @Test @Ignore("This test is broken") fun scanExistingDb() { - synchronizer = Synchronizer.newBlocking( - context, - ZcashNetwork.Mainnet, - lightWalletEndpoint = LightWalletEndpoint - .defaultForNetwork(ZcashNetwork.Mainnet), - seed = byteArrayOf(), - birthday = BlockHeight.new( + synchronizer = + Synchronizer.newBlocking( + context, ZcashNetwork.Mainnet, - birthdayHeight - ), - // Using existing wallet init mode as simplification for the test - walletInitMode = WalletInitMode.ExistingWallet - ) + lightWalletEndpoint = + LightWalletEndpoint + .defaultForNetwork(ZcashNetwork.Mainnet), + seed = byteArrayOf(), + birthday = + BlockHeight.new( + ZcashNetwork.Mainnet, + birthdayHeight + ), + // Using existing wallet init mode as simplification for the test + walletInitMode = WalletInitMode.ExistingWallet + ) println("sync!") val scope = (synchronizer as SdkSynchronizer).coroutineScope diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/SimpleMnemonics.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/SimpleMnemonics.kt index 6a9bdb16..deacc00c 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/SimpleMnemonics.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/SimpleMnemonics.kt @@ -10,11 +10,18 @@ import java.util.Locale class SimpleMnemonics : MnemonicPlugin { override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language) + override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy() + override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars + override fun nextMnemonic(seed: ByteArray): CharArray = MnemonicCode(seed).chars + override fun nextMnemonicList(): List = MnemonicCode(WordCount.COUNT_24).words + override fun nextMnemonicList(seed: ByteArray): List = MnemonicCode(seed).words + override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed() + override fun toWordList(mnemonic: CharArray): List = MnemonicCode(mnemonic).words } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt index 919388da..bb3d8533 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt @@ -49,9 +49,10 @@ class TestWallet( alias = alias ) - val walletScope = CoroutineScope( - SupervisorJob() + newFixedThreadPoolContext(3, this.javaClass.simpleName) - ) + val walletScope = + CoroutineScope( + SupervisorJob() + newFixedThreadPoolContext(3, this.javaClass.simpleName) + ) // Although runBlocking isn't great, this usage is OK because this is only used within the // automated tests @@ -61,16 +62,17 @@ class TestWallet( private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed() private val spendingKey = runBlocking { RustDerivationTool.new().deriveUnifiedSpendingKey(seed, network = network, account) } - val synchronizer: SdkSynchronizer = Synchronizer.newBlocking( - context, - network, - alias, - lightWalletEndpoint = endpoint, - seed = seed, - startHeight, - // Using existing wallet init mode as simplification for the test - walletInitMode = WalletInitMode.ExistingWallet - ) as SdkSynchronizer + val synchronizer: SdkSynchronizer = + Synchronizer.newBlocking( + context, + network, + alias, + lightWalletEndpoint = endpoint, + seed = seed, + startHeight, + // Using existing wallet init mode as simplification for the test + walletInitMode = WalletInitMode.ExistingWallet + ) as SdkSynchronizer val available get() = synchronizer.saplingBalances.value?.available val unifiedAddress = @@ -86,12 +88,13 @@ class TestWallet( } suspend fun sync(timeout: Long = -1): TestWallet { - val killSwitch = walletScope.launch { - if (timeout > 0) { - delay(timeout) - throw TimeoutException("Failed to sync wallet within ${timeout}ms") + val killSwitch = + walletScope.launch { + if (timeout > 0) { + delay(timeout) + throw TimeoutException("Failed to sync wallet within ${timeout}ms") + } } - } // block until synced synchronizer.status.first { it == Synchronizer.Status.SYNCED } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TransactionCounterUtil.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TransactionCounterUtil.kt index 08fb5905..055fa2a7 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TransactionCounterUtil.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TransactionCounterUtil.kt @@ -22,6 +22,7 @@ class TransactionCounterUtil { fun testBlockSize() { val sizes = mutableMapOf() + @Suppress("ktlint:standard:multiline-expression-wrapping") lightWalletClient.getBlockRange( BlockHeightUnsafe.from( BlockHeight.new( @@ -58,6 +59,7 @@ class TransactionCounterUtil { var totalOutputs = 0 var totalTxs = 0 + @Suppress("ktlint:standard:multiline-expression-wrapping") lightWalletClient.getBlockRange( BlockHeightUnsafe.from( BlockHeight.new( diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/CheckpointFixture.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/CheckpointFixture.kt index ba3a83ce..4c601c75 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/CheckpointFixture.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/CheckpointFixture.kt @@ -19,7 +19,7 @@ object CheckpointFixture { const val HASH = "00000000019e5b25a95c7607e7789eb326fddd69736970ebbe1c7d00247ef902" const val EPOCH_SECONDS = 1639913234L - @Suppress("MaxLineLength") + @Suppress("MaxLineLength", "ktlint:standard:max-line-length") const val TREE = "01ce183032b16ed87fcc5052a42d908376526126346567773f55bc58a63e4480160013000001bae5112769a07772345dd402039f2949c457478fe9327363ff631ea9d78fb80d0177c0b6c21aa9664dc255336ed450914088108c38a9171c85875b4e53d31b3e140171add6f9129e124651ca894aa842a3c71b1738f3ee2b7ba829106524ef51e62101f9cebe2141ee9d0a3f3a3e28bce07fa6b6e1c7b42c01cc4fe611269e9d52da540001d0adff06de48569129bd2a211e3253716362da97270d3504d9c1b694689ebe3c0122aaaea90a7fa2773b8166937310f79a4278b25d759128adf3138d052da3725b0137fb2cbc176075a45db2a3c32d3f78e669ff2258fd974e99ec9fb314d7fd90180165aaee3332ea432d13a9398c4863b38b8a7a491877a5c46b0802dcd88f7e324301a9a262f8b92efc2e0e3e4bd1207486a79d62e87b4ab9cc41814d62a23c4e28040001e3c4ee998682df5c5e230d6968e947f83d0c03682f0cfc85f1e6ec8e8552c95a000155989fed7a8cc7a0d479498d6881ca3bafbe05c7095110f85c64442d6a06c25c0185cd8c141e620eda0ca0516f42240aedfabdf9189c8c6ac834b7bdebc171331d01ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644" internal fun new( @@ -30,10 +30,11 @@ object CheckpointFixture { ) = Checkpoint(height = height, hash = hash, epochSeconds = time, tree = tree) } -internal fun Checkpoint.toJson() = JSONObject().apply { - put(Checkpoint.KEY_VERSION, Checkpoint.VERSION_1) - put(Checkpoint.KEY_HEIGHT, height.value) - put(Checkpoint.KEY_HASH, hash) - put(Checkpoint.KEY_EPOCH_SECONDS, epochSeconds) - put(Checkpoint.KEY_TREE, tree) -}.toString() +internal fun Checkpoint.toJson() = + JSONObject().apply { + put(Checkpoint.KEY_VERSION, Checkpoint.VERSION_1) + put(Checkpoint.KEY_HEIGHT, height.value) + put(Checkpoint.KEY_HASH, hash) + put(Checkpoint.KEY_EPOCH_SECONDS, epochSeconds) + put(Checkpoint.KEY_TREE, tree) + }.toString() diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/DatabasePathFixture.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/DatabasePathFixture.kt index 8278cafe..cdba2bf4 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/DatabasePathFixture.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/DatabasePathFixture.kt @@ -12,18 +12,20 @@ import java.io.File * Provides a unified way for getting fixture directories on the database root path for test purposes. */ object DatabasePathFixture { - val NO_BACKUP_DIR_PATH: String = runBlocking { - getAppContext().getNoBackupFilesDirSuspend().absolutePath - } - val DATABASE_DIR_PATH: String = runBlocking { - getAppContext().getDatabasePathSuspend("temporary.db").parentFile.let { parentFile -> - assert(parentFile != null) { "Failed to create database folder." } - parentFile!!.mkdirs() - - assert(parentFile.existsSuspend()) { "Failed to check database folder." } - parentFile.absolutePath + val NO_BACKUP_DIR_PATH: String = + runBlocking { + getAppContext().getNoBackupFilesDirSuspend().absolutePath + } + val DATABASE_DIR_PATH: String = + runBlocking { + getAppContext().getDatabasePathSuspend("temporary.db").parentFile.let { parentFile -> + assert(parentFile != null) { "Failed to create database folder." } + parentFile!!.mkdirs() + + assert(parentFile.existsSuspend()) { "Failed to check database folder." } + parentFile.absolutePath + } } - } const val INTERNAL_DATABASE_PATH = Files.NO_BACKUP_SUBDIRECTORY internal fun new( 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 b33dfc68..70a13743 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 @@ -11,7 +11,6 @@ internal class FakeRustBackend( override val networkId: Int, val metadata: MutableList ) : Backend { - override suspend fun writeBlockMetadata(blockMetadata: List) { metadata.addAll(blockMetadata) } @@ -77,7 +76,9 @@ internal class FakeRustBackend( } override suspend fun initBlockMetaDb(): Int = - error("Intentionally not implemented in mocked FakeRustBackend implementation.") + error( + "Intentionally not implemented in mocked FakeRustBackend implementation." + ) override suspend fun createToAddress( account: Int, @@ -89,7 +90,11 @@ internal class FakeRustBackend( TODO("Not yet implemented") } - override suspend fun shieldToAddress(account: Int, unifiedSpendingKey: ByteArray, memo: ByteArray?): ByteArray { + override suspend fun shieldToAddress( + account: Int, + unifiedSpendingKey: ByteArray, + memo: ByteArray? + ): ByteArray { TODO("Not yet implemented") } @@ -103,8 +108,7 @@ internal class FakeRustBackend( seed: ByteArray, treeState: ByteArray, recoverUntil: Long? - ): JniUnifiedSpendingKey = - error("Intentionally not implemented in mocked FakeRustBackend implementation.") + ): JniUnifiedSpendingKey = error("Intentionally not implemented in mocked FakeRustBackend implementation.") override fun isValidShieldedAddr(addr: String): Boolean = error("Intentionally not implemented in mocked FakeRustBackend implementation.") @@ -123,7 +127,9 @@ internal class FakeRustBackend( error("Intentionally not implemented in mocked FakeRustBackend implementation.") override fun getSaplingReceiver(ua: String): String? = - error("Intentionally not implemented in mocked FakeRustBackend implementation.") + error( + "Intentionally not implemented in mocked FakeRustBackend implementation." + ) override suspend fun listTransparentReceivers(account: Int): List { TODO("Not yet implemented") @@ -133,13 +139,17 @@ internal class FakeRustBackend( TODO("Not yet implemented") } - override suspend fun getMemoAsUtf8(txId: ByteArray, outputIndex: Int): String? = - error("Intentionally not implemented in mocked FakeRustBackend implementation.") + override suspend fun getMemoAsUtf8( + txId: ByteArray, + outputIndex: Int + ): String? = error("Intentionally not implemented in mocked FakeRustBackend implementation.") override suspend fun getNearestRewindHeight(height: Long): Long { TODO("Not yet implemented") } - override suspend fun scanBlocks(fromHeight: Long, limit: Long) = - error("Intentionally not implemented in mocked FakeRustBackend implementation.") + override suspend fun scanBlocks( + fromHeight: Long, + limit: Long + ) = error("Intentionally not implemented in mocked FakeRustBackend implementation.") } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackendFixture.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackendFixture.kt index 8fcce255..ad2ab875 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackendFixture.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackendFixture.kt @@ -4,11 +4,10 @@ import cash.z.ecc.android.sdk.internal.model.JniBlockMeta import cash.z.ecc.android.sdk.model.ZcashNetwork internal class FakeRustBackendFixture { - - private val DEFAULT_NETWORK = ZcashNetwork.Testnet + private val defaultNetwork = ZcashNetwork.Testnet fun new( - network: ZcashNetwork = DEFAULT_NETWORK, + network: ZcashNetwork = defaultNetwork, metadata: MutableList = mutableListOf() ) = FakeRustBackend( networkId = network.id, diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/SaplingParamToolFixture.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/SaplingParamToolFixture.kt index b0ce90d6..ad0a517f 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/SaplingParamToolFixture.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/SaplingParamToolFixture.kt @@ -6,23 +6,23 @@ import cash.z.ecc.android.sdk.internal.SaplingParameters import java.io.File object SaplingParamToolFixture { - internal val PARAMS_DIRECTORY = SaplingParamsFixture.DESTINATION_DIRECTORY internal val PARAMS_LEGACY_DIRECTORY = SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY - internal val SAPLING_PARAMS_FILES = listOf( - SaplingParameters( - PARAMS_DIRECTORY, - SaplingParamTool.SPEND_PARAM_FILE_NAME, - SaplingParamTool.SPEND_PARAM_FILE_MAX_BYTES_SIZE, - SaplingParamTool.SPEND_PARAM_FILE_SHA1_HASH - ), - SaplingParameters( - PARAMS_DIRECTORY, - SaplingParamTool.OUTPUT_PARAM_FILE_NAME, - SaplingParamTool.OUTPUT_PARAM_FILE_MAX_BYTES_SIZE, - SaplingParamTool.OUTPUT_PARAM_FILE_SHA1_HASH + internal val SAPLING_PARAMS_FILES = + listOf( + SaplingParameters( + PARAMS_DIRECTORY, + SaplingParamTool.SPEND_PARAM_FILE_NAME, + SaplingParamTool.SPEND_PARAM_FILE_MAX_BYTES_SIZE, + SaplingParamTool.SPEND_PARAM_FILE_SHA1_HASH + ), + SaplingParameters( + PARAMS_DIRECTORY, + SaplingParamTool.OUTPUT_PARAM_FILE_NAME, + SaplingParamTool.OUTPUT_PARAM_FILE_MAX_BYTES_SIZE, + SaplingParamTool.OUTPUT_PARAM_FILE_SHA1_HASH + ) ) - ) internal fun new( saplingParamsFiles: List = SAPLING_PARAMS_FILES, diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/SaplingParamsFixture.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/SaplingParamsFixture.kt index 961ceb46..e2ce93b5 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/SaplingParamsFixture.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/SaplingParamsFixture.kt @@ -12,16 +12,17 @@ import kotlinx.coroutines.runBlocking import java.io.File object SaplingParamsFixture { - - internal val DESTINATION_DIRECTORY_LEGACY: File = File( - getAppContext().cacheDir, - SaplingParamTool.SAPLING_PARAMS_LEGACY_SUBDIRECTORY - ) + internal val DESTINATION_DIRECTORY_LEGACY: File = + File( + getAppContext().cacheDir, + SaplingParamTool.SAPLING_PARAMS_LEGACY_SUBDIRECTORY + ) internal val DESTINATION_DIRECTORY: File - get() = runBlocking { - Files.getZcashNoBackupSubdirectory(getAppContext()) - } + get() = + runBlocking { + Files.getZcashNoBackupSubdirectory(getAppContext()) + } internal const val SPEND_FILE_NAME = SaplingParamTool.SPEND_PARAM_FILE_NAME internal const val SPEND_FILE_MAX_SIZE = SaplingParamTool.SPEND_PARAM_FILE_MAX_BYTES_SIZE 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 e72eaf3a..4107663e 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 @@ -99,10 +99,10 @@ class SdkSynchronizer private constructor( val processor: CompactBlockProcessor, private val backend: TypesafeBackend ) : CloseableSynchronizer { - companion object { private sealed class InstanceState { object Active : InstanceState() + data class ShuttingDown(val job: Job) : InstanceState() } @@ -188,10 +188,11 @@ class SdkSynchronizer private constructor( override val transparentBalances = processor.transparentBalances.asStateFlow() override val transactions - get() = combine(processor.networkHeight, storage.allTransactions) { networkHeight, allTransactions -> - val latestBlockHeight = networkHeight ?: backend.getMaxScannedHeight() - allTransactions.map { TransactionOverview.new(it, latestBlockHeight) } - } + get() = + combine(processor.networkHeight, storage.allTransactions) { networkHeight, allTransactions -> + val latestBlockHeight = networkHeight ?: backend.getMaxScannedHeight() + allTransactions.map { TransactionOverview.new(it, latestBlockHeight) } + } override val network: ZcashNetwork get() = processor.network @@ -289,10 +290,11 @@ class SdkSynchronizer private constructor( // Note that stopping will continue asynchronously. Race conditions with starting a new synchronizer are // avoided with a delay during startup. - val shutdownJob = coroutineScope.launch { - Twig.debug { "Stopping synchronizer $synchronizerKey…" } - processor.stop() - } + val shutdownJob = + coroutineScope.launch { + Twig.debug { "Stopping synchronizer $synchronizerKey…" } + processor.stop() + } instances[synchronizerKey] = InstanceState.ShuttingDown(shutdownJob) @@ -306,7 +308,9 @@ class SdkSynchronizer private constructor( } override suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight = - processor.getNearestRewindHeight(height) + processor.getNearestRewindHeight( + height + ) override suspend fun rewindToNearestHeight(height: BlockHeight) { processor.rewindToNearestHeight(height) @@ -427,7 +431,10 @@ class SdkSynchronizer private constructor( } @Suppress("UNUSED_PARAMETER") - private fun onCriticalError(unused: CoroutineContext?, error: Throwable) { + private fun onCriticalError( + unused: CoroutineContext?, + error: Throwable + ) { Twig.error(error) { "Critical error occurred" } if (onCriticalErrorHandler == null) { @@ -471,7 +478,10 @@ class SdkSynchronizer private constructor( return onSetupErrorHandler?.invoke(error) == true } - private fun onChainError(errorHeight: BlockHeight, rewindHeight: BlockHeight) { + private fun onChainError( + errorHeight: BlockHeight, + rewindHeight: BlockHeight + ) { Twig.debug { "Chain error detected at height: $errorHeight. Rewinding to: $rewindHeight" } if (onChainErrorHandler == null) { Twig.debug { @@ -486,7 +496,10 @@ class SdkSynchronizer private constructor( /** * @param elapsedMillis the amount of time that passed since the last scan */ - private suspend fun onScanComplete(scannedRange: ClosedRange?, elapsedMillis: Long) { + private suspend fun onScanComplete( + scannedRange: ClosedRange?, + elapsedMillis: Long + ) { // We don't need to update anything if there have been no blocks // refresh anyway if: // - if it's the first time we finished scanning @@ -532,19 +545,28 @@ class SdkSynchronizer private constructor( * Returns the current Unified Address for this account. */ override suspend fun getUnifiedAddress(account: Account): String = - CompactBlockProcessor.getCurrentAddress(backend, account) + CompactBlockProcessor.getCurrentAddress( + backend, + account + ) /** * Returns the legacy Sapling address corresponding to the current Unified Address for this account. */ override suspend fun getSaplingAddress(account: Account): String = - CompactBlockProcessor.getLegacySaplingAddress(backend, account) + CompactBlockProcessor.getLegacySaplingAddress( + backend, + account + ) /** * Returns the legacy transparent address corresponding to the current Unified Address for this account. */ override suspend fun getTransparentAddress(account: Account): String = - CompactBlockProcessor.getTransparentAddress(backend, account) + CompactBlockProcessor.getTransparentAddress( + backend, + account + ) @Throws(TransactionEncoderException::class, TransactionSubmitException::class) override suspend fun sendToAddress( @@ -553,13 +575,14 @@ class SdkSynchronizer private constructor( toAddress: String, memo: String ): Long { - val encodedTx = txManager.encode( - usk, - amount, - TransactionRecipient.Address(toAddress), - memo, - usk.account - ) + val encodedTx = + txManager.encode( + usk, + amount, + TransactionRecipient.Address(toAddress), + memo, + usk.account + ) if (txManager.submit(encodedTx)) { return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!! @@ -577,13 +600,14 @@ class SdkSynchronizer private constructor( val tAddr = CompactBlockProcessor.getTransparentAddress(backend, usk.account) val tBalance = processor.getUtxoCacheBalance(tAddr) - val encodedTx = txManager.encode( - usk, - tBalance.available, - TransactionRecipient.Account(usk.account), - memo, - usk.account - ) + val encodedTx = + txManager.encode( + usk, + tBalance.available, + TransactionRecipient.Account(usk.account), + memo, + usk.account + ) if (txManager.submit(encodedTx)) { return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!! @@ -592,7 +616,10 @@ class SdkSynchronizer private constructor( } } - override suspend fun refreshUtxos(account: Account, since: BlockHeight): Int { + override suspend fun refreshUtxos( + account: Account, + since: BlockHeight + ): Int { return processor.refreshUtxos(account, since) } @@ -600,14 +627,11 @@ class SdkSynchronizer private constructor( return processor.getUtxoCacheBalance(tAddr) } - override suspend fun isValidShieldedAddr(address: String) = - txManager.isValidShieldedAddress(address) + override suspend fun isValidShieldedAddr(address: String) = txManager.isValidShieldedAddress(address) - override suspend fun isValidTransparentAddr(address: String) = - txManager.isValidTransparentAddress(address) + override suspend fun isValidTransparentAddr(address: String) = txManager.isValidTransparentAddress(address) - override suspend fun isValidUnifiedAddr(address: String) = - txManager.isValidUnifiedAddress(address) + override suspend fun isValidUnifiedAddr(address: String) = txManager.isValidUnifiedAddress(address) override suspend fun validateAddress(address: String): AddressType { @Suppress("TooGenericExceptionCaught") @@ -621,7 +645,9 @@ class SdkSynchronizer private constructor( } else { AddressType.Invalid("Not a Zcash address") } - } catch (@Suppress("TooGenericExceptionCaught") error: Throwable) { + } catch ( + @Suppress("TooGenericExceptionCaught") error: Throwable + ) { AddressType.Invalid(error.message ?: "Invalid") } } @@ -629,28 +655,30 @@ class SdkSynchronizer private constructor( override suspend fun validateConsensusBranch(): ConsensusMatchType { val serverBranchId = tryNull { processor.downloader.getServerInfo()?.consensusBranchId } - val currentChainTip = when ( - val response = - processor.downloader.getLatestBlockHeight() - ) { - is Response.Success -> { - Twig.info { "Chain tip for validate consensus branch action fetched: ${response.result.value}" } - runCatching { response.result.toBlockHeight(network) }.getOrNull() - } - is Response.Failure -> { - Twig.error { - "Chain tip fetch failed for validate consensus branch action with:" + - " ${response.toThrowable()}" + val currentChainTip = + when ( + val response = + processor.downloader.getLatestBlockHeight() + ) { + is Response.Success -> { + Twig.info { "Chain tip for validate consensus branch action fetched: ${response.result.value}" } + runCatching { response.result.toBlockHeight(network) }.getOrNull() + } + is Response.Failure -> { + Twig.error { + "Chain tip fetch failed for validate consensus branch action with:" + + " ${response.toThrowable()}" + } + null } - null } - } - val sdkBranchId = currentChainTip?.let { - tryNull { - (txManager as OutboundTransactionManagerImpl).encoder.getConsensusBranchId(currentChainTip) + val sdkBranchId = + currentChainTip?.let { + tryNull { + (txManager as OutboundTransactionManagerImpl).encoder.getConsensusBranchId(currentChainTip) + } } - } return ConsensusMatchType( sdkBranchId?.let { ConsensusBranchId.fromId(it) }, @@ -682,7 +710,6 @@ class SdkSynchronizer private constructor( * See the helper methods for generating default values. */ internal object DefaultSynchronizerFactory { - internal suspend fun defaultBackend( network: ZcashNetwork, alias: String, @@ -733,8 +760,10 @@ internal object DefaultSynchronizerFactory { backend ) - fun defaultService(context: Context, lightWalletEndpoint: LightWalletEndpoint): LightWalletClient = - LightWalletClient.new(context, lightWalletEndpoint) + fun defaultService( + context: Context, + lightWalletEndpoint: LightWalletEndpoint + ): LightWalletClient = LightWalletClient.new(context, lightWalletEndpoint) internal fun defaultEncoder( backend: TypesafeBackend, @@ -762,12 +791,13 @@ internal object DefaultSynchronizerFactory { downloader: CompactBlockDownloader, repository: DerivedDataRepository, birthdayHeight: BlockHeight - ): CompactBlockProcessor = CompactBlockProcessor( - downloader = downloader, - repository = repository, - backend = backend, - minimumHeight = birthdayHeight - ) + ): CompactBlockProcessor = + CompactBlockProcessor( + downloader = downloader, + repository = repository, + backend = backend, + minimumHeight = birthdayHeight + ) } internal data class SynchronizerKey(val zcashNetwork: ZcashNetwork, val alias: String) 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 dcbf2945..bc28121f 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 @@ -1,6 +1,9 @@ package cash.z.ecc.android.sdk import android.content.Context +import cash.z.ecc.android.sdk.WalletInitMode.ExistingWallet +import cash.z.ecc.android.sdk.WalletInitMode.NewWallet +import cash.z.ecc.android.sdk.WalletInitMode.RestoreWallet import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor import cash.z.ecc.android.sdk.exception.InitializeException import cash.z.ecc.android.sdk.ext.ZcashSdk @@ -30,7 +33,6 @@ import java.io.Closeable @Suppress("TooManyFunctions") interface Synchronizer { - // Status /** @@ -105,6 +107,7 @@ interface Synchronizer { // Operations // + @Suppress("ktlint:standard:no-consecutive-comments") /** * Adds the next available account-level spend authority, given the current set of * [ZIP 316](https://zips.z.cash/zip-0316) account identifiers known, to the wallet @@ -128,9 +131,10 @@ interface Synchronizer { * * @return the newly created ZIP 316 account identifier, along with the binary * encoding of the `UnifiedSpendingKey` for the newly created account. - */ - // This is not yet ready to be a public API - // suspend fun createAccount(seed: ByteArray): UnifiedSpendingKey + * + * This is not yet ready to be a public API! + * suspend fun createAccount(seed: ByteArray): UnifiedSpendingKey + **/ /** * Gets the current unified address for the given account. @@ -425,7 +429,6 @@ interface Synchronizer { } companion object { - /** * Primary method that SDK clients will use to construct a synchronizer. * @@ -459,8 +462,7 @@ interface Synchronizer { * @throws IllegalStateException If multiple instances of synchronizer with the same network+alias are * active at the same time. Call `close` to finish one synchronizer before starting another one with the same * network+alias. - */ - /* + * * If customized initialization is required (e.g. for dependency injection or testing), see * [DefaultSynchronizerFactory]. */ @@ -480,22 +482,24 @@ interface Synchronizer { val saplingParamTool = SaplingParamTool.new(applicationContext) - val loadedCheckpoint = CheckpointTool.loadNearest( - applicationContext, - zcashNetwork, - birthday ?: zcashNetwork.saplingActivationHeight - ) + val loadedCheckpoint = + CheckpointTool.loadNearest( + applicationContext, + zcashNetwork, + birthday ?: zcashNetwork.saplingActivationHeight + ) val coordinator = DatabaseCoordinator.getInstance(context) // The pending transaction database no longer exists, so we can delete the file coordinator.deletePendingTransactionDatabase(zcashNetwork, alias) - val backend = DefaultSynchronizerFactory.defaultBackend( - zcashNetwork, - alias, - saplingParamTool, - coordinator - ) + val backend = + DefaultSynchronizerFactory.defaultBackend( + zcashNetwork, + alias, + saplingParamTool, + coordinator + ) val blockStore = DefaultSynchronizerFactory @@ -504,46 +508,52 @@ interface Synchronizer { val service = DefaultSynchronizerFactory.defaultService(applicationContext, lightWalletEndpoint) val downloader = DefaultSynchronizerFactory.defaultDownloader(service, blockStore) - val chainTip = when (walletInitMode) { - is WalletInitMode.RestoreWallet -> { - when (val response = downloader.getLatestBlockHeight()) { - is Response.Success -> { - Twig.info { "Chain tip for recovery until param fetched: ${response.result.value}" } - runCatching { response.result.toBlockHeight(zcashNetwork) }.getOrNull() - } - is Response.Failure -> { - Twig.error { "Chain tip fetch for recovery until failed with: ${response.toThrowable()}" } - null + val chainTip = + when (walletInitMode) { + is WalletInitMode.RestoreWallet -> { + when (val response = downloader.getLatestBlockHeight()) { + is Response.Success -> { + Twig.info { "Chain tip for recovery until param fetched: ${response.result.value}" } + runCatching { response.result.toBlockHeight(zcashNetwork) }.getOrNull() + } + is Response.Failure -> { + Twig.error { + "Chain tip fetch for recovery until failed with: ${response.toThrowable()}" + } + null + } } } + else -> { + null + } } - else -> { - null - } - } - val repository = DefaultSynchronizerFactory.defaultDerivedDataRepository( - context = applicationContext, - rustBackend = backend, - databaseFile = coordinator.dataDbFile(zcashNetwork, alias), - zcashNetwork = zcashNetwork, - checkpoint = loadedCheckpoint, - seed = seed, - numberOfAccounts = Derivation.DEFAULT_NUMBER_OF_ACCOUNTS, - recoverUntil = chainTip, - ) + val repository = + DefaultSynchronizerFactory.defaultDerivedDataRepository( + context = applicationContext, + rustBackend = backend, + databaseFile = coordinator.dataDbFile(zcashNetwork, alias), + zcashNetwork = zcashNetwork, + checkpoint = loadedCheckpoint, + seed = seed, + numberOfAccounts = Derivation.DEFAULT_NUMBER_OF_ACCOUNTS, + recoverUntil = chainTip, + ) val encoder = DefaultSynchronizerFactory.defaultEncoder(backend, saplingParamTool, repository) - val txManager = DefaultSynchronizerFactory.defaultTxManager( - encoder, - service - ) - val processor = DefaultSynchronizerFactory.defaultProcessor( - backend = backend, - downloader = downloader, - repository = repository, - birthdayHeight = birthday ?: zcashNetwork.saplingActivationHeight - ) + val txManager = + DefaultSynchronizerFactory.defaultTxManager( + encoder, + service + ) + val processor = + DefaultSynchronizerFactory.defaultProcessor( + backend = backend, + downloader = downloader, + repository = repository, + birthdayHeight = birthday ?: zcashNetwork.saplingActivationHeight + ) return SdkSynchronizer.new( zcashNetwork = zcashNetwork, @@ -571,9 +581,10 @@ interface Synchronizer { seed: ByteArray?, birthday: BlockHeight?, walletInitMode: WalletInitMode - ): CloseableSynchronizer = runBlocking { - new(context, zcashNetwork, alias, lightWalletEndpoint, seed, birthday, walletInitMode) - } + ): CloseableSynchronizer = + runBlocking { + new(context, zcashNetwork, alias, lightWalletEndpoint, seed, birthday, walletInitMode) + } /** * Delete the databases associated with this wallet. This removes all compact blocks and @@ -610,7 +621,9 @@ interface Synchronizer { */ sealed class WalletInitMode { data object NewWallet : WalletInitMode() + data object RestoreWallet : WalletInitMode() + data object ExistingWallet : WalletInitMode() } 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 5649c8e1..031335f3 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 @@ -140,12 +140,13 @@ class CompactBlockProcessor internal constructor( */ val network = backend.network - private val lowerBoundHeight: BlockHeight = BlockHeight( - max( - network.saplingActivationHeight.value, - minimumHeight.value - MAX_REORG_SIZE + private val lowerBoundHeight: BlockHeight = + BlockHeight( + max( + network.saplingActivationHeight.value, + minimumHeight.value - MAX_REORG_SIZE + ) ) - ) private val _state: MutableStateFlow = MutableStateFlow(State.Initialized) private val _progress = MutableStateFlow(PercentDecimal.ZERO_PERCENT) @@ -212,10 +213,11 @@ class CompactBlockProcessor internal constructor( // lastKnownHeight is only used for error reporting. deleteAllBlockFiles( downloader = downloader, - lastKnownHeight = when (val result = getMaxScannedHeight(backend)) { - is GetMaxScannedHeightResult.Success -> result.height - else -> null - } + lastKnownHeight = + when (val result = getMaxScannedHeight(backend)) { + is GetMaxScannedHeightResult.Success -> result.height + else -> null + } ) // Download note commitment tree data from lightwalletd to decide if we communicate with linear @@ -235,55 +237,58 @@ class CompactBlockProcessor internal constructor( onErrorListener = ::onProcessorError, maxDelayMillis = MAX_BACKOFF_INTERVAL ) { - val result = processingMutex.withLockLogged("processNewBlocks") { - when (subTreeRootResult) { - is GetSubtreeRootsResult.SpendBeforeSync -> { - // Pass the commitment tree data to the database - when ( - val result = putSaplingSubtreeRoots( + val result = + processingMutex.withLockLogged("processNewBlocks") { + when (subTreeRootResult) { + is GetSubtreeRootsResult.SpendBeforeSync -> { + // Pass the commitment tree data to the database + when ( + val result = + putSaplingSubtreeRoots( + backend = backend, + startIndex = 0, + subTreeRootList = + (subTreeRootResult as GetSubtreeRootsResult.SpendBeforeSync) + .subTreeRootList, + lastValidHeight = lowerBoundHeight + ) + ) { + PutSaplingSubtreeRootsResult.Success -> { + // Let's continue with the next step + } + is PutSaplingSubtreeRootsResult.Failure -> { + BlockProcessingResult.SyncFailure(result.failedAtHeight, result.exception) + } + } + processNewBlocksInSbSOrder( backend = backend, - startIndex = 0, - subTreeRootList = (subTreeRootResult as GetSubtreeRootsResult.SpendBeforeSync) - .subTreeRootList, - lastValidHeight = lowerBoundHeight + downloader = downloader, + repository = repository, + network = network, + lastValidHeight = lowerBoundHeight, + firstUnenhancedHeight = _processorInfo.value.firstUnenhancedHeight ) - ) { - PutSaplingSubtreeRootsResult.Success -> { - // Let's continue with the next step - } - is PutSaplingSubtreeRootsResult.Failure -> { - BlockProcessingResult.SyncFailure(result.failedAtHeight, result.exception) - } } - processNewBlocksInSbSOrder( - backend = backend, - downloader = downloader, - repository = repository, - network = network, - lastValidHeight = lowerBoundHeight, - firstUnenhancedHeight = _processorInfo.value.firstUnenhancedHeight - ) - } - is GetSubtreeRootsResult.OtherFailure, GetSubtreeRootsResult.Linear -> { - // This is caused by an empty response result or another unsupported error. - // Although the spend-before-sync synchronization algorithm is not supported, we can get - // the entire block range as we previously did for the linear sync type. - processNewBlocksInSbSOrder( - backend = backend, - downloader = downloader, - repository = repository, - network = network, - lastValidHeight = lowerBoundHeight, - firstUnenhancedHeight = _processorInfo.value.firstUnenhancedHeight - ) - } - GetSubtreeRootsResult.FailureConnection -> { - // SubtreeRoot fetching retry - subTreeRootResult = getSubtreeRoots(downloader, network) - BlockProcessingResult.Reconnecting + is GetSubtreeRootsResult.OtherFailure, GetSubtreeRootsResult.Linear -> { + // This is caused by an empty response result or another unsupported error. + // Although the spend-before-sync synchronization algorithm is not supported, we can get + // the entire block range as we previously did for the linear sync type. + processNewBlocksInSbSOrder( + backend = backend, + downloader = downloader, + repository = repository, + network = network, + lastValidHeight = lowerBoundHeight, + firstUnenhancedHeight = _processorInfo.value.firstUnenhancedHeight + ) + } + GetSubtreeRootsResult.FailureConnection -> { + // SubtreeRoot fetching retry + subTreeRootResult = getSubtreeRoots(downloader, network) + BlockProcessingResult.Reconnecting + } } } - } // Immediately process again after failures in order to download new blocks right away when (result) { @@ -310,11 +315,12 @@ class CompactBlockProcessor internal constructor( BlockProcessingResult.NoBlocksToProcess -> { setState(State.Synced(_processorInfo.value.overallSyncRange)) val noWorkDone = _processorInfo.value.overallSyncRange?.isEmpty() ?: true - val summary = if (noWorkDone) { - "Nothing to process: no new blocks to sync" - } else { - "Done processing blocks" - } + val summary = + if (noWorkDone) { + "Nothing to process: no new blocks to sync" + } else { + "Done processing blocks" + } resetErrorCounters() @@ -372,10 +378,14 @@ class CompactBlockProcessor internal constructor( * @return `True` when the retry limit reached and the error thrown, false when error counter increment and no * error thrown yet */ - suspend fun checkErrorAndFail(failedHeight: BlockHeight?, failCause: Throwable): Boolean { + suspend fun checkErrorAndFail( + failedHeight: BlockHeight?, + failCause: Throwable + ): Boolean { if (consecutiveBlockProcessingErrors.get() >= RETRIES) { - val errorMessage = "ERROR: unable to resolve the error at height $failedHeight after " + - "${consecutiveBlockProcessingErrors.get()} correction attempts!" + val errorMessage = + "ERROR: unable to resolve the error at height $failedHeight after " + + "${consecutiveBlockProcessingErrors.get()} correction attempts!" fail(CompactBlockProcessorException.FailedSynchronizationException(errorMessage, failCause)) return true } @@ -404,6 +414,7 @@ class CompactBlockProcessor internal constructor( // TODO [#1137]: Refactor processNewBlocksInSbSOrder // TODO [#1137]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1137 + /** * This function process the missing blocks in non-linear order with Spend-before-Sync algorithm. */ @@ -422,12 +433,13 @@ class CompactBlockProcessor internal constructor( // This step covers these operations fetchLatestBlockHeight, updateChainTip, suggestScanRanges, updateRange, // and shouldVerifySuggestedScanRanges - val preparationResult = runSbSSyncingPreparation( - backend = backend, - downloader = downloader, - network = network, - lastValidHeight = lastValidHeight - ) + val preparationResult = + runSbSSyncingPreparation( + backend = backend, + downloader = downloader, + network = network, + lastValidHeight = lastValidHeight + ) when (preparationResult) { is SbSPreparationResult.ProcessFailure -> { return preparationResult.toBlockProcessingResult() @@ -539,16 +551,19 @@ class CompactBlockProcessor internal constructor( } // Process the rest of ranges - val scanRanges = when (suggestedRangesResult) { - is SuggestScanRangesResult.Success -> { suggestedRangesResult.ranges } - is SuggestScanRangesResult.Failure -> { - Twig.error { "Process suggested scan ranges failure: ${suggestedRangesResult.exception}" } - return BlockProcessingResult.SyncFailure( - suggestedRangesResult.failedAtHeight, - suggestedRangesResult.exception - ) + val scanRanges = + when (suggestedRangesResult) { + is SuggestScanRangesResult.Success -> { + suggestedRangesResult.ranges + } + is SuggestScanRangesResult.Failure -> { + Twig.error { "Process suggested scan ranges failure: ${suggestedRangesResult.exception}" } + return BlockProcessingResult.SyncFailure( + suggestedRangesResult.failedAtHeight, + suggestedRangesResult.exception + ) + } } - } scanRanges.forEach { scanRange -> Twig.debug { "Start processing the range: $scanRange" } @@ -655,13 +670,14 @@ class CompactBlockProcessor internal constructor( lastValidHeight: BlockHeight ): SbSPreparationResult { // Download chain tip metadata from lightwalletd - val chainTip = fetchLatestBlockHeight( - downloader = downloader, - network = network - ) ?: let { - Twig.warn { "Disconnection detected. Attempting to reconnect." } - return SbSPreparationResult.ConnectionFailure - } + val chainTip = + fetchLatestBlockHeight( + downloader = downloader, + network = network + ) ?: let { + Twig.warn { "Disconnection detected. Attempting to reconnect." } + return SbSPreparationResult.ConnectionFailure + } // Notify the underlying rust layer about the updated chain tip when ( @@ -685,22 +701,24 @@ class CompactBlockProcessor internal constructor( // TODO [#1211]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1211 // Get the suggested scan ranges from the wallet database - val suggestedRangesResult = suggestScanRanges( - backend, - lastValidHeight - ) - val updateRangeResult = when (suggestedRangesResult) { - is SuggestScanRangesResult.Success -> { - updateRange(suggestedRangesResult.ranges) + val suggestedRangesResult = + suggestScanRanges( + backend, + lastValidHeight + ) + val updateRangeResult = + when (suggestedRangesResult) { + is SuggestScanRangesResult.Success -> { + updateRange(suggestedRangesResult.ranges) + } + is SuggestScanRangesResult.Failure -> { + Twig.error { "Process suggested scan ranges failure: ${suggestedRangesResult.exception}" } + return SbSPreparationResult.ProcessFailure( + suggestedRangesResult.failedAtHeight, + suggestedRangesResult.exception + ) + } } - is SuggestScanRangesResult.Failure -> { - Twig.error { "Process suggested scan ranges failure: ${suggestedRangesResult.exception}" } - return SbSPreparationResult.ProcessFailure( - suggestedRangesResult.failedAtHeight, - suggestedRangesResult.exception - ) - } - } if (!updateRangeResult) { Twig.warn { "Disconnection detected. Attempting to reconnect." } @@ -775,10 +793,15 @@ class CompactBlockProcessor internal constructor( sealed class BlockProcessingResult { object NoBlocksToProcess : BlockProcessingResult() + object Success : BlockProcessingResult() + object Reconnecting : BlockProcessingResult() + object RestartSynchronization : BlockProcessingResult() + data class SyncFailure(val failedAtHeight: BlockHeight?, val error: Throwable) : BlockProcessingResult() + data class ContinuityError(val failedAtHeight: BlockHeight?, val error: Throwable) : BlockProcessingResult() } @@ -799,22 +822,23 @@ class CompactBlockProcessor internal constructor( val firstUnenhancedHeight = getFirstUnenhancedHeight(repository) // The overall sync range computation - val syncRange = if (ranges.isNotEmpty()) { - var resultRange = ranges[0].range.start..ranges[0].range.endInclusive - ranges.forEach { nextRange -> - if (nextRange.range.start < resultRange.start) { - resultRange = nextRange.range.start..resultRange.endInclusive - } - if (nextRange.range.endInclusive > resultRange.endInclusive) { - resultRange = resultRange.start..nextRange.range.endInclusive + val syncRange = + if (ranges.isNotEmpty()) { + var resultRange = ranges[0].range.start..ranges[0].range.endInclusive + ranges.forEach { nextRange -> + if (nextRange.range.start < resultRange.start) { + resultRange = nextRange.range.start..resultRange.endInclusive + } + if (nextRange.range.endInclusive > resultRange.endInclusive) { + resultRange = resultRange.start..nextRange.range.endInclusive + } } + resultRange + } else { + // Empty ranges most likely means that the sync is done and the Rust layer replied with an empty + // suggested ranges + null } - resultRange - } else { - // Empty ranges most likely means that the sync is done and the Rust layer replied with an empty suggested - // ranges - null - } setProcessorInfo( networkBlockHeight = networkBlockHeight, @@ -825,51 +849,54 @@ class CompactBlockProcessor internal constructor( return true } - /** - * Confirm that the wallet data is properly setup for use. - */ // TODO [#1127]: Refactor CompactBlockProcessor.verifySetup // TODO [#1127]: Need to refactor this to be less ugly and more testable // TODO [#1127]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1127 + + /** + * Confirm that the wallet data is properly setup for use. + */ @Suppress("NestedBlockDepth") private suspend fun verifySetup() { - val error = if (repository.getAccountCount() == 0) { - CompactBlockProcessorException.NoAccount - } else { - // verify that the server is correct + val error = + if (repository.getAccountCount() == 0) { + CompactBlockProcessorException.NoAccount + } else { + // verify that the server is correct - // How do we handle network connection issues? + // How do we handle network connection issues? - downloader.getServerInfo()?.let { info -> - val serverBlockHeight = - runCatching { info.blockHeightUnsafe.toBlockHeight(network) }.getOrNull() + downloader.getServerInfo()?.let { info -> + val serverBlockHeight = + runCatching { info.blockHeightUnsafe.toBlockHeight(network) }.getOrNull() - if (null == serverBlockHeight) { - // Note: we could better signal network connection issue - CompactBlockProcessorException.BadBlockHeight(info.blockHeightUnsafe) - } else { - val clientBranch = "%x".format( - Locale.ROOT, - backend.getBranchIdForHeight(serverBlockHeight) - ) - val network = backend.network.networkName - - if (!clientBranch.equals(info.consensusBranchId, true)) { - MismatchedNetwork( - clientNetwork = network, - serverNetwork = info.chainName - ) - } else if (!info.matchingNetwork(network)) { - MismatchedNetwork( - clientNetwork = network, - serverNetwork = info.chainName - ) + if (null == serverBlockHeight) { + // Note: we could better signal network connection issue + CompactBlockProcessorException.BadBlockHeight(info.blockHeightUnsafe) } else { - null + val clientBranch = + "%x".format( + Locale.ROOT, + backend.getBranchIdForHeight(serverBlockHeight) + ) + val network = backend.network.networkName + + if (!clientBranch.equals(info.consensusBranchId, true)) { + MismatchedNetwork( + clientNetwork = network, + serverNetwork = info.chainName + ) + } else if (!info.matchingNetwork(network)) { + MismatchedNetwork( + clientNetwork = network, + serverNetwork = info.chainName + ) + } else { + null + } } } } - } if (error != null) { Twig.debug { "Validating setup prior to scanning . . . ISSUE FOUND! - ${error.javaClass.simpleName}" } @@ -896,7 +923,10 @@ class CompactBlockProcessor internal constructor( var failedUtxoFetches = 0 @Suppress("MagicNumber", "LongMethod") - internal suspend fun refreshUtxos(account: Account, startHeight: BlockHeight): Int { + internal suspend fun refreshUtxos( + account: Account, + startHeight: BlockHeight + ): Int { Twig.debug { "Checking for UTXOs above height $startHeight" } var count = 0 // TODO [#683]: cleanup the way that we prevent this from running excessively @@ -1088,9 +1118,10 @@ class CompactBlockProcessor internal constructor( when (val response = downloader.getLatestBlockHeight()) { is Response.Success -> { Twig.debug { "Latest block height fetched successfully with value: ${response.result.value}" } - latestBlockHeight = runCatching { - response.result.toBlockHeight(network) - }.getOrNull() + latestBlockHeight = + runCatching { + response.result.toBlockHeight(network) + }.getOrNull() } is Response.Failure -> { Twig.error { "Fetching latest block height failed with: ${response.toThrowable()}" } @@ -1135,11 +1166,12 @@ class CompactBlockProcessor internal constructor( } } is Response.Failure -> { - val error = LightWalletException.GetSubtreeRootsException( - response.code, - response.description, - response.toThrowable() - ) + val error = + LightWalletException.GetSubtreeRootsException( + response.code, + response.description, + response.toThrowable() + ) if (response is Response.Failure.Server.Unavailable) { Twig.error { "Fetching SubtreeRoot failed due to server communication problem with " + @@ -1162,11 +1194,12 @@ class CompactBlockProcessor internal constructor( .map { SubtreeRoot.new(it, network) }.let { - result = if (it.isEmpty()) { - GetSubtreeRootsResult.Linear - } else { - GetSubtreeRootsResult.SpendBeforeSync(it) - } + result = + if (it.isEmpty()) { + GetSubtreeRootsResult.Linear + } else { + GetSubtreeRootsResult.SpendBeforeSync(it) + } } } return result @@ -1267,9 +1300,8 @@ class CompactBlockProcessor internal constructor( * @return VerifySuggestedScanRange */ @VisibleForTesting - internal fun shouldVerifySuggestedScanRanges( - suggestedRangesResult: SuggestScanRangesResult.Success - ): VerifySuggestedScanRange { + @Suppress("MaxLineLength") + internal fun shouldVerifySuggestedScanRanges(suggestedRangesResult: SuggestScanRangesResult.Success): VerifySuggestedScanRange { Twig.debug { "Check for Priority.Verify scan range result: ${suggestedRangesResult.ranges}" } return if (suggestedRangesResult.ranges.isEmpty()) { @@ -1338,144 +1370,150 @@ class CompactBlockProcessor internal constructor( syncRange: ClosedRange, withDownload: Boolean, enhanceStartHeight: BlockHeight? - ): Flow = flow { - if (syncRange.isEmpty()) { - Twig.debug { "No blocks to sync" } - emit( - BatchSyncProgress( - resultState = SyncingResult.AllSuccess - ) - ) - } else { - Twig.info { "Syncing blocks in range $syncRange" } - - val batches = getBatchedBlockList(syncRange, network) - - // Check for the last enhanced height and eventually set is as the beginning of the next enhancing range - var enhancingRange = if (enhanceStartHeight != null) { - BlockHeight(min(syncRange.start.value, enhanceStartHeight.value))..syncRange.start - } else { - syncRange.start..syncRange.start - } - - batches.asFlow().map { - Twig.debug { "Syncing process starts for batch: $it" } - - // Run downloading stage - SyncStageResult( - batch = it, - stageResult = if (withDownload) { - downloadBatchOfBlocks( - downloader = downloader, - batch = it - ) - } else { - SyncingResult.DownloadSuccess(null) - } - ) - }.buffer(1).map { downloadStageResult -> - Twig.debug { "Download stage done with result: $downloadStageResult" } - - if (downloadStageResult.stageResult !is SyncingResult.DownloadSuccess) { - // In case of any failure, we just propagate the result - downloadStageResult - } else { - // Enrich batch model with fetched blocks. It's useful for later blocks deletion - downloadStageResult.batch.blocks = downloadStageResult.stageResult.downloadedBlocks - - // Run scanning stage (which also validates the fetched blocks) - SyncStageResult( - downloadStageResult.batch, - scanBatchOfBlocks( - backend = backend, - batch = downloadStageResult.batch - ) - ) - } - }.map { scanResult -> - Twig.debug { "Scan stage done with result: $scanResult" } - - if (scanResult.stageResult != SyncingResult.ScanSuccess) { - scanResult - } else { - // Run deletion stage - SyncStageResult( - scanResult.batch, - deleteFilesOfBatchOfBlocks( - downloader = downloader, - batch = scanResult.batch - ) - ) - } - }.onEach { continuousResult -> - Twig.debug { "Deletion stage done with result: $continuousResult" } - - var resultState = if (continuousResult.stageResult == SyncingResult.DeleteSuccess) { - SyncingResult.AllSuccess - } else { - continuousResult.stageResult - } - + ): Flow = + flow { + if (syncRange.isEmpty()) { + Twig.debug { "No blocks to sync" } emit( BatchSyncProgress( - order = continuousResult.batch.order, - range = continuousResult.batch.range, - resultState = resultState + resultState = SyncingResult.AllSuccess ) ) + } else { + Twig.info { "Syncing blocks in range $syncRange" } - // Increment and compare the range for triggering the enhancing - enhancingRange = enhancingRange.start..continuousResult.batch.range.endInclusive + val batches = getBatchedBlockList(syncRange, network) - // Enhancing is run when the range is on or over its limit, or there is any failure - // from previous stages, or if the end of the sync range is reached. - if (enhancingRange.length() >= ENHANCE_BATCH_SIZE || - resultState != SyncingResult.AllSuccess || - continuousResult.batch.order == batches.size.toLong() - ) { - // Copy the range for use and reset for the next iteration - val currentEnhancingRange = enhancingRange - enhancingRange = enhancingRange.endInclusive..enhancingRange.endInclusive - enhanceTransactionDetails( - range = currentEnhancingRange, - repository = repository, - backend = backend, - downloader = downloader - ).collect { enhancingResult -> - Twig.info { "Enhancing result: $enhancingResult" } - resultState = when (enhancingResult) { - is SyncingResult.UpdateBirthday -> { - Twig.info { "Birthday height update reporting" } - enhancingResult + // Check for the last enhanced height and eventually set is as the beginning of the next + // enhancing range + var enhancingRange = + if (enhanceStartHeight != null) { + BlockHeight(min(syncRange.start.value, enhanceStartHeight.value))..syncRange.start + } else { + syncRange.start..syncRange.start + } + + batches.asFlow().map { + Twig.debug { "Syncing process starts for batch: $it" } + + // Run downloading stage + SyncStageResult( + batch = it, + stageResult = + if (withDownload) { + downloadBatchOfBlocks( + downloader = downloader, + batch = it + ) + } else { + SyncingResult.DownloadSuccess(null) } - is SyncingResult.EnhanceFailed -> { - Twig.error { "Enhancing failed for: $enhancingRange with $enhancingResult" } - enhancingResult - } - else -> { - // Transactions enhanced correctly. Let's continue with block processing. - enhancingResult - } - } - emit( - BatchSyncProgress( - order = continuousResult.batch.order, - range = continuousResult.batch.range, - resultState = resultState + ) + }.buffer(1).map { downloadStageResult -> + Twig.debug { "Download stage done with result: $downloadStageResult" } + + if (downloadStageResult.stageResult !is SyncingResult.DownloadSuccess) { + // In case of any failure, we just propagate the result + downloadStageResult + } else { + // Enrich batch model with fetched blocks. It's useful for later blocks deletion + downloadStageResult.batch.blocks = downloadStageResult.stageResult.downloadedBlocks + + // Run scanning stage (which also validates the fetched blocks) + SyncStageResult( + downloadStageResult.batch, + scanBatchOfBlocks( + backend = backend, + batch = downloadStageResult.batch ) ) } - } - Twig.info { - "All sync stages done for the batch ${continuousResult.batch.order}/${batches.size}:" + - " ${continuousResult.batch} with result state: $resultState" - } - }.takeWhile { batchProcessResult -> - batchProcessResult.stageResult == SyncingResult.DeleteSuccess || - batchProcessResult.stageResult == SyncingResult.UpdateBirthday - }.collect() + }.map { scanResult -> + Twig.debug { "Scan stage done with result: $scanResult" } + + if (scanResult.stageResult != SyncingResult.ScanSuccess) { + scanResult + } else { + // Run deletion stage + SyncStageResult( + scanResult.batch, + deleteFilesOfBatchOfBlocks( + downloader = downloader, + batch = scanResult.batch + ) + ) + } + }.onEach { continuousResult -> + Twig.debug { "Deletion stage done with result: $continuousResult" } + + var resultState = + if (continuousResult.stageResult == SyncingResult.DeleteSuccess) { + SyncingResult.AllSuccess + } else { + continuousResult.stageResult + } + + emit( + BatchSyncProgress( + order = continuousResult.batch.order, + range = continuousResult.batch.range, + resultState = resultState + ) + ) + + // Increment and compare the range for triggering the enhancing + enhancingRange = enhancingRange.start..continuousResult.batch.range.endInclusive + + // Enhancing is run when the range is on or over its limit, or there is any failure + // from previous stages, or if the end of the sync range is reached. + if (enhancingRange.length() >= ENHANCE_BATCH_SIZE || + resultState != SyncingResult.AllSuccess || + continuousResult.batch.order == batches.size.toLong() + ) { + // Copy the range for use and reset for the next iteration + val currentEnhancingRange = enhancingRange + enhancingRange = enhancingRange.endInclusive..enhancingRange.endInclusive + enhanceTransactionDetails( + range = currentEnhancingRange, + repository = repository, + backend = backend, + downloader = downloader + ).collect { enhancingResult -> + Twig.info { "Enhancing result: $enhancingResult" } + resultState = + when (enhancingResult) { + is SyncingResult.UpdateBirthday -> { + Twig.info { "Birthday height update reporting" } + enhancingResult + } + is SyncingResult.EnhanceFailed -> { + Twig.error { "Enhancing failed for: $enhancingRange with $enhancingResult" } + enhancingResult + } + else -> { + // Transactions enhanced correctly. Let's continue with block processing. + enhancingResult + } + } + emit( + BatchSyncProgress( + order = continuousResult.batch.order, + range = continuousResult.batch.range, + resultState = resultState + ) + ) + } + } + Twig.info { + "All sync stages done for the batch ${continuousResult.batch.order}/${batches.size}:" + + " ${continuousResult.batch} with result state: $resultState" + } + }.takeWhile { batchProcessResult -> + batchProcessResult.stageResult == SyncingResult.DeleteSuccess || + batchProcessResult.stageResult == SyncingResult.UpdateBirthday + }.collect() + } } - } /** * Returns count of batches of blocks across all ranges. It works the same when triggered from the Linear @@ -1520,13 +1558,14 @@ class CompactBlockProcessor internal constructor( var start = syncRange.start return buildList { for (index in 1..batchCount) { - val end = BlockHeight.new( - network, - min( - (syncRange.start.value + (index * SYNC_BATCH_SIZE)) - 1, - syncRange.endInclusive.value - ) - ) // subtract 1 on the first value because the range is inclusive + val end = + BlockHeight.new( + network, + min( + (syncRange.start.value + (index * SYNC_BATCH_SIZE)) - 1, + syncRange.endInclusive.value + ) + ) // subtract 1 on the first value because the range is inclusive add( BlockBatch( @@ -1579,7 +1618,10 @@ class CompactBlockProcessor internal constructor( } @VisibleForTesting - internal suspend fun scanBatchOfBlocks(batch: BlockBatch, backend: TypesafeBackend): SyncingResult { + internal suspend fun scanBatchOfBlocks( + batch: BlockBatch, + backend: TypesafeBackend + ): SyncingResult { return runCatching { backend.scanBlocks(batch.range.start, batch.range.length()) }.onSuccess { @@ -1592,7 +1634,8 @@ class CompactBlockProcessor internal constructor( // Check if the error is continuity type if (it.isScanContinuityError()) { SyncingResult.ContinuityError( - failedAtHeight = batch.range.start - 1, // To ensure we later rewind below the failed height + // To ensure we later rewind below the failed height + failedAtHeight = batch.range.start - 1, exception = CompactBlockProcessorException.FailedScanException(it) ) } else { @@ -1649,34 +1692,35 @@ class CompactBlockProcessor internal constructor( repository: DerivedDataRepository, backend: TypesafeBackend, downloader: CompactBlockDownloader - ): Flow = flow { - Twig.debug { "Enhancing transaction details for blocks $range" } + ): Flow = + flow { + Twig.debug { "Enhancing transaction details for blocks $range" } - val newTxs = repository.findNewTransactions(range) - if (newTxs.isEmpty()) { - Twig.debug { "No new transactions found in $range" } - } else { - Twig.debug { "Enhancing ${newTxs.size} transaction(s)!" } + val newTxs = repository.findNewTransactions(range) + if (newTxs.isEmpty()) { + Twig.debug { "No new transactions found in $range" } + } else { + Twig.debug { "Enhancing ${newTxs.size} transaction(s)!" } - // If the first transaction has been added - if (newTxs.size.toLong() == repository.getTransactionCount()) { - Twig.debug { "Encountered the first transaction. This changes the birthday height!" } - emit(SyncingResult.UpdateBirthday) - } + // If the first transaction has been added + if (newTxs.size.toLong() == repository.getTransactionCount()) { + Twig.debug { "Encountered the first transaction. This changes the birthday height!" } + emit(SyncingResult.UpdateBirthday) + } - newTxs.filter { it.minedHeight != null }.onEach { newTransaction -> - val trEnhanceResult = enhanceTransaction(newTransaction, backend, downloader) - if (trEnhanceResult is SyncingResult.EnhanceFailed) { - Twig.error { "Encountered transaction enhancing error: ${trEnhanceResult.exception}" } - emit(trEnhanceResult) - // We intentionally do not terminate the batch enhancing here, just reporting it + newTxs.filter { it.minedHeight != null }.onEach { newTransaction -> + val trEnhanceResult = enhanceTransaction(newTransaction, backend, downloader) + if (trEnhanceResult is SyncingResult.EnhanceFailed) { + Twig.error { "Encountered transaction enhancing error: ${trEnhanceResult.exception}" } + emit(trEnhanceResult) + // We intentionally do not terminate the batch enhancing here, just reporting it + } } } - } - Twig.debug { "Done enhancing transaction details" } - emit(SyncingResult.EnhanceSuccess) - } + Twig.debug { "Done enhancing transaction details" } + emit(SyncingResult.EnhanceSuccess) + } private suspend fun enhanceTransaction( transaction: DbTransactionOverview, @@ -1697,12 +1741,13 @@ class CompactBlockProcessor internal constructor( "Fetching transaction (txid:${transaction.txIdString()} block:${transaction .minedHeight})" } - val transactionData = fetchTransaction( - transactionId = transaction.txIdString(), - rawTransactionId = transaction.rawId.byteArray, - minedHeight = transaction.minedHeight, - downloader = downloader - ) + val transactionData = + fetchTransaction( + transactionId = transaction.txIdString(), + rawTransactionId = transaction.rawId.byteArray, + minedHeight = transaction.minedHeight, + downloader = downloader + ) // Decrypting and storing transaction is run just once, since we consider it more stable Twig.verbose { @@ -1807,46 +1852,52 @@ class CompactBlockProcessor internal constructor( * or repository is empty */ @VisibleForTesting - internal suspend fun getFirstUnenhancedHeight(repository: DerivedDataRepository) = - repository.firstUnenhancedHeight() + @Suppress("MaxLineLength") + internal suspend fun getFirstUnenhancedHeight(repository: DerivedDataRepository) = repository.firstUnenhancedHeight() /** * Get the height of the last block that was downloaded by this processor. * * @return the last downloaded height reported by the downloader. */ - internal suspend fun getLastDownloadedHeight(downloader: CompactBlockDownloader) = - downloader.getLastDownloadedHeight() + @Suppress("MaxLineLength") + internal suspend fun getLastDownloadedHeight(downloader: CompactBlockDownloader) = downloader.getLastDownloadedHeight() /** * Get the current unified address for the given wallet account. * * @return the current unified address of this account. */ - internal suspend fun getCurrentAddress(backend: TypesafeBackend, account: Account) = - backend.getCurrentAddress(account) + internal suspend fun getCurrentAddress( + backend: TypesafeBackend, + account: Account + ) = backend.getCurrentAddress(account) /** * Get the legacy Sapling address corresponding to the current unified address for the given wallet account. * * @return a Sapling address. */ - internal suspend fun getLegacySaplingAddress(backend: TypesafeBackend, account: Account) = - backend.getSaplingReceiver( - backend.getCurrentAddress(account) - ) - ?: throw InitializeException.MissingAddressException("legacy Sapling") + internal suspend fun getLegacySaplingAddress( + backend: TypesafeBackend, + account: Account + ) = backend.getSaplingReceiver( + backend.getCurrentAddress(account) + ) + ?: throw InitializeException.MissingAddressException("legacy Sapling") /** * Get the legacy transparent address corresponding to the current unified address for the given wallet account. * * @return a transparent address. */ - internal suspend fun getTransparentAddress(backend: TypesafeBackend, account: Account) = - backend.getTransparentReceiver( - backend.getCurrentAddress(account) - ) - ?: throw InitializeException.MissingAddressException("legacy transparent") + internal suspend fun getTransparentAddress( + backend: TypesafeBackend, + account: Account + ) = backend.getTransparentReceiver( + backend.getCurrentAddress(account) + ) + ?: throw InitializeException.MissingAddressException("legacy transparent") } /** @@ -1868,11 +1919,12 @@ class CompactBlockProcessor internal constructor( firstUnenhancedHeight: BlockHeight? = _processorInfo.value.firstUnenhancedHeight, ) { _networkHeight.value = networkBlockHeight - _processorInfo.value = ProcessorInfo( - networkBlockHeight = networkBlockHeight, - overallSyncRange = overallSyncRange, - firstUnenhancedHeight = firstUnenhancedHeight - ) + _processorInfo.value = + ProcessorInfo( + networkBlockHeight = networkBlockHeight, + overallSyncRange = overallSyncRange, + firstUnenhancedHeight = firstUnenhancedHeight + ) } /** @@ -1921,25 +1973,28 @@ class CompactBlockProcessor internal constructor( * Rewind back two weeks worth of blocks. The final amount of rewinded blocks depends on [rewindToNearestHeight]. */ suspend fun quickRewind(): Boolean { - val height = when (val result = getMaxScannedHeight(backend)) { - is GetMaxScannedHeightResult.Success -> result.height - else -> return false - } + val height = + when (val result = getMaxScannedHeight(backend)) { + is GetMaxScannedHeightResult.Success -> result.height + else -> return false + } val blocksPer14Days = 14.days.inWholeMilliseconds / ZcashSdk.BLOCK_INTERVAL_MILLIS.toInt() - val twoWeeksBack = BlockHeight.new( - network, - (height.value - blocksPer14Days).coerceAtLeast(lowerBoundHeight.value) - ) + val twoWeeksBack = + BlockHeight.new( + network, + (height.value - blocksPer14Days).coerceAtLeast(lowerBoundHeight.value) + ) return rewindToNearestHeight(twoWeeksBack) } @Suppress("LongMethod") suspend fun rewindToNearestHeight(height: BlockHeight): Boolean { processingMutex.withLockLogged("rewindToHeight") { - val lastLocalBlock = when (val result = getMaxScannedHeight(backend)) { - is GetMaxScannedHeightResult.Success -> result.height - else -> return false - } + val lastLocalBlock = + when (val result = getMaxScannedHeight(backend)) { + is GetMaxScannedHeightResult.Success -> result.height + else -> return false + } val targetHeight = getNearestRewindHeight(height) Twig.debug { @@ -1971,7 +2026,10 @@ class CompactBlockProcessor internal constructor( } /** insightful function for debugging these critical errors */ - private suspend fun printValidationErrorInfo(errorHeight: BlockHeight?, count: Int = 11) { + private suspend fun printValidationErrorInfo( + errorHeight: BlockHeight?, + count: Int = 11 + ) { // Note: blocks are public information so it's okay to print them but, still, let's not unless we're // debugging something if (!BuildConfig.DEBUG) { @@ -2030,11 +2088,12 @@ class CompactBlockProcessor internal constructor( * @return the duration in milliseconds to the next poll attempt */ private fun calculatePollInterval(fastIntervalDesired: Boolean = false): Duration { - val interval = if (fastIntervalDesired) { - POLL_INTERVAL_SHORT - } else { - POLL_INTERVAL - } + val interval = + if (fastIntervalDesired) { + POLL_INTERVAL_SHORT + } else { + POLL_INTERVAL + } val now = System.currentTimeMillis() val deltaToNextInterval = interval - (now + interval).rem(interval) return deltaToNextInterval.toDuration(DurationUnit.MILLISECONDS) @@ -2069,10 +2128,11 @@ class CompactBlockProcessor internal constructor( val accountBalance = walletSummary?.accountBalances?.get(account) // `None` means that the caller has not yet called `updateChainTip` on a // brand-new wallet, so we can assume the balance is zero. - val saplingBalance = accountBalance?.sapling ?: WalletBalance( - total = Zatoshi(0L), - available = Zatoshi(0L) - ) + val saplingBalance = + accountBalance?.sapling ?: WalletBalance( + total = Zatoshi(0L), + available = Zatoshi(0L) + ) Twig.info { "Found total balance: ${saplingBalance.total}" } Twig.info { "Found available balance: ${saplingBalance.available}" } saplingBalance @@ -2083,8 +2143,7 @@ class CompactBlockProcessor internal constructor( } } - suspend fun getUtxoCacheBalance(address: String): WalletBalance = - backend.getDownloadedUtxoBalance(address) + suspend fun getUtxoCacheBalance(address: String): WalletBalance = backend.getDownloadedUtxoBalance(address) /** * Sealed class representing the various states of this processor. @@ -2173,7 +2232,10 @@ class CompactBlockProcessor internal constructor( /** * Log the mutex in great detail just in case we need it for troubleshooting deadlock. */ - private suspend inline fun Mutex.withLockLogged(name: String, block: () -> T): T { + private suspend inline fun Mutex.withLockLogged( + name: String, + block: () -> T + ): T { Twig.debug { "$name MUTEX: acquiring lock..." } this.withLock { Twig.debug { "$name MUTEX: ...lock acquired!" } @@ -2185,12 +2247,13 @@ class CompactBlockProcessor internal constructor( } private fun LightWalletEndpointInfoUnsafe.matchingNetwork(network: String): Boolean { - fun String.toId() = lowercase(Locale.ROOT).run { - when { - contains("main") -> "mainnet" - contains("test") -> "testnet" - else -> this + fun String.toId() = + lowercase(Locale.ROOT).run { + when { + contains("main") -> "mainnet" + contains("test") -> "testnet" + else -> this + } } - } return chainName.toId() == network.toId() } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetMaxScannedHeightResult.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetMaxScannedHeightResult.kt index 2eba89b3..b381eb66 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetMaxScannedHeightResult.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetMaxScannedHeightResult.kt @@ -7,6 +7,8 @@ import cash.z.ecc.android.sdk.model.BlockHeight */ internal sealed class GetMaxScannedHeightResult { data class Success(val height: BlockHeight) : GetMaxScannedHeightResult() + data object None : GetMaxScannedHeightResult() + data class Failure(val exception: Throwable) : GetMaxScannedHeightResult() } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetSubtreeRootsResult.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetSubtreeRootsResult.kt index a87d2ceb..99627e70 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetSubtreeRootsResult.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetSubtreeRootsResult.kt @@ -7,7 +7,10 @@ import cash.z.ecc.android.sdk.internal.model.SubtreeRoot */ internal sealed class GetSubtreeRootsResult { data class SpendBeforeSync(val subTreeRootList: List) : GetSubtreeRootsResult() + data object Linear : GetSubtreeRootsResult() + data object FailureConnection : GetSubtreeRootsResult() + data class OtherFailure(val exception: Throwable) : GetSubtreeRootsResult() } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetWalletSummaryResult.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetWalletSummaryResult.kt index c50d67dd..4862f553 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetWalletSummaryResult.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/GetWalletSummaryResult.kt @@ -12,5 +12,6 @@ internal sealed class GetWalletSummaryResult { } data object None : GetWalletSummaryResult() + data class Failure(val exception: Throwable) : GetWalletSummaryResult() } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/PutSaplingSubtreeRootsResult.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/PutSaplingSubtreeRootsResult.kt index d72c4ce6..7fddd621 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/PutSaplingSubtreeRootsResult.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/PutSaplingSubtreeRootsResult.kt @@ -7,5 +7,6 @@ import cash.z.ecc.android.sdk.model.BlockHeight */ internal sealed class PutSaplingSubtreeRootsResult { object Success : PutSaplingSubtreeRootsResult() + data class Failure(val failedAtHeight: BlockHeight, val exception: Throwable) : PutSaplingSubtreeRootsResult() } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SbSPreparationResult.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SbSPreparationResult.kt index c4613394..685f0d41 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SbSPreparationResult.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SbSPreparationResult.kt @@ -8,6 +8,7 @@ import cash.z.ecc.android.sdk.model.BlockHeight */ internal sealed class SbSPreparationResult { object ConnectionFailure : SbSPreparationResult() + data class ProcessFailure( val failedAtHeight: BlockHeight, val exception: Throwable @@ -18,9 +19,11 @@ internal sealed class SbSPreparationResult { this.exception ) } + data class Success( val suggestedRangesResult: SuggestScanRangesResult, val verifyRangeResult: VerifySuggestedScanRange ) : SbSPreparationResult() + object NoMoreBlocksToProcess : SbSPreparationResult() } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SuggestScanRangesResult.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SuggestScanRangesResult.kt index 2b886365..1ab795e4 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SuggestScanRangesResult.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SuggestScanRangesResult.kt @@ -8,5 +8,6 @@ import cash.z.ecc.android.sdk.model.BlockHeight */ internal sealed class SuggestScanRangesResult { data class Success(val ranges: List) : SuggestScanRangesResult() + data class Failure(val failedAtHeight: BlockHeight, val exception: Throwable) : SuggestScanRangesResult() } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SyncingResult.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SyncingResult.kt index 24f09cb1..44b87654 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SyncingResult.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/SyncingResult.kt @@ -12,39 +12,52 @@ internal sealed class SyncingResult { override fun toString(): String = this::class.java.simpleName object AllSuccess : SyncingResult() + object RestartSynchronization : SyncingResult() + data class DownloadSuccess(val downloadedBlocks: List?) : SyncingResult() { override fun toString() = "${this::class.java.simpleName} with ${downloadedBlocks?.size ?: "none"} blocks" } + interface Failure { val failedAtHeight: BlockHeight? val exception: CompactBlockProcessorException + fun toBlockProcessingResult(): CompactBlockProcessor.BlockProcessingResult = CompactBlockProcessor.BlockProcessingResult.SyncFailure( this.failedAtHeight, this.exception ) } + data class DownloadFailed( override val failedAtHeight: BlockHeight, override val exception: CompactBlockProcessorException ) : Failure, SyncingResult() + object ScanSuccess : SyncingResult() + data class ScanFailed( override val failedAtHeight: BlockHeight, override val exception: CompactBlockProcessorException ) : Failure, SyncingResult() + object DeleteSuccess : SyncingResult() + data class DeleteFailed( override val failedAtHeight: BlockHeight?, override val exception: CompactBlockProcessorException ) : Failure, SyncingResult() + object EnhanceSuccess : SyncingResult() + data class EnhanceFailed( override val failedAtHeight: BlockHeight, override val exception: CompactBlockProcessorException ) : Failure, SyncingResult() + object UpdateBirthday : SyncingResult() + data class ContinuityError( override val failedAtHeight: BlockHeight, override val exception: CompactBlockProcessorException diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/UpdateChainTipResult.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/UpdateChainTipResult.kt index 53d218f5..dca70090 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/UpdateChainTipResult.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/UpdateChainTipResult.kt @@ -7,5 +7,6 @@ import cash.z.ecc.android.sdk.model.BlockHeight */ internal sealed class UpdateChainTipResult { data class Success(val height: BlockHeight) : UpdateChainTipResult() + data class Failure(val failedAtHeight: BlockHeight, val exception: Throwable) : UpdateChainTipResult() } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/VerifySuggestedScanRange.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/VerifySuggestedScanRange.kt index 90ed3824..57eef80e 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/VerifySuggestedScanRange.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/model/VerifySuggestedScanRange.kt @@ -7,5 +7,6 @@ import cash.z.ecc.android.sdk.internal.model.ScanRange */ internal sealed class VerifySuggestedScanRange { data class ShouldVerify(val scanRange: ScanRange) : VerifySuggestedScanRange() + object NoRangeToVerify : VerifySuggestedScanRange() } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt index f69c2503..60e9ab2d 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt @@ -10,6 +10,7 @@ import co.electriccoin.lightwallet.client.model.BlockHeightUnsafe // TODO [#1248]: Clean up unused exceptions // TODO [#1248]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1248 + /** * Marker for all custom exceptions from the SDK. Making it an interface would result in more typing * so it's a supertype, instead. @@ -37,6 +38,7 @@ sealed class RepositoryException(message: String, cause: Throwable? = null) : Sd "The channel is closed. Note that once a repository has stopped it " + "cannot be restarted. Verify that the repository is not being restarted." ) + object Unprepared : RepositoryException( "Unprepared repository: Data cannot be accessed before the repository is prepared." + " Ensure that things have been properly initialized. If you see this error it most" + @@ -56,6 +58,7 @@ sealed class SynchronizerException(message: String, cause: Throwable? = null) : "This synchronizer was already started. Multiple calls to start are not" + "allowed and once a synchronizer has stopped it cannot be restarted." ) + object NotYetStarted : SynchronizerException( "The synchronizer has not yet started. Verify that" + " start has been called prior to this operation and that the coroutineScope is not" + @@ -71,10 +74,12 @@ sealed class CompactBlockProcessorException(message: String, cause: Throwable? = "No data db file found at path $path. Verify " + "that the data DB has been initialized via `rustBackend.initDataDb(path)`" ) + open class ConfigurationException( message: String, cause: Throwable? ) : CompactBlockProcessorException(message, cause) + class FileInsteadOfPath(fileName: String) : ConfigurationException( "Invalid Path: the given path appears to be a" + " file name instead of a path: $fileName. The RustBackend expects the absolutePath to the database rather" + @@ -82,13 +87,16 @@ sealed class CompactBlockProcessorException(message: String, cause: Throwable? = " So pass in context.getDatabasePath(dbFileName).absolutePath instead of just dbFileName alone.", null ) + class FailedReorgRepair(message: String) : CompactBlockProcessorException(message) + class Uninitialized(cause: Throwable? = null) : CompactBlockProcessorException( "Cannot process blocks because the wallet has not been" + " initialized. Verify that the seed phrase was properly created or imported. If so, then this problem" + " can be fixed by re-importing the wallet.", cause ) + object NoAccount : CompactBlockProcessorException( "Attempting to scan without an account. This is probably a setup error or a race condition." ) @@ -103,11 +111,13 @@ sealed class CompactBlockProcessorException(message: String, cause: Throwable? = "See logs for details.", cause ) + class FailedScanException(cause: Throwable? = null) : CompactBlockProcessorException( "Error while scanning blocks. This most likely means a problem with locally persisted data. " + "See logs for details.", cause ) + class FailedDeleteException(cause: Throwable? = null) : CompactBlockProcessorException( "Error while deleting block files. This most likely means the data are not persisted correctly." + " See logs for details.", @@ -123,18 +133,19 @@ sealed class CompactBlockProcessorException(message: String, cause: Throwable? = height: BlockHeight, cause: Throwable ) : EnhanceTransactionError( - "Error while attempting to download a transaction to enhance", - height, - cause - ) + "Error while attempting to download a transaction to enhance", + height, + cause + ) + class EnhanceTxDecryptError( height: BlockHeight, cause: Throwable ) : EnhanceTransactionError( - "Error while attempting to decrypt and store a transaction to enhance", - height, - cause - ) + "Error while attempting to decrypt and store a transaction to enhance", + height, + cause + ) } class MismatchedNetwork(clientNetwork: String?, serverNetwork: String?) : CompactBlockProcessorException( @@ -147,9 +158,10 @@ sealed class CompactBlockProcessorException(message: String, cause: Throwable? = serverBranch: String?, networkName: String? ) : CompactBlockProcessorException( - "Incompatible server: this client expects a server following consensus branch $clientBranch on $networkName " + - "but it was $serverBranch! Try updating the client or switching servers." - ) + "Incompatible server: this client expects a server following consensus branch $clientBranch" + + " on $networkName but it was $serverBranch! Try updating the client or switching servers." + ) + class BadBlockHeight(serverBlockHeight: BlockHeightUnsafe) : CompactBlockProcessorException( "The server returned a block height of $serverBlockHeight which is not valid." ) @@ -162,21 +174,24 @@ sealed class BirthdayException(message: String, cause: Throwable? = null) : SdkE class MissingBirthdayFilesException(directory: String) : BirthdayException( "Cannot initialize wallet because no birthday files were found in the $directory directory." ) + class ExactBirthdayNotFoundException internal constructor( birthday: BlockHeight, nearestMatch: Checkpoint? = null ) : BirthdayException( - "Unable to find birthday that exactly matches $birthday.${ - if (nearestMatch != null) { - " An exact match was request but the nearest match found was ${nearestMatch.height}." - } else { - "" - } - }" - ) + "Unable to find birthday that exactly matches $birthday.${ + if (nearestMatch != null) { + " An exact match was request but the nearest match found was ${nearestMatch.height}." + } else { + "" + } + }" + ) + class BirthdayFileNotFoundException(directory: String, height: BlockHeight?) : BirthdayException( "Unable to find birthday file for $height verify that $directory/$height.json exists." ) + class MalformattedBirthdayFilesException(directory: String, file: String, cause: Throwable?) : BirthdayException( "Failed to parse file $directory/$file verify that it is formatted as #####.json, " + "where the first portion is an Int representing the height of the tree contained in the file", @@ -192,24 +207,29 @@ sealed class InitializeException(message: String, cause: Throwable? = null) : Sd "A pending database migration requires the wallet's seed. Call this initialization " + "method again with the seed." ) + class FalseStart(cause: Throwable?) : InitializeException("Failed to initialize accounts due to: $cause", cause) + class AlreadyInitializedException(cause: Throwable, dbPath: String) : InitializeException( "Failed to initialize the blocks table" + " because it already exists in $dbPath", cause ) + object MissingBirthdayException : InitializeException( "Expected a birthday for this wallet but failed to find one. This usually means that " + "wallet setup did not happen correctly. A workaround might be to interpret the " + "birthday, based on the contents of the wallet data but it is probably better " + "not to mask this error because the root issue should be addressed." ) + object MissingViewingKeyException : InitializeException( "Expected a unified viewingKey for this wallet but failed to find one. This usually means" + " that wallet setup happened incorrectly. A workaround might be to derive the" + " unified viewingKey from the seed or seedPhrase, if they exist, but it is probably" + " better not to mask this error because the root issue should be addressed." ) + class MissingAddressException(description: String, cause: Throwable? = null) : InitializeException( "Expected a $description address for this wallet but failed to find one. This usually" + " means that wallet setup happened incorrectly. If this problem persists, a" + @@ -218,6 +238,7 @@ sealed class InitializeException(message: String, cause: Throwable? = null) : Sd " this happened so that the root issue can be uncovered and corrected." + if (cause != null) "\nCaused by: $cause" else "" ) + object DatabasePathException : InitializeException( "Critical failure to locate path for storing databases. Perhaps this device prevents" + @@ -229,9 +250,9 @@ sealed class InitializeException(message: String, cause: Throwable? = null) : Sd val network: ZcashNetwork, val alias: String ) : InitializeException( - "The requested database file with network: $network and alias: $alias does not exist yet. Create and " + - "initialize it using functions from ${DatabaseCoordinator::class.simpleName} first." - ) + "The requested database file with network: $network and alias: $alias does not exist yet. Create and " + + "initialize it using functions from ${DatabaseCoordinator::class.simpleName} first." + ) class InvalidBirthdayHeightException(birthday: BlockHeight?, network: ZcashNetwork) : InitializeException( "Invalid birthday height of ${birthday?.value}. The birthday height must be at least the height of" + @@ -253,6 +274,7 @@ sealed class LightWalletException(message: String, cause: Throwable? = null) : S " resource value for 'R.bool.lightwalletd_allow_very_insecure_connections' is true" + " because this choice should be explicit." ) + class ConsensusBranchException(sdkBranch: String, lwdBranch: String) : LightWalletException( "Error: the lightwalletd server is using a consensus branch" + @@ -294,23 +316,28 @@ sealed class TransactionEncoderException( internal val parameters: SaplingParameters, message: String ) : TransactionEncoderException("Failed to fetch params: $parameters, due to: $message") + class ValidateParamsException internal constructor( internal val parameters: SaplingParameters, message: String ) : TransactionEncoderException("Failed to validate fetched params: $parameters, due to:$message") + object MissingParamsException : TransactionEncoderException( "Cannot send funds due to missing spend or output params and attempting to download them failed." ) + class TransactionNotFoundException(transactionId: FirstClassByteArray) : TransactionEncoderException( "Unable to find transactionId $transactionId in the repository. This means the wallet created a transaction " + "and then returned a row ID that does not actually exist. This is a scenario where the wallet should " + "have thrown an exception but failed to do so." ) + class TransactionNotEncodedException(transactionId: Long) : TransactionEncoderException( "The transaction returned by the wallet," + " with id $transactionId, does not have any raw data. This is a scenario where the wallet should have " + "thrown an exception but failed to do so." ) + class IncompleteScanException(lastScannedHeight: BlockHeight?) : TransactionEncoderException( "Cannot" + " create spending transaction because scanning is incomplete. We must scan up to the" + diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/BatchMetrics.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/BatchMetrics.kt index d80f0e98..dfd9befb 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/BatchMetrics.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/BatchMetrics.kt @@ -14,10 +14,14 @@ class BatchMetrics( private var batchStartTime = 0L private var batchEndTime = 0L private var rangeSize = range.endInclusive.value - range.start.value + 1 + private fun now() = System.currentTimeMillis() @Suppress("MagicNumber") - private fun ips(blocks: Long, time: Long) = 1000.0f * blocks / time + private fun ips( + blocks: Long, + time: Long + ) = 1000.0f * blocks / time val isComplete get() = completedBatches * batchSize >= rangeSize val isBatchComplete get() = batchEndTime > batchStartTime @@ -32,6 +36,7 @@ class BatchMetrics( batchStartTime = now() if (rangeStartTime == 0L) rangeStartTime = batchStartTime } + fun endBatch() { completedBatches++ batchEndTime = now() diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ConsensusBranchId.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ConsensusBranchId.kt index 7b51263b..fdac40f2 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ConsensusBranchId.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ConsensusBranchId.kt @@ -26,10 +26,11 @@ enum class ConsensusBranchId(val displayName: String, val id: Long, val hexId: S fun fromId(id: Long): ConsensusBranchId? = values().firstOrNull { it.id == id } - fun fromHex(hex: String): ConsensusBranchId? = values().firstOrNull { branch -> - hex.lowercase(Locale.US).replace("_", "").replaceFirst("0x", "").let { sanitized -> - branch.hexId.equals(sanitized, true) + fun fromHex(hex: String): ConsensusBranchId? = + values().firstOrNull { branch -> + hex.lowercase(Locale.US).replace("_", "").replaceFirst("0x", "").let { sanitized -> + branch.hexId.equals(sanitized, true) + } } - } } } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/CurrencyFormatter.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/CurrencyFormatter.kt index 45580bb0..75416fa2 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/CurrencyFormatter.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/CurrencyFormatter.kt @@ -22,21 +22,23 @@ import java.util.Locale // TODO [#678]: provide a dynamic way to configure this globally for the SDK // For now, just make these vars so at least they could be modified in one place // TODO [#678]: https://github.com/zcash/zcash-android-wallet-sdk/issues/678 -@Suppress("MagicNumber") +@Suppress("MagicNumber", "ktlint:standard:property-naming") object Conversions { var ONE_ZEC_IN_ZATOSHI = BigDecimal(Zatoshi.ZATOSHI_PER_ZEC, MathContext.DECIMAL128) - var ZEC_FORMATTER = NumberFormat.getInstance(Locale.getDefault()).apply { - roundingMode = RoundingMode.HALF_EVEN - maximumFractionDigits = 6 - minimumFractionDigits = 0 - minimumIntegerDigits = 1 - } - var USD_FORMATTER = NumberFormat.getInstance(Locale.getDefault()).apply { - roundingMode = RoundingMode.HALF_EVEN - maximumFractionDigits = 2 - minimumFractionDigits = 2 - minimumIntegerDigits = 1 - } + var ZEC_FORMATTER = + NumberFormat.getInstance(Locale.getDefault()).apply { + roundingMode = RoundingMode.HALF_EVEN + maximumFractionDigits = 6 + minimumFractionDigits = 0 + minimumIntegerDigits = 1 + } + var USD_FORMATTER = + NumberFormat.getInstance(Locale.getDefault()).apply { + roundingMode = RoundingMode.HALF_EVEN + maximumFractionDigits = 2 + minimumFractionDigits = 2 + minimumIntegerDigits = 1 + } } /** @@ -144,7 +146,10 @@ fun BigDecimal?.toUsdString( * * @return a currency formatter, appropriate for the default locale. */ -fun currencyFormatter(maxDecimals: Int, minDecimals: Int): NumberFormat { +fun currencyFormatter( + maxDecimals: Int, + minDecimals: Int +): NumberFormat { return NumberFormat.getInstance(Locale.getDefault()).apply { roundingMode = ZEC_FORMATTER.roundingMode maximumFractionDigits = maxDecimals @@ -313,7 +318,10 @@ fun BigDecimal?.convertUsdToZec(zecPrice: BigDecimal): BigDecimal { * @return this BigDecimal value converted from one currency into the other, based on the given * price. */ -fun BigDecimal.convertCurrency(zecPrice: BigDecimal, isUsd: Boolean): BigDecimal { +fun BigDecimal.convertCurrency( + zecPrice: BigDecimal, + isUsd: Boolean +): BigDecimal { return if (isUsd) { this.convertUsdToZec(zecPrice) } else { @@ -330,14 +338,15 @@ fun String?.safelyConvertToBigDecimal(): BigDecimal? { if (this.isNullOrEmpty()) { return BigDecimal.ZERO } - val result = try { - // ignore commas and whitespace - val sanitizedInput = this.filter { it.isDigit() or (it == '.') } - BigDecimal.ZERO.max(BigDecimal(sanitizedInput, MathContext.DECIMAL128)) - } catch (nfe: NumberFormatException) { - Twig.debug(nfe) { "Exception while converting String to BigDecimal" } - null - } + val result = + try { + // ignore commas and whitespace + val sanitizedInput = this.filter { it.isDigit() or (it == '.') } + BigDecimal.ZERO.max(BigDecimal(sanitizedInput, MathContext.DECIMAL128)) + } catch (nfe: NumberFormatException) { + Twig.debug(nfe) { "Exception while converting String to BigDecimal" } + null + } return result } @@ -350,8 +359,10 @@ fun String?.safelyConvertToBigDecimal(): BigDecimal? { * @return the abbreviated string unless the string is too short, in which case the original string * is returned. */ -fun String.toAbbreviatedAddress(startLength: Int = 8, endLength: Int = 8) = - if (length > startLength + endLength) "${take(startLength)}…${takeLast(endLength)}" else this +fun String.toAbbreviatedAddress( + startLength: Int = 8, + endLength: Int = 8 +) = if (length > startLength + endLength) "${take(startLength)}…${takeLast(endLength)}" else this /** * Masks the current string for use in logs. If this string appears to be an address, the last diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/Flow.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/Flow.kt index e8e16969..ddd436f4 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/Flow.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/Flow.kt @@ -30,7 +30,10 @@ import kotlinx.coroutines.launch * flow2.collectWith(scope, ::collectingFunction2) * ``` */ -fun Flow.collectWith(scope: CoroutineScope, block: (T) -> Unit) { +fun Flow.collectWith( + scope: CoroutineScope, + block: (T) -> Unit +) { scope.launch { collect { block(it) @@ -41,10 +44,14 @@ fun Flow.collectWith(scope: CoroutineScope, block: (T) -> Unit) { /** * Utility for performing the given action on the first emission of a flow and running that action * in the given scope. + * + * Unused in the SDK but is used by the wallet app */ -// Unused in the SDK but is used by the wallet app @Suppress("unused") -fun Flow.onFirstWith(scope: CoroutineScope, block: suspend (T) -> S) { +fun Flow.onFirstWith( + scope: CoroutineScope, + block: suspend (T) -> S +) { scope.launch { onEach { block(it) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ZcashSdk.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ZcashSdk.kt index c1f49758..d7276099 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ZcashSdk.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ZcashSdk.kt @@ -10,7 +10,6 @@ import cash.z.ecc.android.sdk.model.Zatoshi */ @Suppress("MagicNumber") object ZcashSdk { - /** * Miner's fee in zatoshi. */ diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/AndroidApiVersion.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/AndroidApiVersion.kt index dec11dac..0696db09 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/AndroidApiVersion.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/AndroidApiVersion.kt @@ -11,7 +11,9 @@ internal object AndroidApiVersion { * [sdk]. */ @ChecksSdkIntAtLeast(parameter = 0) - fun isAtLeast(@IntRange(from = Build.VERSION_CODES.BASE.toLong()) sdk: Int): Boolean { + fun isAtLeast( + @IntRange(from = Build.VERSION_CODES.BASE.toLong()) sdk: Int + ): Boolean { return Build.VERSION.SDK_INT >= sdk } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/DerivationToolExt.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/DerivationToolExt.kt index 4b66ed61..799cad5e 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/DerivationToolExt.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/DerivationToolExt.kt @@ -26,19 +26,24 @@ fun Derivation.deriveUnifiedSpendingKey( fun Derivation.deriveUnifiedFullViewingKey( usk: UnifiedSpendingKey, network: ZcashNetwork -): UnifiedFullViewingKey = UnifiedFullViewingKey( - deriveUnifiedFullViewingKey( - JniUnifiedSpendingKey( - usk.account.value, - usk.copyBytes() - ), - network.id +): UnifiedFullViewingKey = + UnifiedFullViewingKey( + deriveUnifiedFullViewingKey( + JniUnifiedSpendingKey( + usk.account.value, + usk.copyBytes() + ), + network.id + ) ) -) fun Derivation.deriveUnifiedFullViewingKeysTypesafe( seed: ByteArray, network: ZcashNetwork, numberOfAccounts: Int ): List = - deriveUnifiedFullViewingKeys(seed, network.id, numberOfAccounts).map { UnifiedFullViewingKey(it) } + deriveUnifiedFullViewingKeys( + seed, + network.id, + numberOfAccounts + ).map { UnifiedFullViewingKey(it) } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/NoBackupContextWrapper.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/NoBackupContextWrapper.kt index cf788f31..a175eced 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/NoBackupContextWrapper.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/NoBackupContextWrapper.kt @@ -20,7 +20,6 @@ internal class NoBackupContextWrapper( context: Context, private val parentDir: File ) : ContextWrapper(context.applicationContext) { - /** * Overriding this function gives us ability to control the result database file location. * diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/SaplingParamTool.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/SaplingParamTool.kt index 3256e5fc..153e6237 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/SaplingParamTool.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/SaplingParamTool.kt @@ -21,7 +21,6 @@ import java.nio.channels.Channels import kotlin.time.Duration.Companion.milliseconds internal class SaplingParamTool(val properties: SaplingParamToolProperties) { - val spendParamsFile: File get() = File(properties.paramsDirectory, SPEND_PARAM_FILE_NAME) @@ -84,24 +83,26 @@ internal class SaplingParamTool(val properties: SaplingParamToolProperties) { */ internal suspend fun new(context: Context): SaplingParamTool { val paramsDirectory = Files.getZcashNoBackupSubdirectory(context) - val toolProperties = SaplingParamToolProperties( - paramsDirectory = paramsDirectory, - paramsLegacyDirectory = File(context.getCacheDirSuspend(), SAPLING_PARAMS_LEGACY_SUBDIRECTORY), - saplingParams = listOf( - SaplingParameters( - paramsDirectory, - SPEND_PARAM_FILE_NAME, - SPEND_PARAM_FILE_MAX_BYTES_SIZE, - SPEND_PARAM_FILE_SHA1_HASH - ), - SaplingParameters( - paramsDirectory, - OUTPUT_PARAM_FILE_NAME, - OUTPUT_PARAM_FILE_MAX_BYTES_SIZE, - OUTPUT_PARAM_FILE_SHA1_HASH - ) + val toolProperties = + SaplingParamToolProperties( + paramsDirectory = paramsDirectory, + paramsLegacyDirectory = File(context.getCacheDirSuspend(), SAPLING_PARAMS_LEGACY_SUBDIRECTORY), + saplingParams = + listOf( + SaplingParameters( + paramsDirectory, + SPEND_PARAM_FILE_NAME, + SPEND_PARAM_FILE_MAX_BYTES_SIZE, + SPEND_PARAM_FILE_SHA1_HASH + ), + SaplingParameters( + paramsDirectory, + OUTPUT_PARAM_FILE_NAME, + OUTPUT_PARAM_FILE_MAX_BYTES_SIZE, + OUTPUT_PARAM_FILE_SHA1_HASH + ) + ) ) - ) return SaplingParamTool(toolProperties) } @@ -153,7 +154,10 @@ internal class SaplingParamTool(val properties: SaplingParamToolProperties) { * * @return true in case of hashes are the same, false otherwise */ - private suspend fun isFileHashValid(parametersFile: File, fileHash: String): Boolean { + private suspend fun isFileHashValid( + parametersFile: File, + fileHash: String + ): Boolean { return try { fileHash == parametersFile.getSha1Hash() } catch (e: IOException) { @@ -250,10 +254,11 @@ internal class SaplingParamTool(val properties: SaplingParamToolProperties) { ) internal suspend fun fetchParams(paramsToFetch: SaplingParameters) { val url = URL("$CLOUD_PARAM_DIR_URL/${paramsToFetch.fileName}") - val temporaryFile = File( - paramsToFetch.destinationDirectory, - "$TEMPORARY_FILE_NAME_PREFIX${paramsToFetch.fileName}" - ) + val temporaryFile = + File( + paramsToFetch.destinationDirectory, + "$TEMPORARY_FILE_NAME_PREFIX${paramsToFetch.fileName}" + ) withContext(Dispatchers.IO) { runCatching { @@ -280,10 +285,11 @@ internal class SaplingParamTool(val properties: SaplingParamToolProperties) { // IOException - If some other I/O error occurs finalizeAndReportError( temporaryFile, - exception = TransactionEncoderException.FetchParamsException( - paramsToFetch, - "Error while fetching ${paramsToFetch.fileName}, caused by $exception." - ) + exception = + TransactionEncoderException.FetchParamsException( + paramsToFetch, + "Error while fetching ${paramsToFetch.fileName}, caused by $exception." + ) ) }.onSuccess { Twig.debug { @@ -293,10 +299,11 @@ internal class SaplingParamTool(val properties: SaplingParamToolProperties) { if (!isFileHashValid(temporaryFile, paramsToFetch.fileHash)) { finalizeAndReportError( temporaryFile, - exception = TransactionEncoderException.ValidateParamsException( - paramsToFetch, - "Failed while validating fetched param file: ${paramsToFetch.fileName}." - ) + exception = + TransactionEncoderException.ValidateParamsException( + paramsToFetch, + "Failed while validating fetched param file: ${paramsToFetch.fileName}." + ) ) } val resultFile = File(paramsToFetch.destinationDirectory, paramsToFetch.fileName) @@ -304,10 +311,11 @@ internal class SaplingParamTool(val properties: SaplingParamToolProperties) { finalizeAndReportError( temporaryFile, resultFile, - exception = TransactionEncoderException.ValidateParamsException( - paramsToFetch, - "Failed while renaming result param file: ${paramsToFetch.fileName}." - ) + exception = + TransactionEncoderException.ValidateParamsException( + paramsToFetch, + "Failed while renaming result param file: ${paramsToFetch.fileName}." + ) ) } } @@ -315,7 +323,10 @@ internal class SaplingParamTool(val properties: SaplingParamToolProperties) { } @Throws(TransactionEncoderException.FetchParamsException::class) - private suspend fun finalizeAndReportError(vararg files: File, exception: TransactionEncoderException) { + private suspend fun finalizeAndReportError( + vararg files: File, + exception: TransactionEncoderException + ) { files.forEach { it.deleteSuspend() } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/Twig.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/Twig.kt index 61825b90..60a840ff 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/Twig.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/Twig.kt @@ -44,7 +44,10 @@ object Twig { // JVMStatic is to simplify ProGuard/R8 rules for stripping this @JvmStatic - fun verbose(throwable: Throwable, message: () -> String) { + fun verbose( + throwable: Throwable, + message: () -> String + ) { Log.v(tag, formatMessage(message), throwable) } @@ -56,7 +59,10 @@ object Twig { // JVMStatic is to simplify ProGuard/R8 rules for stripping this @JvmStatic - fun debug(throwable: Throwable, message: () -> String) { + fun debug( + throwable: Throwable, + message: () -> String + ) { Log.d(tag, formatMessage(message), throwable) } @@ -68,7 +74,10 @@ object Twig { // JVMStatic is to simplify ProGuard/R8 rules for stripping this @JvmStatic - fun info(throwable: Throwable, message: () -> String) { + fun info( + throwable: Throwable, + message: () -> String + ) { Log.i(tag, formatMessage(message), throwable) } @@ -80,7 +89,10 @@ object Twig { // JVMStatic is to simplify ProGuard/R8 rules for stripping this @JvmStatic - fun warn(throwable: Throwable, message: () -> String) { + fun warn( + throwable: Throwable, + message: () -> String + ) { Log.w(tag, formatMessage(message), throwable) } @@ -92,19 +104,27 @@ object Twig { // JVMStatic is to simplify ProGuard/R8 rules for stripping this @JvmStatic - fun error(throwable: Throwable, message: () -> String) { + fun error( + throwable: Throwable, + message: () -> String + ) { Log.e(tag, formatMessage(message), throwable) } /** * Can be called in a release build to test that `assumenosideeffects` ProGuard rules have been * properly processed to strip out logging messages. + * + * JVMStatic is to simplify ProGuard/R8 rules for stripping this */ - // JVMStatic is to simplify ProGuard/R8 rules for stripping this @JvmStatic fun assertLoggingStripped() { @Suppress("MaxLineLength") - throw AssertionError("Logging was not disabled by ProGuard or R8. Logging should be disabled in release builds to reduce risk of sensitive information being leaked.") // $NON-NLS-1$ + // $NON-NLS-1$ + throw AssertionError( + "Logging was not disabled by ProGuard or R8. Logging should be disabled in release builds" + + " to reduce risk of sensitive information being leaked." + ) } private const val CALL_DEPTH = 4 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 6cf10b36..f399797e 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 @@ -14,7 +14,6 @@ import cash.z.ecc.android.sdk.model.ZcashNetwork @Suppress("TooManyFunctions") internal interface TypesafeBackend { - val network: ZcashNetwork suspend fun createAccountAndGetSpendingKey( @@ -64,7 +63,10 @@ internal interface TypesafeBackend { height: BlockHeight ) - suspend fun getMemoAsUtf8(txId: ByteArray, outputIndex: Int): String? + suspend fun getMemoAsUtf8( + txId: ByteArray, + outputIndex: Int + ): String? suspend fun initDataDb(seed: ByteArray?): Int @@ -110,7 +112,10 @@ internal interface TypesafeBackend { * @throws RuntimeException as a common indicator of the operation failure */ @Throws(RuntimeException::class) - suspend fun scanBlocks(fromHeight: BlockHeight, limit: Long) + suspend fun scanBlocks( + fromHeight: BlockHeight, + limit: Long + ) /** * @throws RuntimeException as a common indicator of the operation failure 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 31bde2e4..72dd7921 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 @@ -17,7 +17,6 @@ import kotlinx.coroutines.withContext @Suppress("TooManyFunctions") internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBackend { - override val network: ZcashNetwork get() = ZcashNetwork.from(backend.networkId) @@ -41,26 +40,28 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke to: String, value: Long, memo: ByteArray? - ): FirstClassByteArray = FirstClassByteArray( - backend.createToAddress( - usk.account.value, - usk.copyBytes(), - to, - value, - memo + ): FirstClassByteArray = + FirstClassByteArray( + backend.createToAddress( + usk.account.value, + usk.copyBytes(), + to, + value, + memo + ) ) - ) override suspend fun shieldToAddress( usk: UnifiedSpendingKey, memo: ByteArray? - ): FirstClassByteArray = FirstClassByteArray( - backend.shieldToAddress( - usk.account.value, - usk.copyBytes(), - memo + ): FirstClassByteArray = + FirstClassByteArray( + backend.shieldToAddress( + usk.account.value, + usk.copyBytes(), + memo + ) ) - ) override suspend fun getCurrentAddress(account: Account): String { return backend.getCurrentAddress(account.value) @@ -106,14 +107,16 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke // Note this implementation is not ideal because it requires two database queries without a transaction, which // makes the data potentially inconsistent. However the verified amount is queried first which makes this less // bad. - val verified = withContext(SdkDispatchers.DATABASE_IO) { - backend.getVerifiedTransparentBalance(address) - } - val total = withContext(SdkDispatchers.DATABASE_IO) { - backend.getTotalTransparentBalance( - address - ) - } + val verified = + withContext(SdkDispatchers.DATABASE_IO) { + backend.getVerifiedTransparentBalance(address) + } + val total = + withContext(SdkDispatchers.DATABASE_IO) { + backend.getTotalTransparentBalance( + address + ) + } return WalletBalance(Zatoshi(total), Zatoshi(verified)) } @@ -136,21 +139,26 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke ) } - override suspend fun getMemoAsUtf8(txId: ByteArray, outputIndex: Int): String? = - backend.getMemoAsUtf8(txId, outputIndex) + override suspend fun getMemoAsUtf8( + txId: ByteArray, + outputIndex: Int + ): String? = backend.getMemoAsUtf8(txId, outputIndex) override suspend fun initDataDb(seed: ByteArray?): Int = backend.initDataDb(seed) - override suspend fun putSaplingSubtreeRoots(startIndex: Long, roots: List) = - backend.putSaplingSubtreeRoots( - startIndex = startIndex, - roots = roots.map { + override suspend fun putSaplingSubtreeRoots( + startIndex: Long, + roots: List + ) = backend.putSaplingSubtreeRoots( + startIndex = startIndex, + roots = + roots.map { JniSubtreeRoot.new( rootHash = it.rootHash, completingBlockHeight = it.completingBlockHeight.value ) } - ) + ) override suspend fun updateChainTip(height: BlockHeight) = backend.updateChainTip(height.value) @@ -172,18 +180,23 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke } } - override suspend fun scanBlocks(fromHeight: BlockHeight, limit: Long) = backend.scanBlocks(fromHeight.value, limit) + override suspend fun scanBlocks( + fromHeight: BlockHeight, + limit: Long + ) = backend.scanBlocks(fromHeight.value, limit) - override suspend fun getWalletSummary(): WalletSummary? = backend.getWalletSummary()?.let { jniWalletSummary -> - WalletSummary.new(jniWalletSummary) - } + override suspend fun getWalletSummary(): WalletSummary? = + backend.getWalletSummary()?.let { jniWalletSummary -> + WalletSummary.new(jniWalletSummary) + } - override suspend fun suggestScanRanges(): List = backend.suggestScanRanges().map { jniScanRange -> - ScanRange.new( - jniScanRange, - network - ) - } + override suspend fun suggestScanRanges(): List = + backend.suggestScanRanges().map { jniScanRange -> + ScanRange.new( + jniScanRange, + network + ) + } override suspend fun decryptAndStoreTransaction(tx: ByteArray) = backend.decryptAndStoreTransaction(tx) @@ -194,7 +207,9 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke override suspend fun initBlockMetaDb(): Int = backend.initBlockMetaDb() override suspend fun writeBlockMetadata(blockMetadata: List) = - backend.writeBlockMetadata(blockMetadata) + backend.writeBlockMetadata( + blockMetadata + ) override fun isValidShieldedAddr(addr: String): Boolean = backend.isValidShieldedAddr(addr) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeDerivationToolImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeDerivationToolImpl.kt index c3f7755e..bdf340af 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeDerivationToolImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeDerivationToolImpl.kt @@ -7,7 +7,6 @@ import cash.z.ecc.android.sdk.model.ZcashNetwork import cash.z.ecc.android.sdk.tool.DerivationTool internal class TypesafeDerivationToolImpl(private val derivation: Derivation) : DerivationTool { - override suspend fun deriveUnifiedFullViewingKeys( seed: ByteArray, network: ZcashNetwork, diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/block/CompactBlockDownloader.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/block/CompactBlockDownloader.kt index 2b633215..d481afde 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/block/CompactBlockDownloader.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/block/CompactBlockDownloader.kt @@ -30,7 +30,6 @@ import kotlinx.coroutines.withContext * @property compactBlockStore responsible for persisting the compact blocks that are received */ open class CompactBlockDownloader private constructor(val compactBlockRepository: CompactBlockRepository) { - private lateinit var lightWalletClient: LightWalletClient constructor( @@ -52,34 +51,35 @@ open class CompactBlockDownloader private constructor(val compactBlockRepository */ @Throws(LightWalletException.DownloadBlockException::class) suspend fun downloadBlockRange(heightRange: ClosedRange): List { - val filteredFlow = lightWalletClient.getBlockRange( - BlockHeightUnsafe.from(heightRange.start)..BlockHeightUnsafe.from(heightRange.endInclusive) - ).onEach { response -> - when (response) { - is Response.Success -> { - Twig.verbose { "Downloading block at height: ${response.result.height} succeeded." } - } - is Response.Failure -> { - Twig.warn { "Downloading blocks in range: $heightRange failed with: ${response.description}." } - throw LightWalletException.DownloadBlockException( - response.code, - response.description, - response.toThrowable() - ) + val filteredFlow = + lightWalletClient.getBlockRange( + BlockHeightUnsafe.from(heightRange.start)..BlockHeightUnsafe.from(heightRange.endInclusive) + ).onEach { response -> + when (response) { + is Response.Success -> { + Twig.verbose { "Downloading block at height: ${response.result.height} succeeded." } + } + is Response.Failure -> { + Twig.warn { "Downloading blocks in range: $heightRange failed with: ${response.description}." } + throw LightWalletException.DownloadBlockException( + response.code, + response.description, + response.toThrowable() + ) + } } } - } - .filterIsInstance>() - .map { response -> - response.result - } - .onCompletion { - if (it != null) { - Twig.warn { "Blocks in range $heightRange failed to download with: $it" } - } else { - Twig.verbose { "All blocks in range $heightRange downloaded successfully" } + .filterIsInstance>() + .map { response -> + response.result + } + .onCompletion { + if (it != null) { + Twig.warn { "Blocks in range $heightRange failed to download with: $it" } + } else { + Twig.verbose { "All blocks in range $heightRange downloaded successfully" } + } } - } return compactBlockRepository.write(filteredFlow) } @@ -107,22 +107,22 @@ open class CompactBlockDownloader private constructor(val compactBlockRepository * * @return the latest block height that has been persisted. */ - suspend fun getLastDownloadedHeight() = - compactBlockRepository.getLatestHeight() + suspend fun getLastDownloadedHeight() = compactBlockRepository.getLatestHeight() - suspend fun getServerInfo(): LightWalletEndpointInfoUnsafe? = withContext(IO) { - retryUpToAndThrow(GET_SERVER_INFO_RETRIES) { - when (val response = lightWalletClient.getServerInfo()) { - is Response.Success -> return@withContext response.result - else -> { - lightWalletClient.reconnect() - Twig.warn { "WARNING: reconnecting to server in response to failure (retry #${it + 1})" } + suspend fun getServerInfo(): LightWalletEndpointInfoUnsafe? = + withContext(IO) { + retryUpToAndThrow(GET_SERVER_INFO_RETRIES) { + when (val response = lightWalletClient.getServerInfo()) { + is Response.Success -> return@withContext response.result + else -> { + lightWalletClient.reconnect() + Twig.warn { "WARNING: reconnecting to server in response to failure (retry #${it + 1})" } + } } } - } - null - } + null + } /** * Stop this downloader and cleanup any resources being used. diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/DatabaseCoordinator.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/DatabaseCoordinator.kt index a18ca6da..20f4f1ef 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/DatabaseCoordinator.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/DatabaseCoordinator.kt @@ -25,7 +25,6 @@ import java.io.File */ @Suppress("TooManyFunctions") internal class DatabaseCoordinator private constructor(context: Context) { - /* * This implementation is thread-safe but is not multi-process safe. * @@ -81,11 +80,12 @@ internal class DatabaseCoordinator private constructor(context: Context) { // First we deal with the legacy Cache database files (rollback included) on both older and newer path. In // case of deletion failure caused by any reason, we try it on the next time again. val legacyDbFilesDeleted = deleteLegacyCacheDbFiles(network, alias) - val result = if (legacyDbFilesDeleted) { - "are successfully deleted" - } else { - "failed to be deleted. Will be retried it on the next time" - } + val result = + if (legacyDbFilesDeleted) { + "are successfully deleted" + } else { + "failed to be deleted. Will be retried it on the next time" + } Twig.debug { "Legacy Cache database files $result." } return newDatabaseFilePointer( @@ -109,13 +109,14 @@ internal class DatabaseCoordinator private constructor(context: Context) { network: ZcashNetwork, alias: String ): File { - val dbLocationsPair = prepareDbFiles( - applicationContext, - network, - alias, - DB_DATA_NAME_LEGACY, - DB_DATA_NAME - ) + val dbLocationsPair = + prepareDbFiles( + applicationContext, + network, + alias, + DB_DATA_NAME_LEGACY, + DB_DATA_NAME + ) createFileMutex.withLock { return checkAndMoveDatabaseFiles( @@ -140,18 +141,20 @@ internal class DatabaseCoordinator private constructor(context: Context) { network: ZcashNetwork, alias: String ): File { - val legacyLocationDbFile = newDatabaseFilePointer( - null, - null, - DB_PENDING_TRANSACTIONS_NAME_LEGACY, - getDatabaseParentDir(applicationContext) - ) - val preferredLocationDbFile = newDatabaseFilePointer( - network, - alias, - DB_PENDING_TRANSACTIONS_NAME, - Files.getZcashNoBackupSubdirectory(applicationContext) - ) + val legacyLocationDbFile = + newDatabaseFilePointer( + null, + null, + DB_PENDING_TRANSACTIONS_NAME_LEGACY, + getDatabaseParentDir(applicationContext) + ) + val preferredLocationDbFile = + newDatabaseFilePointer( + network, + alias, + DB_PENDING_TRANSACTIONS_NAME, + Files.getZcashNoBackupSubdirectory(applicationContext) + ) createFileMutex.withLock { return checkAndMoveDatabaseFiles( @@ -217,13 +220,14 @@ internal class DatabaseCoordinator private constructor(context: Context) { network: ZcashNetwork, alias: String ): Boolean { - val legacyDatabaseLocationPair = prepareDbFiles( - applicationContext, - network, - alias, - DB_CACHE_OLDER_NAME_LEGACY, - DB_CACHE_NEWER_NAME_LEGACY - ) + val legacyDatabaseLocationPair = + prepareDbFiles( + applicationContext, + network, + alias, + DB_CACHE_OLDER_NAME_LEGACY, + DB_CACHE_NEWER_NAME_LEGACY + ) var olderLegacyCacheDbDeleted = true var newerLegacyCacheDbDeleted = true @@ -257,24 +261,27 @@ internal class DatabaseCoordinator private constructor(context: Context) { // Here we change the alias to be lowercase and underscored only if we work with the default // Zcash alias, otherwise we need to keep an SDK caller alias the same to avoid the database // files move breakage. - val aliasLegacy = if (ZcashSdk.DEFAULT_ALIAS == alias) { - ALIAS_LEGACY - } else { - alias - } + val aliasLegacy = + if (ZcashSdk.DEFAULT_ALIAS == alias) { + ALIAS_LEGACY + } else { + alias + } - val legacyLocationDbFile = newDatabaseFilePointer( - network, - aliasLegacy, - databaseNameLegacy, - getDatabaseParentDir(appContext) - ) - val preferredLocationDbFile = newDatabaseFilePointer( - network, - alias, - databaseName, - Files.getZcashNoBackupSubdirectory(appContext) - ) + val legacyLocationDbFile = + newDatabaseFilePointer( + network, + aliasLegacy, + databaseNameLegacy, + getDatabaseParentDir(appContext) + ) + val preferredLocationDbFile = + newDatabaseFilePointer( + network, + alias, + databaseName, + Files.getZcashNoBackupSubdirectory(appContext) + ) return Pair( legacyLocationDbFile, @@ -324,14 +331,16 @@ internal class DatabaseCoordinator private constructor(context: Context) { legacyLocationDbFile: File, preferredLocationDbFile: File ): Boolean { - val filesToBeRenamed = mutableListOf>().apply { - add(Pair(legacyLocationDbFile, preferredLocationDbFile)) - } + val filesToBeRenamed = + mutableListOf>().apply { + add(Pair(legacyLocationDbFile, preferredLocationDbFile)) + } // add journal database file, if exists - val journalSuffixedDbFile = File( - "${legacyLocationDbFile.absolutePath}-$DATABASE_FILE_JOURNAL_SUFFIX" - ) + val journalSuffixedDbFile = + File( + "${legacyLocationDbFile.absolutePath}-$DATABASE_FILE_JOURNAL_SUFFIX" + ) if (journalSuffixedDbFile.existsSuspend()) { filesToBeRenamed.add( Pair( @@ -344,9 +353,10 @@ internal class DatabaseCoordinator private constructor(context: Context) { } // add wal database file, if exists - val walSuffixedDbFile = File( - "${legacyLocationDbFile.absolutePath}-$DATABASE_FILE_WAL_SUFFIX" - ) + val walSuffixedDbFile = + File( + "${legacyLocationDbFile.absolutePath}-$DATABASE_FILE_WAL_SUFFIX" + ) if (walSuffixedDbFile.existsSuspend()) { filesToBeRenamed.add( Pair( @@ -394,13 +404,14 @@ internal class DatabaseCoordinator private constructor(context: Context) { dbFileName: String, parentDir: File ): File { - val aliasPrefix = if (alias == null) { - "" - } else if (alias.endsWith('_')) { - alias - } else { - "${alias}_" - } + val aliasPrefix = + if (alias == null) { + "" + } else if (alias.endsWith('_')) { + alias + } else { + "${alias}_" + } val networkPrefix = network?.networkName ?: "" diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/ReadOnlySqliteOpenHelper.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/ReadOnlySqliteOpenHelper.kt index a5a2144d..0e633019 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/ReadOnlySqliteOpenHelper.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/ReadOnlySqliteOpenHelper.kt @@ -11,12 +11,15 @@ internal class ReadOnlySqliteOpenHelper( name: String, version: Int ) : SQLiteOpenHelper(context, name, null, version) { - override fun onCreate(db: SQLiteDatabase?) { error("Database should be created by Rust libraries") } - override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { + override fun onUpgrade( + db: SQLiteDatabase?, + oldVersion: Int, + newVersion: Int + ) { error("Database should be upgraded by Rust libraries") } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/ReadOnlySupportSqliteOpenHelper.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/ReadOnlySupportSqliteOpenHelper.kt index 504d232e..e02e1132 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/ReadOnlySupportSqliteOpenHelper.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/ReadOnlySupportSqliteOpenHelper.kt @@ -11,7 +11,6 @@ import kotlinx.coroutines.withContext import java.io.File object ReadOnlySupportSqliteOpenHelper { - /** * Opens a database that has already been initialized by something else. * @@ -26,15 +25,17 @@ object ReadOnlySupportSqliteOpenHelper { databaseVersion: Int ): SupportSQLiteDatabase { return withContext(Dispatchers.IO) { - val contextWrapper = NoBackupContextWrapper( - context, - file.parentFile ?: throw InitializeException.DatabasePathException - ) - val config = SupportSQLiteOpenHelper.Configuration.builder(contextWrapper) - .apply { - name(file.name) - callback(ReadOnlyCallback(databaseVersion)) - }.build() + val contextWrapper = + NoBackupContextWrapper( + context, + file.parentFile ?: throw InitializeException.DatabasePathException + ) + val config = + SupportSQLiteOpenHelper.Configuration.builder(contextWrapper) + .apply { + name(file.name) + callback(ReadOnlyCallback(databaseVersion)) + }.build() FrameworkSQLiteOpenHelperFactory().create(config).readableDatabase } @@ -46,7 +47,11 @@ private class ReadOnlyCallback(version: Int) : SupportSQLiteOpenHelper.Callback( error("Database ${db.path} should be created by Rust libraries") } - override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { + override fun onUpgrade( + db: SupportSQLiteDatabase, + oldVersion: Int, + newVersion: Int + ) { error("Database ${db.path} should be upgraded by Rust libraries") } } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/SQLiteDatabaseExt.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/SQLiteDatabaseExt.kt index 60f566c9..b9401c81 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/SQLiteDatabaseExt.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/SQLiteDatabaseExt.kt @@ -33,19 +33,21 @@ internal fun SQLiteDatabase.queryAndMap( ) = flow { // TODO [#703]: Support blobs for argument binding // TODO [#703]: https://github.com/zcash/zcash-android-wallet-sdk/issues/703 - val mappedSelectionArgs = selectionArgs?.onEach { - require(it !is ByteArray) { - "ByteArray is not supported" - } - }?.map { it.toString() }?.toTypedArray() + val mappedSelectionArgs = + selectionArgs?.onEach { + require(it !is ByteArray) { + "ByteArray is not supported" + } + }?.map { it.toString() }?.toTypedArray() // Counterintuitive but correct. When using the comma syntax, offset comes first. // When using the keyword syntax, "LIMIT 1 OFFSET 2" then the offset comes second. - val limitAndOffset = if (null == offset) { - limit - } else { - String.format(Locale.ROOT, "%s,%s", offset, limit) // NON-NLS - } + val limitAndOffset = + if (null == offset) { + limit + } else { + String.format(Locale.ROOT, "%s,%s", offset, limit) // NON-NLS + } query( table, @@ -84,23 +86,24 @@ internal fun SupportSQLiteDatabase.queryAndMap( coroutineContext: CoroutineContext = Dispatchers.IO, cursorParser: CursorParser ) = flow { - val qb = SupportSQLiteQueryBuilder.builder(table).apply { - columns(columns) - selection(selection, selectionArgs) - having(having) - groupBy(groupBy) - orderBy(orderBy) + val qb = + SupportSQLiteQueryBuilder.builder(table).apply { + columns(columns) + selection(selection, selectionArgs) + having(having) + groupBy(groupBy) + orderBy(orderBy) - if (null != limit) { - // Counterintuitive but correct. When using the comma syntax, offset comes first. - // When using the keyword syntax, "LIMIT 1 OFFSET 2" then the offset comes second. - if (null == offset) { - limit(limit) - } else { - limit(String.format(Locale.ROOT, "%s,%s", offset, limit)) // NON-NLS + if (null != limit) { + // Counterintuitive but correct. When using the comma syntax, offset comes first. + // When using the keyword syntax, "LIMIT 1 OFFSET 2" then the offset comes second. + if (null == offset) { + limit(limit) + } else { + limit(String.format(Locale.ROOT, "%s,%s", offset, limit)) // NON-NLS + } } } - } query(qb.create()).use { it.moveToPosition(-1) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/AccountTable.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/AccountTable.kt index ac1466a9..2a744440 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/AccountTable.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/AccountTable.kt @@ -6,15 +6,15 @@ import kotlinx.coroutines.flow.first internal class AccountTable(private val sqliteDatabase: SupportSQLiteDatabase) { companion object { - private val PROJECTION_COUNT = arrayOf("COUNT(*)") // $NON-NLS } - suspend fun count() = sqliteDatabase.queryAndMap( - AccountTableDefinition.TABLE_NAME, - columns = PROJECTION_COUNT, - cursorParser = { it.getLong(0) } - ).first() + suspend fun count() = + sqliteDatabase.queryAndMap( + AccountTableDefinition.TABLE_NAME, + columns = PROJECTION_COUNT, + cursorParser = { it.getLong(0) } + ).first() } internal object AccountTableDefinition { diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/AllTransactionView.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/AllTransactionView.kt index fdd59d96..df99f238 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/AllTransactionView.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/AllTransactionView.kt @@ -20,102 +20,118 @@ internal class AllTransactionView( private val sqliteDatabase: SupportSQLiteDatabase ) { companion object { - private const val COLUMN_SORT_HEIGHT = "sort_height" // $NON-NLS private const val QUERY_LIMIT = "1" // $NON-NLS - private val COLUMNS = arrayOf( - "*", // $NON-NLS - @Suppress("MaxLineLength") - "IFNULL(${AllTransactionViewDefinition.COLUMN_INTEGER_MINED_HEIGHT}, ${UInt.MAX_VALUE}) AS $COLUMN_SORT_HEIGHT" // $NON-NLS - ) + private val COLUMNS = + arrayOf( + // $NON-NLS + "*", + @Suppress("MaxLineLength") + // $NON-NLS + "IFNULL(${AllTransactionViewDefinition.COLUMN_INTEGER_MINED_HEIGHT}, ${UInt.MAX_VALUE}) AS $COLUMN_SORT_HEIGHT" + ) - private val ORDER_BY = String.format( - Locale.ROOT, - "%s DESC, %s DESC", // $NON-NLS - COLUMN_SORT_HEIGHT, - AllTransactionViewDefinition.COLUMN_INTEGER_TRANSACTION_INDEX - ) + private val ORDER_BY = + String.format( + Locale.ROOT, + // $NON-NLS + "%s DESC, %s DESC", + COLUMN_SORT_HEIGHT, + AllTransactionViewDefinition.COLUMN_INTEGER_TRANSACTION_INDEX + ) - private val ORDER_BY_MINED_HEIGHT = String.format( - Locale.ROOT, - "%s ASC", // $NON-NLS - AllTransactionViewDefinition.COLUMN_INTEGER_MINED_HEIGHT - ) + private val ORDER_BY_MINED_HEIGHT = + String.format( + Locale.ROOT, + // $NON-NLS + "%s ASC", + AllTransactionViewDefinition.COLUMN_INTEGER_MINED_HEIGHT + ) - private val SELECTION_BLOCK_RANGE = String.format( - Locale.ROOT, - "%s >= ? AND %s <= ?", // $NON-NLS - AllTransactionViewDefinition.COLUMN_INTEGER_MINED_HEIGHT, - AllTransactionViewDefinition.COLUMN_INTEGER_MINED_HEIGHT - ) + private val SELECTION_BLOCK_RANGE = + String.format( + Locale.ROOT, + // $NON-NLS + "%s >= ? AND %s <= ?", + AllTransactionViewDefinition.COLUMN_INTEGER_MINED_HEIGHT, + AllTransactionViewDefinition.COLUMN_INTEGER_MINED_HEIGHT + ) - private val SELECTION_RAW_IS_NULL = String.format( - Locale.ROOT, - "%s IS NULL", // $NON-NLS - AllTransactionViewDefinition.COLUMN_BLOB_RAW - ) + private val SELECTION_RAW_IS_NULL = + String.format( + Locale.ROOT, + // $NON-NLS + "%s IS NULL", + AllTransactionViewDefinition.COLUMN_BLOB_RAW + ) private val PROJECTION_COUNT = arrayOf("COUNT(*)") // $NON-NLS private val PROJECTION_MINED_HEIGHT = arrayOf(AllTransactionViewDefinition.COLUMN_INTEGER_MINED_HEIGHT) } - private val cursorParser: CursorParser = CursorParser { cursor -> - val minedHeightColumnIndex = - cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_INTEGER_MINED_HEIGHT) - val transactionIndexColumnIndex = cursor.getColumnIndex( - AllTransactionViewDefinition.COLUMN_INTEGER_TRANSACTION_INDEX - ) - val rawTransactionIdIndex = - cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_BLOB_RAW_TRANSACTION_ID) - val expiryHeightIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_INTEGER_EXPIRY_HEIGHT) - val rawIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_BLOB_RAW) - val netValueIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_LONG_ACCOUNT_BALANCE_DELTA) - val feePaidIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_LONG_FEE_PAID) - val isChangeIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_BOOLEAN_IS_CHANGE) - val receivedNoteCountIndex = cursor.getColumnIndex( - AllTransactionViewDefinition.COLUMN_INTEGER_RECEIVED_NOTE_COUNT - ) - val sentNoteCountIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_INTEGER_SENT_NOTE_COUNT) - val memoCountIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_INTEGER_MEMO_COUNT) - val blockTimeIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_INTEGER_BLOCK_TIME) + private val cursorParser: CursorParser = + CursorParser { cursor -> + val minedHeightColumnIndex = + cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_INTEGER_MINED_HEIGHT) + val transactionIndexColumnIndex = + cursor.getColumnIndex( + AllTransactionViewDefinition.COLUMN_INTEGER_TRANSACTION_INDEX + ) + val rawTransactionIdIndex = + cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_BLOB_RAW_TRANSACTION_ID) + val expiryHeightIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_INTEGER_EXPIRY_HEIGHT) + val rawIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_BLOB_RAW) + val netValueIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_LONG_ACCOUNT_BALANCE_DELTA) + val feePaidIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_LONG_FEE_PAID) + val isChangeIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_BOOLEAN_IS_CHANGE) + val receivedNoteCountIndex = + cursor.getColumnIndex( + AllTransactionViewDefinition.COLUMN_INTEGER_RECEIVED_NOTE_COUNT + ) + val sentNoteCountIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_INTEGER_SENT_NOTE_COUNT) + val memoCountIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_INTEGER_MEMO_COUNT) + val blockTimeIndex = cursor.getColumnIndex(AllTransactionViewDefinition.COLUMN_INTEGER_BLOCK_TIME) - val netValueLong = cursor.getLong(netValueIndex) - val isSent = netValueLong < 0 + val netValueLong = cursor.getLong(netValueIndex) + val isSent = netValueLong < 0 - DbTransactionOverview( - rawId = FirstClassByteArray(cursor.getBlob(rawTransactionIdIndex)), - minedHeight = cursor.getLongOrNull(minedHeightColumnIndex)?.let { - BlockHeight.new(zcashNetwork, it) - }, - expiryHeight = cursor.getLongOrNull(expiryHeightIndex)?.let { - // TODO [#1251]: Separate "no expiry height" from "expiry height unknown". - if (0L == it) { - null - } else { - BlockHeight.new(zcashNetwork, it) - } - }, - index = cursor.getLongOrNull(transactionIndexColumnIndex), - raw = cursor.getBlobOrNull(rawIndex)?.let { FirstClassByteArray(it) }, - isSentTransaction = isSent, - netValue = Zatoshi(netValueLong.absoluteValue), - feePaid = cursor.getLongOrNull(feePaidIndex)?.let { Zatoshi(it) }, - isChange = cursor.getInt(isChangeIndex) != 0, - receivedNoteCount = cursor.getInt(receivedNoteCountIndex), - sentNoteCount = cursor.getInt(sentNoteCountIndex), - memoCount = cursor.getInt(memoCountIndex), - blockTimeEpochSeconds = cursor.getLongOrNull(blockTimeIndex) - ) - } + DbTransactionOverview( + rawId = FirstClassByteArray(cursor.getBlob(rawTransactionIdIndex)), + minedHeight = + cursor.getLongOrNull(minedHeightColumnIndex)?.let { + BlockHeight.new(zcashNetwork, it) + }, + expiryHeight = + cursor.getLongOrNull(expiryHeightIndex)?.let { + // TODO [#1251]: Separate "no expiry height" from "expiry height unknown". + if (0L == it) { + null + } else { + BlockHeight.new(zcashNetwork, it) + } + }, + index = cursor.getLongOrNull(transactionIndexColumnIndex), + raw = cursor.getBlobOrNull(rawIndex)?.let { FirstClassByteArray(it) }, + isSentTransaction = isSent, + netValue = Zatoshi(netValueLong.absoluteValue), + feePaid = cursor.getLongOrNull(feePaidIndex)?.let { Zatoshi(it) }, + isChange = cursor.getInt(isChangeIndex) != 0, + receivedNoteCount = cursor.getInt(receivedNoteCountIndex), + sentNoteCount = cursor.getInt(sentNoteCountIndex), + memoCount = cursor.getInt(memoCountIndex), + blockTimeEpochSeconds = cursor.getLongOrNull(blockTimeIndex) + ) + } - suspend fun count() = sqliteDatabase.queryAndMap( - AllTransactionViewDefinition.VIEW_NAME, - columns = PROJECTION_COUNT, - cursorParser = { it.getLong(0) } - ).first() + suspend fun count() = + sqliteDatabase.queryAndMap( + AllTransactionViewDefinition.VIEW_NAME, + columns = PROJECTION_COUNT, + cursorParser = { it.getLong(0) } + ).first() fun getAllTransactions() = sqliteDatabase.queryAndMap( diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DbDerivedDataRepository.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DbDerivedDataRepository.kt index a3f2aafd..799d638c 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DbDerivedDataRepository.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/DbDerivedDataRepository.kt @@ -31,11 +31,13 @@ internal class DbDerivedDataRepository( override suspend fun getOldestTransaction() = derivedDataDb.allTransactionView.getOldestTransaction() - override suspend fun findMinedHeight(rawTransactionId: ByteArray) = derivedDataDb.transactionTable - .findMinedHeight(rawTransactionId) + override suspend fun findMinedHeight(rawTransactionId: ByteArray) = + derivedDataDb.transactionTable + .findMinedHeight(rawTransactionId) - override suspend fun findMatchingTransactionId(rawTransactionId: ByteArray) = derivedDataDb.transactionTable - .findDatabaseId(rawTransactionId) + override suspend fun findMatchingTransactionId(rawTransactionId: ByteArray) = + derivedDataDb.transactionTable + .findDatabaseId(rawTransactionId) override suspend fun getTransactionCount() = derivedDataDb.transactionTable.count() @@ -43,9 +45,10 @@ internal class DbDerivedDataRepository( invalidatingFlow.value = UUID.randomUUID() } - override suspend fun getAccountCount() = derivedDataDb.accountTable.count() - // toInt() should be safe because we expect very few accounts - .toInt() + override suspend fun getAccountCount() = + derivedDataDb.accountTable.count() + // toInt() should be safe because we expect very few accounts + .toInt() override val allTransactions: Flow> get() = invalidatingFlow.map { derivedDataDb.allTransactionView.getAllTransactions().toList() } 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 945a18cb..783e27c0 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 @@ -57,14 +57,15 @@ internal class DerivedDataDb private constructor( throw CompactBlockProcessorException.Uninitialized(it) } - val database = ReadOnlySupportSqliteOpenHelper.openExistingDatabaseAsReadOnly( - NoBackupContextWrapper( - context, - databaseFile.parentFile!! - ), - databaseFile, - DATABASE_VERSION - ) + val database = + ReadOnlySupportSqliteOpenHelper.openExistingDatabaseAsReadOnly( + NoBackupContextWrapper( + context, + databaseFile.parentFile!! + ), + databaseFile, + DATABASE_VERSION + ) val dataDb = DerivedDataDb(zcashNetwork, database) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TransactionTable.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TransactionTable.kt index ed99bb02..a9d42c41 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TransactionTable.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TransactionTable.kt @@ -16,13 +16,14 @@ internal class TransactionTable( private val zcashNetwork: ZcashNetwork, private val sqliteDatabase: SupportSQLiteDatabase ) { - companion object { - private val SELECTION_BLOCK_IS_NULL = String.format( - Locale.ROOT, - "%s IS NULL", // $NON-NLS - TransactionTableDefinition.COLUMN_INTEGER_BLOCK - ) + private val SELECTION_BLOCK_IS_NULL = + String.format( + Locale.ROOT, + // $NON-NLS + "%s IS NULL", + TransactionTableDefinition.COLUMN_INTEGER_BLOCK + ) private val PROJECTION_COUNT = arrayOf("COUNT(*)") // $NON-NLS @@ -30,33 +31,39 @@ internal class TransactionTable( private val PROJECTION_PRIMARY_KEY_ID = arrayOf(TransactionTableDefinition.COLUMN_INTEGER_ID) - private val PROJECTION_ENCODED_TRANSACTION = arrayOf( - TransactionTableDefinition.COLUMN_BLOB_TRANSACTION_ID, - TransactionTableDefinition.COLUMN_BLOB_RAW, - TransactionTableDefinition.COLUMN_INTEGER_EXPIRY_HEIGHT - ) + private val PROJECTION_ENCODED_TRANSACTION = + arrayOf( + TransactionTableDefinition.COLUMN_BLOB_TRANSACTION_ID, + TransactionTableDefinition.COLUMN_BLOB_RAW, + TransactionTableDefinition.COLUMN_INTEGER_EXPIRY_HEIGHT + ) - private val SELECTION_RAW_TRANSACTION_ID = String.format( - Locale.ROOT, - "%s = ?", // $NON-NLS - TransactionTableDefinition.COLUMN_BLOB_TRANSACTION_ID - ) + private val SELECTION_RAW_TRANSACTION_ID = + String.format( + Locale.ROOT, + // $NON-NLS + "%s = ?", + TransactionTableDefinition.COLUMN_BLOB_TRANSACTION_ID + ) - private val SELECTION_TRANSACTION_ID_AND_RAW_NOT_NULL = String.format( - Locale.ROOT, - "%s = ? AND %s IS NOT NULL", // $NON-NLS - TransactionTableDefinition.COLUMN_BLOB_TRANSACTION_ID, - TransactionTableDefinition.COLUMN_BLOB_RAW - ) + private val SELECTION_TRANSACTION_ID_AND_RAW_NOT_NULL = + String.format( + Locale.ROOT, + // $NON-NLS + "%s = ? AND %s IS NOT NULL", + TransactionTableDefinition.COLUMN_BLOB_TRANSACTION_ID, + TransactionTableDefinition.COLUMN_BLOB_RAW + ) } - suspend fun count() = withContext(Dispatchers.IO) { - sqliteDatabase.queryAndMap( - table = TransactionTableDefinition.TABLE_NAME, - columns = PROJECTION_COUNT, - cursorParser = { it.getLong(0) } - ).first() - } + suspend fun count() = + withContext(Dispatchers.IO) { + sqliteDatabase.queryAndMap( + table = TransactionTableDefinition.TABLE_NAME, + columns = PROJECTION_COUNT, + cursorParser = { it.getLong(0) } + ).first() + } suspend fun countUnmined() = sqliteDatabase.queryAndMap( @@ -77,11 +84,12 @@ internal class TransactionTable( val heightIndex = it.getColumnIndexOrThrow(TransactionTableDefinition.COLUMN_INTEGER_EXPIRY_HEIGHT) val raw = it.getBlob(rawIndex) - val expiryHeight = if (it.isNull(heightIndex)) { - null - } else { - BlockHeight.new(zcashNetwork, it.getLong(heightIndex)) - } + val expiryHeight = + if (it.isNull(heightIndex)) { + null + } else { + BlockHeight.new(zcashNetwork, it.getLong(heightIndex)) + } EncodedTransaction( txId, diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TxOutputsView.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TxOutputsView.kt index bc1603c2..fe9fac4b 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TxOutputsView.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/db/derived/TxOutputsView.kt @@ -14,26 +14,30 @@ internal class TxOutputsView( private val sqliteDatabase: SupportSQLiteDatabase ) { companion object { - - private val ORDER_BY = String.format( - Locale.ROOT, - "%s ASC", // $NON-NLS - TxOutputsViewDefinition.COLUMN_BLOB_TRANSACTION_ID - ) + private val ORDER_BY = + String.format( + Locale.ROOT, + // $NON-NLS + "%s ASC", + TxOutputsViewDefinition.COLUMN_BLOB_TRANSACTION_ID + ) private val PROJECTION_OUTPUT_INDEX = arrayOf(TxOutputsViewDefinition.COLUMN_INTEGER_OUTPUT_INDEX) - private val PROJECTION_RECIPIENT = arrayOf( - TxOutputsViewDefinition.COLUMN_STRING_TO_ADDRESS, - TxOutputsViewDefinition.COLUMN_INTEGER_TO_ACCOUNT - ) + private val PROJECTION_RECIPIENT = + arrayOf( + TxOutputsViewDefinition.COLUMN_STRING_TO_ADDRESS, + TxOutputsViewDefinition.COLUMN_INTEGER_TO_ACCOUNT + ) - private val SELECT_BY_TRANSACTION_ID_AND_NOT_CHANGE = String.format( - Locale.ROOT, - "%s = ? AND %s == 0", // $NON-NLS - TxOutputsViewDefinition.COLUMN_BLOB_TRANSACTION_ID, - TxOutputsViewDefinition.COLUMN_INTEGER_IS_CHANGE - ) + private val SELECT_BY_TRANSACTION_ID_AND_NOT_CHANGE = + String.format( + Locale.ROOT, + // $NON-NLS + "%s = ? AND %s == 0", + TxOutputsViewDefinition.COLUMN_BLOB_TRANSACTION_ID, + TxOutputsViewDefinition.COLUMN_INTEGER_IS_CHANGE + ) } fun getSaplingOutputIndices(transactionId: FirstClassByteArray) = diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/ClosedRangeExt.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/ClosedRangeExt.kt index 2294bd81..235d029b 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/ClosedRangeExt.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/ClosedRangeExt.kt @@ -7,5 +7,4 @@ internal fun ClosedRange.isNotEmpty() = this.length() > 1 internal fun ClosedRange?.isNullOrEmpty() = this?.isEmpty() ?: true // Add 1 because the range is inclusive -internal fun ClosedRange.length() = - this.endInclusive.value.plus(1).minus(this.start.value) +internal fun ClosedRange.length() = this.endInclusive.value.plus(1).minus(this.start.value) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/ContextExt.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/ContextExt.kt index 6f160344..f32f5104 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/ContextExt.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/ContextExt.kt @@ -6,16 +6,15 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext internal suspend fun Context.getDatabasePathSuspend(fileName: String) = - withContext(Dispatchers.IO) { getDatabasePath(fileName) } + withContext(Dispatchers.IO) { + getDatabasePath(fileName) + } -internal suspend fun Context.getNoBackupFilesDirSuspend() = - withContext(Dispatchers.IO) { noBackupFilesDir } +internal suspend fun Context.getNoBackupFilesDirSuspend() = withContext(Dispatchers.IO) { noBackupFilesDir } -internal suspend fun Context.getCacheDirSuspend() = - withContext(Dispatchers.IO) { cacheDir } +internal suspend fun Context.getCacheDirSuspend() = withContext(Dispatchers.IO) { cacheDir } -internal suspend fun Context.getFilesDirSuspend() = - withContext(Dispatchers.IO) { filesDir } +internal suspend fun Context.getFilesDirSuspend() = withContext(Dispatchers.IO) { filesDir } internal suspend fun Context.getDataDirCompatSuspend() = withContext(Dispatchers.IO) { ContextCompat.getDataDir(this@getDataDirCompatSuspend) } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/ExceptionExt.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/ExceptionExt.kt index b87426c2..1d24548a 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/ExceptionExt.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/ExceptionExt.kt @@ -28,13 +28,14 @@ internal inline fun tryWarn( return try { block() } catch (t: Throwable) { - val shouldThrowAnyway = ( - unlessContains != null && - (t.message?.lowercase()?.contains(unlessContains.lowercase()) == true) - ) || + val shouldThrowAnyway = ( - ifContains != null && - (t.message?.lowercase()?.contains(ifContains.lowercase()) == false) + unlessContains != null && + (t.message?.lowercase()?.contains(unlessContains.lowercase()) == true) + ) || + ( + ifContains != null && + (t.message?.lowercase()?.contains(ifContains.lowercase()) == false) ) if (shouldThrowAnyway) { throw t @@ -47,11 +48,13 @@ internal inline fun tryWarn( // Note: Do NOT change these texts as they match the ones from ScanError in // librustzcash/zcash_client_backend/src/scanning.rs -internal const val PREV_HASH_MISMATCH = "The parent hash of proposed block does not correspond to the block hash at " + - "height" // $NON-NLS +internal const val PREV_HASH_MISMATCH = + "The parent hash of proposed block does not correspond to the block hash at " + + "height" // $NON-NLS internal const val BLOCK_HEIGHT_DISCONTINUITY = "Block height discontinuity at height" // $NON-NLS -internal const val TREE_SIZE_MISMATCH = "note commitment tree size provided by a compact block did not match the " + - "expected size at height" // $NON-NLS +internal const val TREE_SIZE_MISMATCH = + "note commitment tree size provided by a compact block did not match the " + + "expected size at height" // $NON-NLS /** * Check whether this error is the result of a failed continuity while scanning new blocks in the Rust layer. @@ -59,11 +62,12 @@ internal const val TREE_SIZE_MISMATCH = "note commitment tree size provided by a * @return true in case of the check match, false otherwise */ internal fun Throwable.isScanContinuityError(): Boolean { - val errorMessages = listOf( - PREV_HASH_MISMATCH, - BLOCK_HEIGHT_DISCONTINUITY, - TREE_SIZE_MISMATCH - ) + val errorMessages = + listOf( + PREV_HASH_MISMATCH, + BLOCK_HEIGHT_DISCONTINUITY, + TREE_SIZE_MISMATCH + ) errorMessages.forEach { errMessage -> if (this.message?.lowercase()?.contains(errMessage.lowercase()) == true) { return true diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/Placeholders.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/Placeholders.kt index a10e11d0..c5523e8c 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/Placeholders.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/Placeholders.kt @@ -8,14 +8,20 @@ import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -@Deprecated(message = InsecureWarning.message) +@Deprecated(message = InsecureWarning.MESSAGE) class SampleSpendingKeyProvider(private val seedValue: String) : ReadWriteProperty { - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { + override fun setValue( + thisRef: Any?, + property: KProperty<*>, + value: String + ) { Twig.debug { "Set value called on property: $property, with value: $value." } } - override fun getValue(thisRef: Any?, property: KProperty<*>): String { + override fun getValue( + thisRef: Any?, + property: KProperty<*> + ): String { // dynamically generating keys, based on seed is out of scope for this sample if (seedValue != "dummyseed") { error("This sample provider only supports the dummy seed") @@ -26,42 +32,64 @@ class SampleSpendingKeyProvider(private val seedValue: String) : ReadWriteProper } } -@Deprecated(message = InsecureWarning.message) +@Deprecated(message = InsecureWarning.MESSAGE) class SampleSeedProvider(val seed: ByteArray) : ReadOnlyProperty { constructor(seedValue: String) : this(seedValue.toByteArray()) - override fun getValue(thisRef: Any?, property: KProperty<*>): ByteArray { + + override fun getValue( + thisRef: Any?, + property: KProperty<*> + ): ByteArray { return seed } } -@Deprecated(message = InsecureWarning.message) +@Deprecated(message = InsecureWarning.MESSAGE) class BlockingSeedProvider(val seed: ByteArray, val delay: Long = 5000L) : ReadOnlyProperty { constructor(seedValue: String, delayMillis: Long = 5000L) : this(seedValue.toByteArray(), delayMillis) - override fun getValue(thisRef: Any?, property: KProperty<*>): ByteArray { + + override fun getValue( + thisRef: Any?, + property: KProperty<*> + ): ByteArray { Thread.sleep(delay) return seed } } -@Deprecated(message = InsecureWarning.message) +@Deprecated(message = InsecureWarning.MESSAGE) class SimpleProvider(var value: T) : ReadWriteProperty { - override fun getValue(thisRef: Any?, property: KProperty<*>): T { + override fun getValue( + thisRef: Any?, + property: KProperty<*> + ): T { return value } - override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + override fun setValue( + thisRef: Any?, + property: KProperty<*>, + value: T + ) { this.value = value } } -@Deprecated(message = InsecureWarning.message) +@Deprecated(message = InsecureWarning.MESSAGE) class BlockingProvider(var value: T, val delay: Long = 5000L) : ReadWriteProperty { - override fun getValue(thisRef: Any?, property: KProperty<*>): T { + override fun getValue( + thisRef: Any?, + property: KProperty<*> + ): T { Thread.sleep(delay) return value } - override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + override fun setValue( + thisRef: Any?, + property: KProperty<*>, + value: T + ) { Thread.sleep(delay) this.value = value } @@ -84,10 +112,10 @@ class BlockingProvider(var value: T, val delay: Long = 5000L) : ReadWriteProp * https://github.com/iamMehedi/Secured-Preference-Store */ @Suppress("HardwareIds", "UtilityClassWithPublicConstructor") -@Deprecated(message = InsecureWarning.message) +@Deprecated(message = InsecureWarning.MESSAGE) class SeedGenerator { companion object { - @Deprecated(message = InsecureWarning.message) + @Deprecated(message = InsecureWarning.MESSAGE) fun getDeviceId(appContext: Context): String { val id = Build.FINGERPRINT + Settings.Secure.getString(appContext.contentResolver, Settings.Secure.ANDROID_ID) @@ -97,6 +125,7 @@ class SeedGenerator { } internal object InsecureWarning { - const val message = "Do not use this because it is insecure and only intended for test code and samples. " + - "Instead, use the Android Keystore system or a 3rd party library that leverages it." + const val MESSAGE = + "Do not use this because it is insecure and only intended for test code and samples. " + + "Instead, use the Android Keystore system or a 3rd party library that leverages it." } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/WalletService.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/WalletService.kt index 05e6f202..b7843f97 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/WalletService.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/WalletService.kt @@ -108,8 +108,9 @@ suspend inline fun retryWithBackoff( sequence++ // initialDelay^(sequence/4) + jitter - var duration = initialDelayMillis.toDouble().pow((sequence.toDouble() / 4.0)).toLong() + - Random.nextLong(1000L) + var duration = + initialDelayMillis.toDouble().pow((sequence.toDouble() / 4.0)).toLong() + + Random.nextLong(1000L) if (duration > maxDelayMillis) { duration = maxDelayMillis - Random.nextLong(1000L) // include jitter but don't exceed max delay sequence /= 2 diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/AccountBalance.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/AccountBalance.kt index f6a423a1..0a54ccd1 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/AccountBalance.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/AccountBalance.kt @@ -9,10 +9,11 @@ internal data class AccountBalance( companion object { fun new(jni: JniAccountBalance): AccountBalance { return AccountBalance( - sapling = WalletBalance( - Zatoshi(jni.saplingTotalBalance), - Zatoshi(jni.saplingVerifiedBalance) - ) + sapling = + WalletBalance( + Zatoshi(jni.saplingTotalBalance), + Zatoshi(jni.saplingVerifiedBalance) + ) ) } } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/CompactBlock.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/CompactBlock.kt index fb50739c..6dcc382a 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/CompactBlock.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/CompactBlock.kt @@ -8,7 +8,10 @@ data class CompactBlock internal constructor( val data: FirstClassByteArray ) { companion object { - fun new(height: BlockHeight, data: FirstClassByteArray): CompactBlock { + fun new( + height: BlockHeight, + data: FirstClassByteArray + ): CompactBlock { // on this place we should validate input values from "unsafe" model version return CompactBlock(height, data) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/DbTransactionOverview.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/DbTransactionOverview.kt index a4372c17..20e34591 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/DbTransactionOverview.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/DbTransactionOverview.kt @@ -21,6 +21,7 @@ internal data class DbTransactionOverview internal constructor( val blockTimeEpochSeconds: Long? ) { override fun toString() = "DbTransactionOverview" + fun txIdString(): String { return rawId.byteArray.toHexReversed() } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ScanProgress.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ScanProgress.kt index ce597e56..87fa5128 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ScanProgress.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ScanProgress.kt @@ -9,13 +9,14 @@ internal data class ScanProgress( /** * Returns progress ratio in [0, 1] range. Any out-of-range value is treated as 0. */ - fun getSafeRatio() = numerator.toFloat().div(denominator).let { ratio -> - if (ratio < 0f || ratio > 1f) { - 0f - } else { - ratio + fun getSafeRatio() = + numerator.toFloat().div(denominator).let { ratio -> + if (ratio < 0f || ratio > 1f) { + 0f + } else { + ratio + } } - } companion object { fun new(jni: JniWalletSummary): ScanProgress { 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 6586db5e..e9056837 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 @@ -24,10 +24,13 @@ internal data class ScanRange( * Note that this function subtracts 1 from [JniScanRange.endHeight] as the rest of the logic works with * [ClosedRange] and the endHeight is exclusive. */ - fun new(jni: JniScanRange, zcashNetwork: ZcashNetwork): ScanRange { + fun new( + jni: JniScanRange, + zcashNetwork: ZcashNetwork + ): ScanRange { return ScanRange( range = - BlockHeight.new(zcashNetwork, jni.startHeight)..(BlockHeight.new(zcashNetwork, jni.endHeight) - 1), + BlockHeight.new(zcashNetwork, jni.startHeight)..(BlockHeight.new(zcashNetwork, jni.endHeight) - 1), priority = jni.priority ) } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/SubtreeRoot.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/SubtreeRoot.kt index a58306ed..34611bfa 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/SubtreeRoot.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/SubtreeRoot.kt @@ -32,7 +32,10 @@ internal data class SubtreeRoot( } companion object { - fun new(unsafe: SubtreeRootUnsafe, zcashNetwork: ZcashNetwork): SubtreeRoot { + fun new( + unsafe: SubtreeRootUnsafe, + zcashNetwork: ZcashNetwork + ): SubtreeRoot { return SubtreeRoot( rootHash = unsafe.rootHash, completingBlockHash = unsafe.completingBlockHash, diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/WalletSummary.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/WalletSummary.kt index 7df20c7f..53e5921b 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/WalletSummary.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/WalletSummary.kt @@ -9,9 +9,10 @@ internal data class WalletSummary( companion object { fun new(jni: JniWalletSummary): WalletSummary { return WalletSummary( - accountBalances = jni.accountBalances.associateBy({ Account(it.account) }, { - AccountBalance.new(it) - }), + accountBalances = + jni.accountBalances.associateBy({ Account(it.account) }, { + AccountBalance.new(it) + }), scanProgress = ScanProgress.new(jni) ) } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ext/BlockHeightUnsafeExt.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ext/BlockHeightUnsafeExt.kt index c28811b7..af451783 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ext/BlockHeightUnsafeExt.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ext/BlockHeightUnsafeExt.kt @@ -4,8 +4,6 @@ import cash.z.ecc.android.sdk.model.BlockHeight import cash.z.ecc.android.sdk.model.ZcashNetwork import co.electriccoin.lightwallet.client.model.BlockHeightUnsafe -internal fun BlockHeightUnsafe.Companion.from(blockHeight: BlockHeight) = - BlockHeightUnsafe(blockHeight.value) +internal fun BlockHeightUnsafe.Companion.from(blockHeight: BlockHeight) = BlockHeightUnsafe(blockHeight.value) -internal fun BlockHeightUnsafe.toBlockHeight(zcashNetwork: ZcashNetwork) = - BlockHeight.new(zcashNetwork, value) +internal fun BlockHeightUnsafe.toBlockHeight(zcashNetwork: ZcashNetwork) = BlockHeight.new(zcashNetwork, value) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ext/CheckpointExt.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ext/CheckpointExt.kt index 7cc1f493..04bd4a0c 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ext/CheckpointExt.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ext/CheckpointExt.kt @@ -20,8 +20,10 @@ internal val Checkpoint.Companion.KEY_EPOCH_SECONDS internal val Checkpoint.Companion.KEY_TREE get() = "saplingTree" -internal fun Checkpoint.Companion.from(zcashNetwork: ZcashNetwork, jsonString: String) = - from(zcashNetwork, JSONObject(jsonString)) +internal fun Checkpoint.Companion.from( + zcashNetwork: ZcashNetwork, + jsonString: String +) = from(zcashNetwork, JSONObject(jsonString)) private fun Checkpoint.Companion.from( zcashNetwork: ZcashNetwork, @@ -29,10 +31,11 @@ private fun Checkpoint.Companion.from( ): Checkpoint { when (val version = jsonObject.optInt(Checkpoint.KEY_VERSION, Checkpoint.VERSION_1)) { Checkpoint.VERSION_1 -> { - val height = run { - val heightLong = jsonObject.getLong(Checkpoint.KEY_HEIGHT) - BlockHeight.new(zcashNetwork, heightLong) - } + val height = + run { + val heightLong = jsonObject.getLong(Checkpoint.KEY_HEIGHT) + BlockHeight.new(zcashNetwork, heightLong) + } val hash = jsonObject.getString(Checkpoint.KEY_HASH) val epochSeconds = jsonObject.getLong(Checkpoint.KEY_EPOCH_SECONDS) val tree = jsonObject.getString(Checkpoint.KEY_TREE) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/repository/DerivedDataRepository.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/repository/DerivedDataRepository.kt index 08050b5f..f1f0dede 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/repository/DerivedDataRepository.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/repository/DerivedDataRepository.kt @@ -12,7 +12,6 @@ import kotlinx.coroutines.flow.Flow */ @Suppress("TooManyFunctions") internal interface DerivedDataRepository { - /** * The height of the first transaction that hasn't been enhanced yet. * diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/storage/block/FileCompactBlockRepository.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/storage/block/FileCompactBlockRepository.kt index 4ce75751..2b7c0e9d 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/storage/block/FileCompactBlockRepository.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/storage/block/FileCompactBlockRepository.kt @@ -24,7 +24,6 @@ internal class FileCompactBlockRepository( private val blocksDirectory: File, private val backend: TypesafeBackend ) : CompactBlockRepository { - override suspend fun getLatestHeight() = backend.getLatestCacheHeight() override suspend fun findCompactBlock(height: BlockHeight) = backend.findBlockMetadata(height) @@ -78,11 +77,12 @@ internal class FileCompactBlockRepository( if (blocksDirectory.existsSuspend()) { blocksDirectory.listFilesSuspend()?.forEach { - val result = if (it.isDirectorySuspend()) { - it.deleteRecursivelySuspend() - } else { - it.deleteSuspend() - } + val result = + if (it.isDirectorySuspend()) { + it.deleteRecursivelySuspend() + } else { + it.deleteSuspend() + } if (!result) { return false } @@ -139,9 +139,10 @@ internal class FileCompactBlockRepository( backend: TypesafeBackend ): FileCompactBlockRepository { // create and check cache directories - val blocksDirectory = File(blockCacheRoot, BLOCKS_DOWNLOAD_DIRECTORY).also { - it.mkdirsSuspend() - } + val blocksDirectory = + File(blockCacheRoot, BLOCKS_DOWNLOAD_DIRECTORY).also { + it.mkdirsSuspend() + } if (!blocksDirectory.existsSuspend()) { error("${blocksDirectory.path} directory does not exist and could not be created.") } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt index 1d758c8d..4671d457 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt @@ -13,7 +13,6 @@ import cash.z.ecc.android.sdk.model.Zatoshi */ @Suppress("TooManyFunctions") internal interface OutboundTransactionManager { - /** * Encode the pending transaction using the given spending key. This is a local operation that * produces a raw transaction to submit to lightwalletd. diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt index 44dc2784..0eadef75 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt @@ -14,7 +14,6 @@ internal class OutboundTransactionManagerImpl( internal val encoder: TransactionEncoder, private val service: LightWalletClient ) : OutboundTransactionManager { - override suspend fun encode( usk: UnifiedSpendingKey, amount: Zatoshi, @@ -59,27 +58,24 @@ internal class OutboundTransactionManagerImpl( } } - override suspend fun isValidShieldedAddress(address: String) = - encoder.isValidShieldedAddress(address) + override suspend fun isValidShieldedAddress(address: String) = encoder.isValidShieldedAddress(address) - override suspend fun isValidTransparentAddress(address: String) = - encoder.isValidTransparentAddress(address) + override suspend fun isValidTransparentAddress(address: String) = encoder.isValidTransparentAddress(address) - override suspend fun isValidUnifiedAddress(address: String) = - encoder.isValidUnifiedAddress(address) + override suspend fun isValidUnifiedAddress(address: String) = encoder.isValidUnifiedAddress(address) // // Helper functions // companion object { - fun new( encoder: TransactionEncoder, lightWalletClient: LightWalletClient, - ): OutboundTransactionManager = OutboundTransactionManagerImpl( - encoder, - lightWalletClient - ) + ): OutboundTransactionManager = + OutboundTransactionManagerImpl( + encoder, + lightWalletClient + ) } } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt index 39fc73df..55e16fdc 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt @@ -27,7 +27,6 @@ internal class TransactionEncoderImpl( private val saplingParamTool: SaplingParamTool, private val repository: DerivedDataRepository ) : TransactionEncoder { - /** * Creates a transaction, throwing an exception whenever things are missing. When the provided * wallet implementation doesn't throw an exception, we wrap the issue into a descriptive @@ -73,8 +72,7 @@ internal class TransactionEncoderImpl( * * @return true when the given address is a valid z-addr */ - override suspend fun isValidShieldedAddress(address: String): Boolean = - backend.isValidShieldedAddr(address) + override suspend fun isValidShieldedAddress(address: String): Boolean = backend.isValidShieldedAddr(address) /** * Utility function to help with validation. This is not called during [createTransaction] @@ -84,8 +82,7 @@ internal class TransactionEncoderImpl( * * @return true when the given address is a valid t-addr */ - override suspend fun isValidTransparentAddress(address: String): Boolean = - backend.isValidTransparentAddr(address) + override suspend fun isValidTransparentAddress(address: String): Boolean = backend.isValidTransparentAddr(address) /** * Utility function to help with validation. This is not called during [createTransaction] @@ -95,8 +92,7 @@ internal class TransactionEncoderImpl( * * @return true when the given address is a valid ZIP 316 Unified Address */ - override suspend fun isValidUnifiedAddress(address: String): Boolean = - backend.isValidUnifiedAddr(address) + override suspend fun isValidUnifiedAddress(address: String): Boolean = backend.isValidUnifiedAddr(address) /** * Return the consensus branch that the encoder is using when making transactions. diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/BlockHeight.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/BlockHeight.kt index 85a12db5..830ffe9f 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/BlockHeight.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/BlockHeight.kt @@ -9,8 +9,7 @@ import cash.z.ecc.android.sdk.tool.CheckpointTool * New instances are constructed using the [new] factory method. * * @param value The block height. Must be in range of a UInt32. - */ -/* + * * For easier compatibility with Java clients, this class represents the height value as a Long with * assertions to ensure that it is a 32-bit unsigned integer. */ @@ -63,7 +62,10 @@ data class BlockHeight internal constructor(val value: Long) : Comparable= zcashNetwork.saplingActivationHeight.value) { "Height $blockHeight is below sapling activation height ${zcashNetwork.saplingActivationHeight}" } @@ -77,7 +79,10 @@ data class BlockHeight internal constructor(val value: Long) : Comparable 0 diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/PercentDecimal.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/PercentDecimal.kt index 27c109ab..20ee6f06 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/PercentDecimal.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/PercentDecimal.kt @@ -23,10 +23,11 @@ value class PercentDecimal(val decimal: Float) { val ZERO_PERCENT = PercentDecimal(MIN) val ONE_HUNDRED_PERCENT = PercentDecimal(MAX) - fun newLenient(decimal: Float) = PercentDecimal( - decimal - .coerceAtLeast(MIN) - .coerceAtMost(MAX) - ) + fun newLenient(decimal: Float) = + PercentDecimal( + decimal + .coerceAtLeast(MIN) + .coerceAtMost(MAX) + ) } } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/TransactionOverview.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/TransactionOverview.kt index 17607731..50548814 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/TransactionOverview.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/TransactionOverview.kt @@ -64,8 +64,8 @@ enum class TransactionState { Expired; companion object { - private const val MIN_CONFIRMATIONS = 10 + internal fun new( latestBlockHeight: BlockHeight?, minedHeight: BlockHeight?, diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKey.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKey.kt index c038d6ce..3a590b78 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKey.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/UnifiedSpendingKey.kt @@ -14,7 +14,6 @@ import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey */ class UnifiedSpendingKey private constructor( val account: Account, - /** * The binary encoding of the [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending * Key for [account]. @@ -25,7 +24,6 @@ class UnifiedSpendingKey private constructor( */ private val bytes: FirstClassByteArray ) { - internal constructor(uskJni: JniUnifiedSpendingKey) : this( Account(uskJni.account), FirstClassByteArray(uskJni.bytes.copyOf()) @@ -63,7 +61,6 @@ class UnifiedSpendingKey private constructor( } companion object { - /** * This method may fail if the [bytes] no longer represent a valid key. A key could become invalid due to * network upgrades or other internal changes. If a non-successful result is returned, clients are expected @@ -71,7 +68,10 @@ class UnifiedSpendingKey private constructor( * * @return A validated UnifiedSpendingKey. */ - suspend fun new(account: Account, bytes: ByteArray): Result { + suspend fun new( + account: Account, + bytes: ByteArray + ): Result { val bytesCopy = bytes.copyOf() RustBackend.loadLibrary() return runCatching { diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Zatoshi.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Zatoshi.kt index 73842773..aefca73b 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Zatoshi.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Zatoshi.kt @@ -14,6 +14,7 @@ data class Zatoshi(val value: Long) : Comparable { } operator fun plus(other: Zatoshi) = Zatoshi(value + other.value) + operator fun minus(other: Zatoshi) = Zatoshi(value - other.value) override fun compareTo(other: Zatoshi) = value.compareTo(other.value) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/ZcashNetwork.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/ZcashNetwork.kt index ead0b1c3..7463b46c 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/ZcashNetwork.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/ZcashNetwork.kt @@ -23,24 +23,27 @@ data class ZcashNetwork( // You may notice there are extra checkpoints bundled in the SDK that match the // sapling/orchard activation heights. - val Testnet = ZcashNetwork( - ID_TESTNET, - "testnet", - saplingActivationHeight = BlockHeight(280_000), - orchardActivationHeight = BlockHeight(1_842_420) - ) + val Testnet = + ZcashNetwork( + ID_TESTNET, + "testnet", + saplingActivationHeight = BlockHeight(280_000), + orchardActivationHeight = BlockHeight(1_842_420) + ) - val Mainnet = ZcashNetwork( - ID_MAINNET, - "mainnet", - saplingActivationHeight = BlockHeight(419_200), - orchardActivationHeight = BlockHeight(1_687_104) - ) + val Mainnet = + ZcashNetwork( + ID_MAINNET, + "mainnet", + saplingActivationHeight = BlockHeight(419_200), + orchardActivationHeight = BlockHeight(1_687_104) + ) - fun from(id: Int) = when (id) { - 0 -> Testnet - 1 -> Mainnet - else -> throw IllegalArgumentException("Unknown network id: $id") - } + fun from(id: Int) = + when (id) { + 0 -> Testnet + 1 -> Mainnet + else -> throw IllegalArgumentException("Unknown network id: $id") + } } } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/CheckpointTool.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/CheckpointTool.kt index a6e4a8eb..6590daf4 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/CheckpointTool.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/CheckpointTool.kt @@ -18,7 +18,6 @@ import java.util.Locale * Tool for loading checkpoints for the wallet, based on the height at which the wallet was born. */ internal object CheckpointTool { - // Behavior change implemented as a fix for issue #270. Temporarily adding a boolean // that allows the change to be rolled back quickly if needed, although long-term // this flag should be removed. @@ -43,22 +42,27 @@ internal object CheckpointTool { * Useful for when an exact checkpoint is needed, like for SAPLING_ACTIVATION_HEIGHT. In * most cases, loading the nearest checkpoint is preferred for privacy reasons. */ - suspend fun loadExact(context: Context, network: ZcashNetwork, birthday: BlockHeight) = - loadNearest(context, network, birthday).also { - if (it.height != birthday) { - throw BirthdayException.ExactBirthdayNotFoundException( - birthday, - it - ) - } + suspend fun loadExact( + context: Context, + network: ZcashNetwork, + birthday: BlockHeight + ) = loadNearest(context, network, birthday).also { + if (it.height != birthday) { + throw BirthdayException.ExactBirthdayNotFoundException( + birthday, + it + ) } + } // Converting this to suspending will then propagate @Throws(IOException::class) - internal suspend fun listCheckpointDirectoryContents(context: Context, directory: String) = - withContext(Dispatchers.IO) { - context.assets.list(directory) - } + internal suspend fun listCheckpointDirectoryContents( + context: Context, + directory: String + ) = withContext(Dispatchers.IO) { + context.assets.list(directory) + } /** * Returns the directory within the assets folder where birthday data @@ -68,8 +72,10 @@ internal object CheckpointTool { internal fun checkpointDirectory(network: ZcashNetwork) = "co.electriccoin.zcash/checkpoint/${network.networkName.lowercase(Locale.ROOT)}" - internal fun checkpointHeightFromFilename(zcashNetwork: ZcashNetwork, fileName: String) = - BlockHeight.new(zcashNetwork, fileName.split('.').first().toLong()) + internal fun checkpointHeightFromFilename( + zcashNetwork: ZcashNetwork, + fileName: String + ) = BlockHeight.new(zcashNetwork, fileName.split('.').first().toLong()) private fun Array.sortDescending(zcashNetwork: ZcashNetwork) = apply { sortByDescending { checkpointHeightFromFilename(zcashNetwork, it).value } } @@ -109,11 +115,12 @@ internal object CheckpointTool { throw BirthdayException.MissingBirthdayFilesException(directory) } - val filteredTreeFiles = unfilteredTreeFiles - .sortDescending(network) - .filter { filename -> - birthday?.let { checkpointHeightFromFilename(network, filename) <= it } ?: true - } + val filteredTreeFiles = + unfilteredTreeFiles + .sortDescending(network) + .filter { filename -> + birthday?.let { checkpointHeightFromFilename(network, filename) <= it } ?: true + } if (filteredTreeFiles.isEmpty()) { throw BirthdayException.BirthdayFileNotFoundException( @@ -140,23 +147,25 @@ internal object CheckpointTool { treeFiles.forEach { treefile -> @Suppress("TooGenericExceptionCaught") try { - val jsonString = withContext(Dispatchers.IO) { - context.assets.open("$directory/$treefile").use { inputStream -> - inputStream.reader().use { inputStreamReader -> - BufferedReader(inputStreamReader).use { bufferedReader -> - bufferedReader.readText() + val jsonString = + withContext(Dispatchers.IO) { + context.assets.open("$directory/$treefile").use { inputStream -> + inputStream.reader().use { inputStreamReader -> + BufferedReader(inputStreamReader).use { bufferedReader -> + bufferedReader.readText() + } } } } - } return Checkpoint.from(network, jsonString) } catch (t: Throwable) { - val exception = BirthdayException.MalformattedBirthdayFilesException( - directory, - treefile, - t - ) + val exception = + BirthdayException.MalformattedBirthdayFilesException( + directory, + treefile, + t + ) lastException = exception if (IS_FALLBACK_ON_FAILURE) { diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt index e699e2ea..a1d7ef32 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/tool/DerivationTool.kt @@ -10,7 +10,6 @@ import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.ZcashNetwork interface DerivationTool { - /** * Given a seed and a number of accounts, return the associated Unified Full Viewing Keys. * @@ -64,7 +63,11 @@ interface DerivationTool { * * @return the address that corresponds to the seed and account index. */ - suspend fun deriveUnifiedAddress(seed: ByteArray, network: ZcashNetwork, account: Account): String + suspend fun deriveUnifiedAddress( + seed: ByteArray, + network: ZcashNetwork, + account: Account + ): String /** * Given a Unified Full Viewing Key string, return the associated Unified Address. @@ -82,9 +85,11 @@ interface DerivationTool { companion object { const val DEFAULT_NUMBER_OF_ACCOUNTS = Derivation.DEFAULT_NUMBER_OF_ACCOUNTS - private val instance = SuspendingLazy { - TypesafeDerivationToolImpl(RustDerivationTool.new()) - } + private val instance = + SuspendingLazy { + TypesafeDerivationToolImpl(RustDerivationTool.new()) + } + suspend fun getInstance() = instance.getInstance(Unit) } } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/type/ConsensusMatchType.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/type/ConsensusMatchType.kt index 1c2aa496..f534b5d0 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/type/ConsensusMatchType.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/type/ConsensusMatchType.kt @@ -18,24 +18,25 @@ class ConsensusMatchType(val sdkBranch: ConsensusBranchId?, val serverBranch: Co val isSdkNewer = hasBoth && sdkBranch!!.ordinal > serverBranch!!.ordinal val errorMessage - get() = when { - isValid -> null - hasNeither -> - "Our branch is unknown and the server branch is unknown. Verify" + - " that they are both using the latest consensus branch ID." - hasServerBranch -> - "The server is on $serverBranch but our branch is unknown." + - " Verify that we are fully synced." - hasSdkBranch -> - "We are on $sdkBranch but the server branch is unknown. Verify" + - " the network connection." - else -> { - val newerBranch = if (isServerNewer) serverBranch else sdkBranch - val olderBranch = if (isSdkNewer) serverBranch else sdkBranch - val newerDevice = if (isServerNewer) "the server has" else "we have" - val olderDevice = if (isSdkNewer) "the server has" else "we have" - "Incompatible consensus: $newerDevice upgraded to $newerBranch but" + - " $olderDevice $olderBranch." + get() = + when { + isValid -> null + hasNeither -> + "Our branch is unknown and the server branch is unknown. Verify" + + " that they are both using the latest consensus branch ID." + hasServerBranch -> + "The server is on $serverBranch but our branch is unknown." + + " Verify that we are fully synced." + hasSdkBranch -> + "We are on $sdkBranch but the server branch is unknown. Verify" + + " the network connection." + else -> { + val newerBranch = if (isServerNewer) serverBranch else sdkBranch + val olderBranch = if (isSdkNewer) serverBranch else sdkBranch + val newerDevice = if (isServerNewer) "the server has" else "we have" + val olderDevice = if (isSdkNewer) "the server has" else "we have" + "Incompatible consensus: $newerDevice upgraded to $newerBranch but" + + " $olderDevice $olderBranch." + } } - } } diff --git a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessorTest.kt b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessorTest.kt index 1df6c35c..abeb7649 100644 --- a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessorTest.kt +++ b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessorTest.kt @@ -5,7 +5,6 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue class CompactBlockProcessorTest { - @Test fun should_refresh_preparation_test() { assertTrue { diff --git a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/ext/ConversionsTest.kt b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/ext/ConversionsTest.kt index 4818f201..91148e89 100644 --- a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/ext/ConversionsTest.kt +++ b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/ext/ConversionsTest.kt @@ -7,7 +7,6 @@ import java.math.MathContext import kotlin.test.assertEquals class ConversionsTest { - @Test fun `default right padding is 6`() { assertEquals(1.13.toZec(6), Zatoshi(113000000L).convertZatoshiToZec()) diff --git a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/ext/ExceptionExtTest.kt b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/ext/ExceptionExtTest.kt index 1288d9a2..bc932091 100644 --- a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/ext/ExceptionExtTest.kt +++ b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/ext/ExceptionExtTest.kt @@ -9,7 +9,6 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue class ExceptionExtTest { - @Test fun is_scan_continuity_error() { assertTrue { RuntimeException(PREV_HASH_MISMATCH).isScanContinuityError() } diff --git a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/ScanProgressTest.kt b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/ScanProgressTest.kt index b17340e0..e610fe39 100644 --- a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/ScanProgressTest.kt +++ b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/ScanProgressTest.kt @@ -16,9 +16,10 @@ class ScanProgressTest { @Test fun get_fallback_ratio_test() { - val scanProgress = ScanProgressFixture.new( - denominator = 0 - ) + val scanProgress = + ScanProgressFixture.new( + denominator = 0 + ) assertEquals(0f, scanProgress.getSafeRatio()) } } diff --git a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/ScanRangeTest.kt b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/ScanRangeTest.kt index d64fd559..315d1317 100644 --- a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/ScanRangeTest.kt +++ b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/internal/model/ScanRangeTest.kt @@ -12,9 +12,10 @@ import kotlin.test.assertTrue class ScanRangeTest { @Test fun get_suggest_scan_range_priority_test() { - val scanRange = ScanRangeFixture.new( - priority = SuggestScanRangePriority.Verify.priority - ) + val scanRange = + ScanRangeFixture.new( + priority = SuggestScanRangePriority.Verify.priority + ) assertTrue { scanRange.getSuggestScanRangePriority() == SuggestScanRangePriority.Verify } @@ -22,9 +23,10 @@ class ScanRangeTest { @Test fun priority_attribute_within_constraints() { - val instance = ScanRangeFixture.new( - priority = SuggestScanRangePriority.Verify.priority - ) + val instance = + ScanRangeFixture.new( + priority = SuggestScanRangePriority.Verify.priority + ) assertIs(instance) } @@ -39,9 +41,10 @@ class ScanRangeTest { @Test fun scan_range_boundaries_test() { - val scanRange = ScanRangeFixture.new( - range = ZcashNetwork.Testnet.saplingActivationHeight..ZcashNetwork.Testnet.saplingActivationHeight + 9 - ) + val scanRange = + ScanRangeFixture.new( + range = ZcashNetwork.Testnet.saplingActivationHeight..ZcashNetwork.Testnet.saplingActivationHeight + 9 + ) assertTrue { scanRange.range.isNotEmpty() } assertTrue { scanRange.range.length() == 10L } } diff --git a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/BlockHeightTest.kt b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/BlockHeightTest.kt index 57565ad0..653da273 100644 --- a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/BlockHeightTest.kt +++ b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/BlockHeightTest.kt @@ -63,11 +63,12 @@ class BlockHeightTest { @Test fun subtraction_of_block_height_succeeds() { - val one = BlockHeight.new( - ZcashNetwork.Mainnet, - ZcashNetwork.Mainnet.saplingActivationHeight.value + - ZcashNetwork.Mainnet.saplingActivationHeight.value - ) + val one = + BlockHeight.new( + ZcashNetwork.Mainnet, + ZcashNetwork.Mainnet.saplingActivationHeight.value + + ZcashNetwork.Mainnet.saplingActivationHeight.value + ) val two = BlockHeight.new(ZcashNetwork.Mainnet, ZcashNetwork.Mainnet.saplingActivationHeight.value) assertEquals(ZcashNetwork.Mainnet.saplingActivationHeight.value, one - two) diff --git a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/ZcashNetworkTest.kt b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/ZcashNetworkTest.kt index b20638be..e21a22d2 100644 --- a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/ZcashNetworkTest.kt +++ b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/model/ZcashNetworkTest.kt @@ -5,7 +5,6 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue class ZcashNetworkTest { - @Test fun is_mainnet_succeed_test() { assertTrue { ZcashNetwork.Mainnet.isMainnet() }