[#1327] Ktlint 1.1.0

* [#1327] Ktlint 1.1.0

Closes #1327

* [#1327] Autoformatting with Ktlint

* Ktlint formatting warnings fix
This commit is contained in:
Honza Rychnovský 2024-01-04 21:21:32 +01:00 committed by GitHub
parent e550cd9943
commit 73d7afc2a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
240 changed files with 4723 additions and 3814 deletions

View File

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

View File

@ -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
/**

View File

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

View File

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

View File

@ -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<JniBlockMeta>) =
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<String>
private external fun listTransparentReceivers(
dbDataPath: String,
account: Int,
networkId: Int
): Array<String>
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")

View File

@ -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<String> =
deriveUnifiedFullViewingKeysFromSeed(seed, numberOfAccounts, networkId = networkId)
): Array<String> = 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<String>
@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
}
}

View File

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

View File

@ -24,7 +24,6 @@ class JniUnifiedSpendingKey(
*/
val bytes: ByteArray
) {
// Override to prevent leaking key to logs
override fun toString() = "JniUnifiedSpendingKey(account=$account, bytes=***)"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@ open class DarksideTest : ScopedTest() {
ranOnce = true
}
}
companion object {
private var ranOnce = false
}

View File

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

View File

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

View File

@ -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<Unit> {
testScope.cancel()
testScope.coroutineContext[Job]?.join()
}
fun end() =
runBlocking<Unit> {
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<Unit> {
classScope.cancel()
classScope.coroutineContext[Job]?.join()
}
fun destroyScope() =
runBlocking<Unit> {
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"

View File

@ -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<CharArray> = MnemonicCode(WordCount.COUNT_24).words
override fun nextMnemonicList(seed: ByteArray): List<CharArray> = MnemonicCode(seed).words
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
}

View File

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

View File

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

View File

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

View File

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

View File

@ -6,10 +6,16 @@ import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import kotlin.time.Duration
fun UiDevice.waitFor(condition: SearchCondition<Boolean>, timeout: Duration): Boolean {
fun UiDevice.waitFor(
condition: SearchCondition<Boolean>,
timeout: Duration
): Boolean {
return wait(condition, timeout.inWholeMilliseconds)
}
fun UiObject2.clickAndWaitFor(condition: EventCondition<Boolean>, timeout: Duration): Boolean {
fun UiObject2.clickAndWaitFor(
condition: EventCondition<Boolean>,
timeout: Duration
): Boolean {
return clickAndWait(condition, timeout.inWholeMilliseconds)
}

View File

@ -25,8 +25,9 @@ open class UiTestPrerequisites {
}
private fun isScreenOn(): Boolean {
val powerService = ApplicationProvider.getApplicationContext<Context>()
.getSystemService(Context.POWER_SERVICE) as PowerManager
val powerService =
ApplicationProvider.getApplicationContext<Context>()
.getSystemService(Context.POWER_SERVICE) as PowerManager
return powerService.isInteractive
}
@ -40,7 +41,7 @@ open class UiTestPrerequisites {
val keyguardService = (
ApplicationProvider.getApplicationContext<Context>()
.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
)
)
return keyguardService.isKeyguardLocked
}

View File

@ -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<BlockHeightUnsafe>)
Twig.debug { "Latest Block: ${(it as Response.Success<BlockHeightUnsafe>).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<BlockHeightUnsafe>)
Twig.debug { "Latest Block: ${(it as Response.Success<BlockHeightUnsafe>).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<Response.Success<CompactBlockUnsafe>>()
.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<Response.Success<CompactBlockUnsafe>>()
.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" }
}

View File

@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.flowOf
*/
class MockPreferenceProvider(mutableMapFactory: () -> MutableMap<String, String?> = { mutableMapOf() }) :
PreferenceProvider {
private val map = mutableMapFactory()
override suspend fun getString(key: Key) = map[key.key]
@ -20,7 +19,10 @@ class MockPreferenceProvider(mutableMapFactory: () -> MutableMap<String, String?
override suspend fun hasKey(key: Key) = map.containsKey(key.key)
override suspend fun putString(key: Key, value: String?) {
override suspend fun putString(
key: Key,
value: String?
) {
map[key.key] = value
}
}

View File

@ -5,6 +5,8 @@ import cash.z.ecc.android.sdk.demoapp.preference.model.entry.Key
object BooleanPreferenceDefaultFixture {
val KEY = Key("some_boolean_key") // $NON-NLS
fun newTrue() = BooleanPreferenceDefault(KEY, true)
fun newFalse() = BooleanPreferenceDefault(KEY, false)
}

View File

@ -6,5 +6,9 @@ import cash.z.ecc.android.sdk.demoapp.preference.model.entry.Key
object IntegerPreferenceDefaultFixture {
val KEY = Key("some_string_key") // $NON-NLS
const val DEFAULT_VALUE = 123
fun new(key: Key = KEY, value: Int = DEFAULT_VALUE) = IntegerPreferenceDefault(key, value)
fun new(
key: Key = KEY,
value: Int = DEFAULT_VALUE
) = IntegerPreferenceDefault(key, value)
}

View File

@ -6,5 +6,9 @@ import cash.z.ecc.android.sdk.demoapp.preference.model.entry.StringPreferenceDef
object StringDefaultPreferenceFixture {
val KEY = Key("some_string_key") // $NON-NLS
const val DEFAULT_VALUE = "some_default_value" // $NON-NLS
fun new(key: Key = KEY, value: String = DEFAULT_VALUE) = StringPreferenceDefault(key, value)
fun new(
key: Key = KEY,
value: String = DEFAULT_VALUE
) = StringPreferenceDefault(key, value)
}

View File

@ -16,32 +16,38 @@ class BooleanPreferenceDefaultTest {
}
@Test
fun value_default_true() = runTest {
val entry = BooleanPreferenceDefaultFixture.newTrue()
assertTrue(entry.getValue(MockPreferenceProvider()))
}
@Test
fun value_default_false() = runTest {
val entry = BooleanPreferenceDefaultFixture.newFalse()
assertFalse(entry.getValue(MockPreferenceProvider()))
}
@Test
fun value_from_config_false() = runTest {
val entry = BooleanPreferenceDefaultFixture.newTrue()
val mockPreferenceProvider = MockPreferenceProvider {
mutableMapOf(BooleanPreferenceDefaultFixture.KEY.key to false.toString())
fun value_default_true() =
runTest {
val entry = BooleanPreferenceDefaultFixture.newTrue()
assertTrue(entry.getValue(MockPreferenceProvider()))
}
assertFalse(entry.getValue(mockPreferenceProvider))
}
@Test
fun value_from_config_true() = runTest {
val entry = BooleanPreferenceDefaultFixture.newTrue()
val mockPreferenceProvider = MockPreferenceProvider {
mutableMapOf(BooleanPreferenceDefaultFixture.KEY.key to true.toString())
fun value_default_false() =
runTest {
val entry = BooleanPreferenceDefaultFixture.newFalse()
assertFalse(entry.getValue(MockPreferenceProvider()))
}
@Test
fun value_from_config_false() =
runTest {
val entry = BooleanPreferenceDefaultFixture.newTrue()
val mockPreferenceProvider =
MockPreferenceProvider {
mutableMapOf(BooleanPreferenceDefaultFixture.KEY.key to false.toString())
}
assertFalse(entry.getValue(mockPreferenceProvider))
}
@Test
fun value_from_config_true() =
runTest {
val entry = BooleanPreferenceDefaultFixture.newTrue()
val mockPreferenceProvider =
MockPreferenceProvider {
mutableMapOf(BooleanPreferenceDefaultFixture.KEY.key to true.toString())
}
assertTrue(entry.getValue(mockPreferenceProvider))
}
assertTrue(entry.getValue(mockPreferenceProvider))
}
}

View File

@ -15,20 +15,23 @@ class IntegerPreferenceDefaultTest {
}
@Test
fun value_default() = runTest {
val entry = IntegerPreferenceDefaultFixture.new()
assertEquals(IntegerPreferenceDefaultFixture.DEFAULT_VALUE, entry.getValue(MockPreferenceProvider()))
}
@Test
fun value_override() = runTest {
val expected = IntegerPreferenceDefaultFixture.DEFAULT_VALUE + 5
val entry = IntegerPreferenceDefaultFixture.new()
val mockPreferenceProvider = MockPreferenceProvider {
mutableMapOf(StringDefaultPreferenceFixture.KEY.key to expected.toString())
fun value_default() =
runTest {
val entry = IntegerPreferenceDefaultFixture.new()
assertEquals(IntegerPreferenceDefaultFixture.DEFAULT_VALUE, entry.getValue(MockPreferenceProvider()))
}
assertEquals(expected, entry.getValue(mockPreferenceProvider))
}
@Test
fun value_override() =
runTest {
val expected = IntegerPreferenceDefaultFixture.DEFAULT_VALUE + 5
val entry = IntegerPreferenceDefaultFixture.new()
val mockPreferenceProvider =
MockPreferenceProvider {
mutableMapOf(StringDefaultPreferenceFixture.KEY.key to expected.toString())
}
assertEquals(expected, entry.getValue(mockPreferenceProvider))
}
}

View File

@ -14,19 +14,22 @@ class StringPreferenceDefaultTest {
}
@Test
fun value_default() = runTest {
val entry = StringDefaultPreferenceFixture.new()
assertEquals(StringDefaultPreferenceFixture.DEFAULT_VALUE, entry.getValue(MockPreferenceProvider()))
}
@Test
fun value_override() = runTest {
val entry = StringDefaultPreferenceFixture.new()
val mockPreferenceProvider = MockPreferenceProvider {
mutableMapOf(StringDefaultPreferenceFixture.KEY.key to "override")
fun value_default() =
runTest {
val entry = StringDefaultPreferenceFixture.new()
assertEquals(StringDefaultPreferenceFixture.DEFAULT_VALUE, entry.getValue(MockPreferenceProvider()))
}
assertEquals("override", entry.getValue(mockPreferenceProvider))
}
@Test
fun value_override() =
runTest {
val entry = StringDefaultPreferenceFixture.new()
val mockPreferenceProvider =
MockPreferenceProvider {
mutableMapOf(StringDefaultPreferenceFixture.KEY.key to "override")
}
assertEquals("override", entry.getValue(mockPreferenceProvider))
}
}

View File

@ -23,8 +23,9 @@ open class UiTestPrerequisites {
}
private fun isScreenOn(): Boolean {
val powerService = ApplicationProvider.getApplicationContext<Context>()
.getSystemService(Context.POWER_SERVICE) as PowerManager
val powerService =
ApplicationProvider.getApplicationContext<Context>()
.getSystemService(Context.POWER_SERVICE) as PowerManager
return powerService.isInteractive
}
@ -38,7 +39,7 @@ open class UiTestPrerequisites {
val keyguardService = (
ApplicationProvider.getApplicationContext<Context>()
.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
)
)
return keyguardService.isKeyguardLocked
}

View File

@ -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<Int>()
flow.collect {
values.add(it)
val values = mutableListOf<Int>()
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) }
}
}
}

View File

@ -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<Context>().getString(resId)
fun getStringResource(
@StringRes resId: Int
) = ApplicationProvider.getApplicationContext<Context>().getString(resId)
fun getStringResourceWithArgs(@StringRes resId: Int, formatArgs: Array<Any>) =
ApplicationProvider.getApplicationContext<Context>().getString(resId, *formatArgs)
fun getStringResourceWithArgs(
@StringRes resId: Int,
formatArgs: Array<Any>
) = ApplicationProvider.getApplicationContext<Context>().getString(resId, *formatArgs)
fun isLocaleRTL(locale: Locale) = TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL

View File

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

View File

@ -4,7 +4,6 @@ import androidx.multidex.MultiDexApplication
import cash.z.ecc.android.sdk.internal.Twig
class App : MultiDexApplication() {
override fun onCreate() {
super.onCreate()

View File

@ -21,7 +21,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
abstract class BaseDemoFragment<T : ViewBinding> : 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<T : ViewBinding> : 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)

View File

@ -33,6 +33,7 @@ class ComposeActivity : ComponentActivity() {
}
@Composable
@Suppress("ktlint:standard:function-naming")
private fun MainContent() {
when (walletViewModel.secretState.collectAsStateWithLifecycle().value) {
SecretState.Loading -> {

View File

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

View File

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

View File

@ -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<BlockHeight?>(
runBlocking {
BlockHeight.ofLatestCheckpoint(
getApplication(),
ZcashNetwork.fromResources(application)
)
}
)
private val _birthdayHeight =
MutableStateFlow<BlockHeight?>(
runBlocking {
BlockHeight.ofLatestCheckpoint(
getApplication(),
ZcashNetwork.fromResources(application)
)
}
)
// publicly, this is read-only
val seedPhrase: StateFlow<String> get() = _seedPhrase
// publicly, this is read-only
val birthdayHeight: StateFlow<BlockHeight?> get() = _blockHeight
val birthdayHeight: StateFlow<BlockHeight?> get() = _birthdayHeight
private val lockoutMutex = Mutex()
private val lockoutIdFlow = MutableStateFlow<UUID?>(null)
@OptIn(ExperimentalCoroutinesApi::class)
private val synchronizerOrLockout: Flow<InternalSynchronizerStatus> = lockoutIdFlow.flatMapLatest { lockoutId ->
if (null != lockoutId) {
flowOf(InternalSynchronizerStatus.Lockout(lockoutId))
} else {
callbackFlow<InternalSynchronizerStatus> {
// 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<InternalSynchronizerStatus> =
lockoutIdFlow.flatMapLatest { lockoutId ->
if (null != lockoutId) {
flowOf(InternalSynchronizerStatus.Lockout(lockoutId))
} else {
callbackFlow<InternalSynchronizerStatus> {
// 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<Synchronizer?> = 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<Synchronizer?> =
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<InternalSynchronizerStatus.Lockout>()
.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()
}

View File

@ -5,7 +5,6 @@ import android.os.StrictMode
import cash.z.ecc.android.sdk.demoapp.util.AndroidApiVersion
object StrictModeHelper {
fun enableStrictMode() {
configureStrictMode()
}

View File

@ -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<Context, WalletCoordinator> {
private val lazy =
LazyWithArgument<Context, WalletCoordinator> {
/*
* 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)

View File

@ -23,7 +23,6 @@ import kotlinx.coroutines.launch
* that is used, update the `DemoConfig.seedWords` value.
*/
class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
private lateinit var viewingKey: UnifiedFullViewingKey
private fun displayAddress() {
@ -67,7 +66,10 @@ class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
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()

View File

@ -34,7 +34,6 @@ import kotlinx.coroutines.launch
*/
@Suppress("TooManyFunctions")
class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBalanceBinding =
FragmentGetBalanceBinding.inflate(layoutInflater)
@ -48,7 +47,10 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
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<FragmentGetBalanceBinding>() {
}
}
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<FragmentGetBalanceBinding>() {
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<FragmentGetBalanceBinding>() {
}
@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()
}

View File

@ -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<FragmentGetBlockBinding>() {
// 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<FragmentGetBlockBinding>() {
// 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<FragmentGetBlockBinding>() {
//
// Base Fragment overrides
//
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBlockBinding =
FragmentGetBlockBinding.inflate(layoutInflater)
@Suppress("MaxLineLength")
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBlockBinding = FragmentGetBlockBinding.inflate(layoutInflater)
}

View File

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

View File

@ -11,7 +11,6 @@ import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetLatestHeightBinding
* instance.
*/
class GetLatestHeightFragment : BaseDemoFragment<FragmentGetLatestHeightBinding>() {
// TODO [#973]: Eliminate old UI demo-app
// TODO [#973]: https://github.com/zcash/zcash-android-wallet-sdk/issues/973
private fun displayLatestHeight() {

View File

@ -24,7 +24,6 @@ import kotlinx.coroutines.launch
* HomeFragment.
*/
class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
private lateinit var seedPhrase: String
private lateinit var seed: ByteArray
@ -47,17 +46,19 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
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<FragmentGetPrivateKeyBinding>() {
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
displayKeys()
}

View File

@ -18,11 +18,12 @@ import kotlinx.coroutines.launch
*/
@Suppress("TooManyFunctions")
class HomeFragment : BaseDemoFragment<FragmentHomeBinding>() {
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)

View File

@ -111,7 +111,10 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
super.onCreate(savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
initTransactionUI()
monitorChanges()

View File

@ -23,7 +23,6 @@ class TransactionAdapter : ListAdapter<TransactionOverview, TransactionViewHolde
) = oldItem == newItem
}
) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int

View File

@ -33,7 +33,12 @@ class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
@Suppress("MagicNumber")
fun bindTo(isInbound: Boolean, minedHeight: BlockHeight?, time: Long?, value: Zatoshi) {
fun bindTo(
isInbound: Boolean,
minedHeight: BlockHeight?,
time: Long?,
value: Zatoshi
) {
amountText.text = value.convertZatoshiToZecString()
timeText.text = minedHeight?.let {
time?.let {

View File

@ -49,8 +49,8 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
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<FragmentListUtxosBinding>() {
// 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<FragmentListUtxosBinding>() {
Twig.debug { message }
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
initUi()
monitorStatus()

View File

@ -23,7 +23,6 @@ class UtxoAdapter : ListAdapter<TransactionOverview, UtxoViewHolder>(
) = oldItem == newItem
}
) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int

View File

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

View File

@ -34,7 +34,6 @@ import kotlinx.coroutines.launch
*/
@Suppress("TooManyFunctions")
class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
private lateinit var amountInput: TextView
private lateinit var addressInput: TextView
@ -67,12 +66,14 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
//
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<FragmentSendBinding>() {
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<FragmentSendBinding>() {
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<FragmentSendBinding>() {
//
// BaseDemoFragment overrides
//
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentSendBinding =
FragmentSendBinding.inflate(layoutInflater)
@Suppress("MaxLineLength")
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentSendBinding = FragmentSendBinding.inflate(layoutInflater)
}

View File

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

View File

@ -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<Unit> = callbackFlow<Unit> {
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
// Callback on main thread
override fun observe(key: Key): Flow<Unit> =
callbackFlow<Unit> {
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)
}

View File

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

View File

@ -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<Context, PreferenceProvider> {
AndroidPreferenceProvider.newEncrypted(it, PREF_FILENAME)
}
private val lazy =
SuspendingLazy<Context, PreferenceProvider> {
AndroidPreferenceProvider.newEncrypted(it, PREF_FILENAME)
}
suspend fun getInstance(context: Context) = lazy.getInstance(context)
}

View File

@ -9,7 +9,6 @@ import org.json.JSONObject
data class PersistableWalletPreferenceDefault(
override val key: Key
) : PreferenceDefault<PersistableWallet?> {
override suspend fun getValue(preferenceProvider: PreferenceProvider) =
preferenceProvider.getString(key)?.let { PersistableWallet.from(JSONObject(it)) }

View File

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

View File

@ -6,19 +6,22 @@ data class BooleanPreferenceDefault(
override val key: Key,
private val defaultValue: Boolean
) : PreferenceDefault<Boolean> {
@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())
}
}

View File

@ -6,18 +6,21 @@ data class IntegerPreferenceDefault(
override val key: Key,
private val defaultValue: Int
) : PreferenceDefault<Int> {
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())
}
}

View File

@ -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<T> {
val key: Key
/**
@ -34,14 +32,18 @@ interface PreferenceDefault<T> {
* @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<T> = preferenceProvider.observe(key)
.map { getValue(preferenceProvider) }
.distinctUntilChanged()
fun observe(preferenceProvider: PreferenceProvider): Flow<T> =
preferenceProvider.observe(key)
.map { getValue(preferenceProvider) }
.distinctUntilChanged()
}

View File

@ -6,11 +6,14 @@ data class StringPreferenceDefault(
override val key: Key,
private val defaultValue: String
) : PreferenceDefault<String> {
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)
}
}

View File

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

View File

@ -18,41 +18,43 @@ import kotlin.time.TimeSource
fun <T> Flow<T>.throttle(
duration: Duration,
timeSource: TimeSource = TimeSource.Monotonic
): Flow<T> = flow {
coroutineScope {
val context = coroutineContext
val mutex = Mutex()
): Flow<T> =
flow {
coroutineScope {
val context = coroutineContext
val mutex = Mutex()
var timeMark = timeSource.markNow()
var delayEmit: Deferred<Unit>? = 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<Unit>? = 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()
}
}
}
}
}
}
}

View File

@ -29,6 +29,7 @@ class ScreenTimeout {
val LocalScreenTimeout = compositionLocalOf { ScreenTimeout() }
@Composable
@Suppress("ktlint:standard:function-naming")
fun DisableScreenTimeout() {
val screenTimeout = LocalScreenTimeout.current
DisposableEffect(screenTimeout) {

View File

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

View File

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

View File

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

View File

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

View File

@ -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<SecretState> = 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<SecretState.Ready>()
.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<SecretState> =
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<SecretState.Ready>()
.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<WalletSnapshot?> = synchronizer
.flatMapLatest {
if (null == it) {
flowOf(null)
} else {
it.toWalletSnapshot()
val walletSnapshot: StateFlow<WalletSnapshot?> =
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<WalletAddresses?> = synchronizer
.filterNotNull()
.map {
WalletAddresses.new(it)
}.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
null
)
val addresses: StateFlow<WalletAddresses?> =
synchronizer
.filterNotNull()
.map {
WalletAddresses.new(it)
}.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
null
)
private val mutableSendState = MutableStateFlow<SendState>(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<SynchronizerError?> = callbackFlow {
// just for initial default value emit
trySend(null)
private fun Synchronizer.toCommonError(): Flow<SynchronizerError?> =
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?

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,6 @@ import kotlin.test.assertFalse
import kotlin.test.assertTrue
class BenchmarkingExtTest {
@Test
@SmallTest
fun check_build_config() {

View File

@ -5,7 +5,6 @@ import kotlin.test.Test
import kotlin.test.assertEquals
class BlockRangeFixtureTest {
@Test
@SmallTest
fun compare_default_values() {

View File

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

View File

@ -6,7 +6,6 @@ import kotlin.test.Ignore
import kotlin.test.Test
class ChannelFactoryTest {
private val channelFactory = AndroidChannelFactory(getAppContext())
@Test

View File

@ -10,7 +10,6 @@ import kotlin.test.assertNull
import kotlin.test.assertTrue
class GrpcStatusResolverTest {
@Test
@SmallTest
fun resolve_explicitly_caught_server_error_test() {

View File

@ -6,7 +6,6 @@ import kotlin.test.Ignore
import kotlin.test.Test
class LightWalletClientImplTest {
private val channelFactory = AndroidChannelFactory(getAppContext())
@Test

View File

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

View File

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

View File

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

View File

@ -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<BlockHeightUnsafe> = DEFAULT_FILE_BLOCK_RANGE
): Sequence<CompactBlockUnsafe> {
@Suppress("MaxLineLength")
fun newSequence(blocksHeightRange: ClosedRange<BlockHeightUnsafe> = DEFAULT_FILE_BLOCK_RANGE): Sequence<CompactBlockUnsafe> {
val blocks = mutableListOf<CompactBlockUnsafe>()
for (blockHeight in blocksHeightRange.start.value..blocksHeightRange.endInclusive.value) {
@ -28,9 +26,8 @@ object ListOfCompactBlocksFixture {
return blocks.asSequence()
}
fun newFlow(
blocksHeightRange: ClosedRange<BlockHeightUnsafe> = DEFAULT_FILE_BLOCK_RANGE
): Flow<CompactBlockUnsafe> {
@Suppress("MaxLineLength")
fun newFlow(blocksHeightRange: ClosedRange<BlockHeightUnsafe> = DEFAULT_FILE_BLOCK_RANGE): Flow<CompactBlockUnsafe> {
return newSequence(blocksHeightRange).asFlow()
}
}

View File

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

View File

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

View File

@ -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<Service.RawTransaction>?, tipHeight: BlockHeightUnsafe) {
fun stageTransactions(
txs: Iterator<Service.RawTransaction>?,
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,

Some files were not shown because too many files have changed in this diff Show More