[#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:
parent
e550cd9943
commit
73d7afc2a4
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -24,7 +24,6 @@ class JniUnifiedSpendingKey(
|
|||
*/
|
||||
val bytes: ByteArray
|
||||
) {
|
||||
|
||||
// Override to prevent leaking key to logs
|
||||
override fun toString() = "JniUnifiedSpendingKey(account=$account, bytes=***)"
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ open class DarksideTest : ScopedTest() {
|
|||
ranOnce = true
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var ranOnce = false
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -4,7 +4,6 @@ import androidx.multidex.MultiDexApplication
|
|||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
|
||||
class App : MultiDexApplication() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -33,6 +33,7 @@ class ComposeActivity : ComponentActivity() {
|
|||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("ktlint:standard:function-naming")
|
||||
private fun MainContent() {
|
||||
when (walletViewModel.secretState.collectAsStateWithLifecycle().value) {
|
||||
SecretState.Loading -> {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import android.os.StrictMode
|
|||
import cash.z.ecc.android.sdk.demoapp.util.AndroidApiVersion
|
||||
|
||||
object StrictModeHelper {
|
||||
|
||||
fun enableStrictMode() {
|
||||
configureStrictMode()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -23,7 +23,6 @@ class TransactionAdapter : ListAdapter<TransactionOverview, TransactionViewHolde
|
|||
) = oldItem == newItem
|
||||
}
|
||||
) {
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -23,7 +23,6 @@ class UtxoAdapter : ListAdapter<TransactionOverview, UtxoViewHolder>(
|
|||
) = oldItem == newItem
|
||||
}
|
||||
) {
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)) }
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ class ScreenTimeout {
|
|||
val LocalScreenTimeout = compositionLocalOf { ScreenTimeout() }
|
||||
|
||||
@Composable
|
||||
@Suppress("ktlint:standard:function-naming")
|
||||
fun DisableScreenTimeout() {
|
||||
val screenTimeout = LocalScreenTimeout.current
|
||||
DisposableEffect(screenTimeout) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
) {
|
||||
|
|
|
@ -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()
|
||||
) {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import kotlin.test.assertFalse
|
|||
import kotlin.test.assertTrue
|
||||
|
||||
class BenchmarkingExtTest {
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
fun check_build_config() {
|
||||
|
|
|
@ -5,7 +5,6 @@ import kotlin.test.Test
|
|||
import kotlin.test.assertEquals
|
||||
|
||||
class BlockRangeFixtureTest {
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
fun compare_default_values() {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import kotlin.test.Ignore
|
|||
import kotlin.test.Test
|
||||
|
||||
class ChannelFactoryTest {
|
||||
|
||||
private val channelFactory = AndroidChannelFactory(getAppContext())
|
||||
|
||||
@Test
|
||||
|
|
|
@ -10,7 +10,6 @@ import kotlin.test.assertNull
|
|||
import kotlin.test.assertTrue
|
||||
|
||||
class GrpcStatusResolverTest {
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
fun resolve_explicitly_caught_server_error_test() {
|
||||
|
|
|
@ -6,7 +6,6 @@ import kotlin.test.Ignore
|
|||
import kotlin.test.Test
|
||||
|
||||
class LightWalletClientImplTest {
|
||||
|
||||
private val channelFactory = AndroidChannelFactory(getAppContext())
|
||||
|
||||
@Test
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue