Merge branch 'main' into merge-detekt-warning-fixes

This commit is contained in:
Jack Grigg 2022-08-23 21:28:01 +01:00
commit 0b80bff23d
61 changed files with 687 additions and 723 deletions

View File

@ -1,5 +1,7 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="RIGHT_MARGIN" value="120" />
<option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>

View File

@ -30,7 +30,6 @@ tasks {
exclude("**/jvmTest/**")
exclude("**/androidTest/**")
config.setFrom(files("${rootProject.projectDir}/tools/detekt.yml"))
baseline.set(file("$rootDir/tools/detekt-baseline.xml"))
buildUponDefaultConfig = true
}

View File

@ -20,12 +20,14 @@ import androidx.viewbinding.ViewBinding
import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.internal.service.LightWalletService
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.model.defaultForNetwork
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.navigation.NavigationView
@Suppress("TooManyFunctions")
class MainActivity :
AppCompatActivity(),
ClipboardManager.OnPrimaryClipChangedListener,
@ -155,15 +157,19 @@ class MainActivity :
/* DrawerListener implementation */
override fun onDrawerStateChanged(newState: Int) {
twig("Drawer state changed to: $newState.")
}
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
twig("Drawer slides with offset: $slideOffset.")
}
override fun onDrawerClosed(drawerView: View) {
twig("Drawer closed.")
}
override fun onDrawerOpened(drawerView: View) {
twig("Drawer opened.")
hideKeyboard()
}
}

View File

@ -2,6 +2,7 @@ package cash.z.ecc.android.sdk.demoapp
import androidx.lifecycle.ViewModel
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.sdk.internal.twig
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -10,7 +11,7 @@ import kotlinx.coroutines.flow.StateFlow
*/
class SharedViewModel : ViewModel() {
private val _seedPhrase = MutableStateFlow(DemoConstants.initialSeedWords)
private val _seedPhrase = MutableStateFlow(DemoConstants.INITIAL_SEED_WORDS)
// publicly, this is read-only
val seedPhrase: StateFlow<String> get() = _seedPhrase
@ -25,10 +26,21 @@ class SharedViewModel : ViewModel() {
}
private fun isValidSeedPhrase(phrase: String?): Boolean {
if (phrase.isNullOrEmpty()) return false
if (phrase.isNullOrEmpty()) {
return false
}
return try {
Mnemonics.MnemonicCode(phrase).validate()
true
} catch (t: Throwable) { false }
} catch (e: Mnemonics.WordCountException) {
twig("Seed phrase validation failed with WordCountException: ${e.message}, cause: ${e.cause}")
false
} catch (e: Mnemonics.InvalidWordException) {
twig("Seed phrase validation failed with InvalidWordException: ${e.message}, cause: ${e.cause}")
false
} catch (e: Mnemonics.ChecksumException) {
twig("Seed phrase validation failed with ChecksumException: ${e.message}, cause: ${e.cause}")
false
}
}
}

View File

@ -56,7 +56,8 @@ class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
}
}
// TODO: show an example with the synchronizer
// TODO [#677]: Show an example with the synchronizer
// TODO [#677]: https://github.com/zcash/zcash-android-wallet-sdk/issues/677
//
// Android Lifecycle overrides

View File

@ -83,6 +83,7 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
synchronizer.saplingBalances.filterNotNull().collectWith(lifecycleScope, ::onBalance)
}
@Suppress("MagicNumber")
private fun onBalance(balance: WalletBalance) {
binding.textBalance.text = """
Available balance: ${balance.available.convertZatoshiToZecString(12)}
@ -100,6 +101,7 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
}
}
@Suppress("MagicNumber")
private fun onProgress(i: Int) {
if (i < 100) {
binding.textStatus.text = "Downloading blocks...$i%"

View File

@ -43,10 +43,15 @@ class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
}
@Suppress("UNUSED_PARAMETER")
private fun onApply(_unused: View? = null) {
private fun onApply(unused: View? = null) {
val network = ZcashNetwork.fromResources(requireApplicationContext())
val newHeight = min(binding.textBlockHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)
val newHeight = min(
binding.textBlockHeight.text.toString().toLongOrNull()
?: network.saplingActivationHeight.value,
network.saplingActivationHeight.value
)
@Suppress("TooGenericExceptionCaught")
try {
setBlockHeight(BlockHeight.new(network, newHeight))
} catch (t: Throwable) {

View File

@ -51,7 +51,7 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
val inCount = sumOf { block -> block.vtxList.sumOf { it.spendsCount } }
val processTime = System.currentTimeMillis() - start - fetchDelta
@Suppress("MaxLineLength")
@Suppress("MaxLineLength", "MagicNumber")
"""
<b>total blocks:</b> ${count.withCommas()}
<br/><b>fetch time:</b> ${if (fetchDelta > 1000) "%.2f sec".format(fetchDelta / 1000.0) else "%d ms".format(fetchDelta)}
@ -75,11 +75,20 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
}
@Suppress("UNUSED_PARAMETER")
private fun onApply(_unused: View) {
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 {
with(binding.buttonApply) {
isEnabled = false

View File

@ -42,6 +42,7 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
// derive the keys from the seed:
// demonstrate deriving spending keys for five accounts but only take the first one
lifecycleScope.launchWhenStarted {
@Suppress("MagicNumber")
val spendingKey = DerivationTool.deriveSpendingKeys(
seed,
ZcashNetwork.fromResources(requireApplicationContext()),

View File

@ -5,7 +5,6 @@ import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentHomeBinding
@ -18,10 +17,9 @@ import kotlinx.coroutines.launch
* data just for sanity. The goal is for each demo to be self-contained so that the behavior is
* repeatable and independent of pre-existing state.
*/
@Suppress("TooManyFunctions")
class HomeFragment : BaseDemoFragment<FragmentHomeBinding>() {
private val homeViewModel: HomeViewModel by viewModels()
override fun inflateBinding(layoutInflater: LayoutInflater) =
FragmentHomeBinding.inflate(layoutInflater)

View File

@ -31,6 +31,7 @@ import kotlinx.coroutines.runBlocking
* By default, the SDK uses a PagedTransactionRepository to provide transaction contents from the
* database in a paged format that works natively with RecyclerViews.
*/
@Suppress("TooManyFunctions")
class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBinding>() {
private lateinit var initializer: Initializer
private lateinit var synchronizer: Synchronizer
@ -97,6 +98,7 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
if (info.isScanning) binding.textInfo.text = "Scanning blocks...${info.scanProgress}%"
}
@Suppress("MagicNumber")
private fun onProgress(i: Int) {
if (i < 100) binding.textInfo.text = "Downloading blocks...$i%"
}

View File

@ -22,6 +22,7 @@ class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
private val icon = itemView.findViewById<ImageView>(R.id.image_transaction_type)
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
@Suppress("MagicNumber")
fun bindTo(transaction: ConfirmedTransaction?) {
val isInbound = transaction?.toAddress.isNullOrEmpty()
amountText.text = transaction?.valueInZatoshi.convertZatoshiToZecString()
@ -32,7 +33,9 @@ class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
icon.rotation = if (isInbound) 0f else 180f
icon.rotation = if (isInbound) 0f else 180f
icon.setColorFilter(ContextCompat.getColor(itemView.context, if (isInbound) R.color.tx_inbound else R.color.tx_outbound))
icon.setColorFilter(
ContextCompat.getColor(itemView.context, if (isInbound) R.color.tx_inbound else R.color.tx_outbound)
)
}
private fun getMemoString(transaction: ConfirmedTransaction?): String {

View File

@ -44,6 +44,7 @@ import kotlin.math.max
* By default, the SDK uses a PagedTransactionRepository to provide transaction contents from the
* database in a paged format that works natively with RecyclerViews.
*/
@Suppress("TooManyFunctions")
class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
private lateinit var seed: ByteArray
private lateinit var initializer: Initializer
@ -88,7 +89,9 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
private fun initUi() {
binding.inputAddress.setText(address)
binding.inputRangeStart.setText(ZcashNetwork.fromResources(requireApplicationContext()).saplingActivationHeight.toString())
binding.inputRangeStart.setText(
ZcashNetwork.fromResources(requireApplicationContext()).saplingActivationHeight.toString()
)
binding.inputRangeEnd.setText(getUxtoEndHeight(requireApplicationContext()).value.toString())
binding.buttonLoad.setOnClickListener {
@ -148,6 +151,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
withContext(Dispatchers.IO) {
finalCount = (synchronizer as SdkSynchronizer).getTransactionCount()
withContext(Dispatchers.Main) {
@Suppress("MagicNumber")
delay(100)
updateStatus("Also found ${finalCount - initialCount} shielded txs")
}
@ -188,7 +192,9 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
var initialCount: Int = 0
var finalCount: Int = 0
fun resetInBackground() {
@Suppress("TooGenericExceptionCaught")
private fun resetInBackground() {
try {
lifecycleScope.launch {
withContext(Dispatchers.IO) {
@ -242,6 +248,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
if (info.isScanning) binding.textStatus.text = "Scanning blocks...${info.scanProgress}%"
}
@Suppress("MagicNumber")
private fun onProgress(i: Int) {
if (i < 100) binding.textStatus.text = "Downloading blocks...$i%"
}

View File

@ -19,6 +19,7 @@ class UtxoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val timeText = itemView.findViewById<TextView>(R.id.text_transaction_timestamp)
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
@Suppress("MagicNumber")
fun bindTo(transaction: ConfirmedTransaction?) {
amountText.text = transaction?.valueInZatoshi.convertZatoshiToZecString()
timeText.text =

View File

@ -45,6 +45,7 @@ import kotlinx.coroutines.runBlocking
* PendingTransaction objects which represent the active state of the transaction that was sent.
* Any time the state of that transaction changes, a new instance will be emitted.
*/
@Suppress("TooManyFunctions")
class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
private lateinit var synchronizer: Synchronizer
@ -81,7 +82,9 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
}.let { initializer ->
synchronizer = Synchronizer.newBlocking(initializer)
}
spendingKey = runBlocking { DerivationTool.deriveSpendingKeys(seed, ZcashNetwork.fromResources(requireApplicationContext())).first() }
spendingKey = runBlocking {
DerivationTool.deriveSpendingKeys(seed, ZcashNetwork.fromResources(requireApplicationContext())).first()
}
}
//
@ -111,10 +114,10 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
private fun initSendUi() {
amountInput = binding.inputAmount.apply {
setText(DemoConstants.sendAmount.toZecString())
setText(DemoConstants.SEND_AMOUNT.toZecString())
}
addressInput = binding.inputAddress.apply {
setText(DemoConstants.toAddress)
setText(DemoConstants.TO_ADDRESS)
}
binding.buttonSend.setOnClickListener(::onSend)
}
@ -140,6 +143,7 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
}
}
@Suppress("MagicNumber")
private fun onProgress(i: Int) {
if (i < 100) {
binding.textStatus.text = "Downloading blocks...$i%"
@ -153,6 +157,7 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
if (info.isScanning) binding.textStatus.text = "Scanning blocks...${info.scanProgress}%"
}
@Suppress("MagicNumber")
private fun onBalance(balance: WalletBalance?) {
this.balance = balance
if (!isSyncing) {
@ -177,14 +182,17 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
mainActivity()?.hideKeyboard()
}
@Suppress("ComplexMethod")
private fun onPendingTxUpdated(pendingTransaction: PendingTransaction?) {
val id = pendingTransaction?.id ?: -1
val message = when {
pendingTransaction == null -> "Transaction not found"
pendingTransaction.isMined() -> "Transaction Mined (id: $id)!\n\nSEND COMPLETE".also { isSending = false }
pendingTransaction.isSubmitSuccess() -> "Successfully submitted transaction!\nAwaiting confirmation..."
pendingTransaction.isFailedEncoding() -> "ERROR: failed to encode transaction! (id: $id)".also { isSending = false }
pendingTransaction.isFailedSubmit() -> "ERROR: failed to submit transaction! (id: $id)".also { isSending = false }
pendingTransaction.isFailedEncoding() ->
"ERROR: failed to encode transaction! (id: $id)".also { isSending = false }
pendingTransaction.isFailedSubmit() ->
"ERROR: failed to submit transaction! (id: $id)".also { isSending = false }
pendingTransaction.isCreated() -> "Transaction creation complete! (id: $id)"
pendingTransaction.isCreating() -> "Creating transaction!".also { onResetInfo() }
else -> "Transaction updated!".also { twig("Unhandled TX state: $pendingTransaction") }

View File

@ -20,6 +20,7 @@ fun Number?.withCommas() = this?.let { "%,d".format(it) } ?: "Unknown"
/**
* Convert date time in seconds to relative time like (4 days ago).
*/
@Suppress("MagicNumber")
fun Int?.toRelativeTime(context: Context) =
this?.let { timeInSeconds ->
DateUtils.getRelativeDateTimeString(

View File

@ -10,9 +10,9 @@ import java.util.Locale
fun ZcashNetwork.Companion.fromResources(context: Context): ZcashNetwork {
val networkNameFromResources = context.getString(R.string.network_name).lowercase(Locale.ROOT)
return if (networkNameFromResources == Testnet.networkName) {
ZcashNetwork.Testnet
Testnet
} else if (networkNameFromResources.lowercase(Locale.ROOT) == Mainnet.networkName) {
ZcashNetwork.Mainnet
Mainnet
} else {
throw IllegalArgumentException("Unknown network name: $networkNameFromResources")
}

View File

@ -1,14 +1,15 @@
package cash.z.ecc.android.sdk.demoapp
object DemoConstants {
val sendAmount: Double = 0.000018
const val SEND_AMOUNT: Double = 0.000018
// corresponds to address: zs15tzaulx5weua5c7l47l4pku2pw9fzwvvnsp4y80jdpul0y3nwn5zp7tmkcclqaca3mdjqjkl7hx
val initialSeedWords: String =
"wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame"
const val INITIAL_SEED_WORDS: String =
"wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair" +
" throw review deal slush frame"
// corresponds to seed: urban kind wise collect social marble riot primary craft lucky head cause syrup odor artist decorate rhythm phone style benefit portion bus truck top
val toAddress: String =
// corresponds to seed: urban kind wise collect social marble riot primary craft lucky head cause syrup odor
// artist decorate rhythm phone style benefit portion bus truck top
const val TO_ADDRESS: String =
"zs1lcdmue7rewgvzh3jd09sfvwq3sumu6hkhpk53q94kcneuffjkdg9e3tyxrugkmpza5c3c5e6eqh"
}

View File

@ -1,14 +1,16 @@
package cash.z.ecc.android.sdk.demoapp
object DemoConstants {
val utxoEndHeight: Int = 1075590
val sendAmount: Double = 0.00017
const val UTXO_END_HEIGHT: Int = 1075590
const val SEND_AMOUNT: Double = 0.00017
// corresponds to address: ztestsapling1zhqvuq8zdwa8nsnde7074kcfsat0w25n08jzuvz5skzcs6h9raxu898l48xwr8fmkny3zqqrgd9
val initialSeedWords: String =
"wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame"
const val INITIAL_SEED_WORDS: String =
"wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair" +
" throw review deal slush frame"
// corresponds to seed: urban kind wise collect social marble riot primary craft lucky head cause syrup odor artist decorate rhythm phone style benefit portion bus truck top
val toAddress: String =
// corresponds to seed: urban kind wise collect social marble riot primary craft lucky head cause syrup odor artist
// decorate rhythm phone style benefit portion bus truck top
const val TO_ADDRESS: String =
"ztestsapling1ddttvrm6ueug4vwlczs8daqjaul60aur4udnvcz9qdnjt9ekt2tsxheqvv3mn50wvhmzj4ge9rl"
}

View File

@ -153,7 +153,9 @@ android {
}
kotlinOptions {
// Tricky: fix: By default, the kotlin_module name will not include the version (in classes.jar/META-INF). Instead it has a colon, which breaks compilation on Windows. This is one way to set it explicitly to the proper value. See https://github.com/zcash/zcash-android-wallet-sdk/issues/222 for more info.
// Tricky: fix: By default, the kotlin_module name will not include the version (in classes.jar/META-INF).
// Instead it has a colon, which breaks compilation on Windows. This is one way to set it explicitly to the
// proper value. See https://github.com/zcash/zcash-android-wallet-sdk/issues/222 for more info.
freeCompilerArgs += listOf("-module-name", "$myArtifactId-${myVersion}_release")
}
@ -271,8 +273,10 @@ dependencies {
// Locked Versions
// these should be checked regularly and removed when possible
// solves error: Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules jetified-guava-26.0-android.jar (com.google.guava:guava:26.0-android) and listenablefuture-1.0.jar (com.google.guava:listenablefuture:1.0)
// per this recommendation from Chris Povirk, given guava's decision to split ListenableFuture away from Guava: https://groups.google.com/d/msg/guava-discuss/GghaKwusjcY/bCIAKfzOEwAJ
// solves error: Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules
// jetified-guava-26.0-android.jar (com.google.guava:guava:26.0-android) and listenablefuture-1.0.jar
// (com.google.guava:listenablefuture:1.0) per this recommendation from Chris Povirk, given guava's decision to
// split ListenableFuture away from Guava: https://groups.google.com/d/msg/guava-discuss/GghaKwusjcY/bCIAKfzOEwAJ
implementation(libs.guava)
// OKIO is a transitive dependency used when writing param files to disk. Like GSON, this can be
// replaced if needed. For compatibility, we match the library version used in grpc-okhttp:
@ -301,7 +305,8 @@ dependencies {
androidTestImplementation(libs.coroutines.okhttp)
androidTestImplementation(libs.kotlin.test)
androidTestImplementation(libs.kotlinx.coroutines.test)
// used by 'ru.gildor.corutines.okhttp.await' (to make simple suspended requests) and breaks on versions higher than 3.8.0
// used by 'ru.gildor.corutines.okhttp.await' (to make simple suspended requests) and breaks on versions higher
// than 3.8.0
androidTestImplementation(libs.okhttp)
// sample mnemonic plugin

View File

@ -9,6 +9,7 @@ import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.util.TestWallet
import kotlinx.coroutines.runBlocking
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@ -74,6 +75,10 @@ class SanityTest(
}
@Test
@Ignore(
"This test needs to be refactored to a separate test module. It causes SSLHandshakeException: Chain " +
"validation failed on CI"
)
fun testLatestHeight() = runBlocking {
if (wallet.networkName == "mainnet") {
val expectedHeight = BlockExplorer.fetchLatestHeight()
@ -94,6 +99,10 @@ class SanityTest(
}
@Test
@Ignore(
"This test needs to be refactored to a separate test module. It causes SSLHandshakeException: Chain " +
"validation failed on CI"
)
fun testSingleBlockDownload() = runBlocking {
// Fetch height directly because the synchronizer hasn't started, yet. Then we test the
// result, only if there is no server communication problem.

View File

@ -13,6 +13,10 @@ import org.junit.runner.RunWith
import java.io.File
@RunWith(AndroidJUnit4::class)
@Ignore(
"These tests need to be refactored to a separate test module. They cause SSLHandshakeException: Chain " +
"validation failed on CI"
)
class SaplingParamToolTest {
val context: Context = InstrumentationRegistry.getInstrumentation().context

View File

@ -34,6 +34,7 @@ class Initializer private constructor(
suspend fun erase() = erase(context, network, alias)
@Suppress("TooManyFunctions")
class Config private constructor(
val viewingKeys: MutableList<UnifiedFullViewingKey> = mutableListOf(),
var alias: String = ZcashSdk.DEFAULT_ALIAS
@ -236,6 +237,7 @@ class Initializer private constructor(
numberOfAccounts: Int = 1
): Config =
apply {
@Suppress("SpreadOperator")
setViewingKeys(
*DerivationTool.deriveUnifiedFullViewingKeys(
seed,
@ -423,7 +425,7 @@ class Initializer private constructor(
*/
internal fun validateAlias(alias: String) {
require(
alias.length in 1..99 && alias[0].isLetter() &&
alias.length in ZcashSdk.ALIAS_MIN_LENGTH..ZcashSdk.ALIAS_MAX_LENGTH && alias[0].isLetter() &&
alias.all { it.isLetterOrDigit() || it == '_' }
) {
"ERROR: Invalid alias ($alias). For security, the alias must be shorter than 100 " +

View File

@ -98,6 +98,7 @@ import kotlin.coroutines.EmptyCoroutineContext
*/
@OptIn(kotlinx.coroutines.ObsoleteCoroutinesApi::class)
@FlowPreview
@Suppress("TooManyFunctions")
class SdkSynchronizer internal constructor(
private val storage: TransactionRepository,
private val txManager: OutboundTransactionManager,
@ -329,7 +330,9 @@ class SdkSynchronizer internal constructor(
// Storage APIs
//
// TODO: turn this section into the data access API. For now, just aggregate all the things that we want to do with the underlying data
// TODO [#682]: turn this section into the data access API. For now, just aggregate all the things that we want
// to do with the underlying data
// TODO [#682]: https://github.com/zcash/zcash-android-wallet-sdk/issues/682
suspend fun findBlockHash(height: BlockHeight): ByteArray? {
return (storage as? PagedTransactionRepository)?.findBlockHash(height)
@ -363,7 +366,8 @@ class SdkSynchronizer internal constructor(
suspend fun refreshAllBalances() {
refreshSaplingBalance()
refreshTransparentBalance()
// TODO: refresh orchard balance
// TODO [#682]: refresh orchard balance
// TODO [#682]: https://github.com/zcash/zcash-android-wallet-sdk/issues/682
twig("Warning: Orchard balance does not yet refresh. Only some of the plumbing is in place.")
}
@ -378,11 +382,7 @@ class SdkSynchronizer internal constructor(
}
suspend fun isValidAddress(address: String): Boolean {
try {
return !validateAddress(address).isNotValid
} catch (t: Throwable) {
}
return false
return !validateAddress(address).isNotValid
}
private fun CoroutineScope.onReady() = launch(CoroutineExceptionHandler(::onCriticalError)) {
@ -411,7 +411,8 @@ class SdkSynchronizer internal constructor(
is Enhancing -> ENHANCING
}.let { synchronizerStatus ->
// ignore enhancing status for now
// TODO: clean this up and handle enhancing gracefully
// TODO [#682]: clean this up and handle enhancing gracefully
// TODO [#682]: https://github.com/zcash/zcash-android-wallet-sdk/issues/682
if (synchronizerStatus != ENHANCING) _status.send(synchronizerStatus)
}
}.launchIn(this)
@ -440,13 +441,6 @@ class SdkSynchronizer internal constructor(
onCriticalErrorHandler?.invoke(error)
}
private fun onFailedSend(error: Throwable): Boolean {
twig("ERROR while submitting transaction: $error")
return onSubmissionErrorHandler?.invoke(error)?.also {
if (it) twig("submission error handler signaled that we should try again!")
} == true
}
private fun onProcessorError(error: Throwable): Boolean {
twig("ERROR while processing data: $error")
if (onProcessorErrorHandler == null) {
@ -496,6 +490,7 @@ class SdkSynchronizer internal constructor(
// refresh anyway if:
// - if it's the first time we finished scanning
// - if we check for blocks 5 times and find nothing was mined
@Suppress("MagicNumber")
val shouldRefresh = !scannedRange.isEmpty() || elapsedMillis > (ZcashSdk.POLL_INTERVAL * 5)
val reason = if (scannedRange.isEmpty()) "it's been a while" else "new blocks were scanned"
@ -527,11 +522,12 @@ class SdkSynchronizer internal constructor(
}
}
@Suppress("LongMethod", "ComplexMethod")
private suspend fun refreshPendingTransactions() {
twig("[cleanup] beginning to refresh and clean up pending transactions")
// TODO: this would be the place to clear out any stale pending transactions. Remove filter
// logic and then delete any pending transaction with sufficient confirmations (all in one
// db transaction).
// TODO [#682]: this would be the place to clear out any stale pending transactions. Remove filter logic and
// then delete any pending transaction with sufficient confirmations (all in one db transaction).
// TODO [#682]: https://github.com/zcash/zcash-android-wallet-sdk/issues/682
val allPendingTxs = txManager.getAll().first()
val lastScannedHeight = storage.lastScannedHeight()
@ -596,15 +592,23 @@ class SdkSynchronizer internal constructor(
}
.forEach {
val result = txManager.abort(it)
twig("[cleanup] FOUND EXPIRED pendingTX (lastScanHeight: $lastScannedHeight expiryHeight: ${it.expiryHeight}): and ${it.id} ${if (result > 0) "successfully removed" else "failed to remove"} it")
twig(
"[cleanup] FOUND EXPIRED pendingTX (lastScanHeight: $lastScannedHeight " +
" expiryHeight: ${it.expiryHeight}): and ${it.id} " +
"${if (result > 0) "successfully removed" else "failed to remove"} it"
)
}
twig("[cleanup] deleting expired transactions from storage", -1)
val expiredCount = storage.deleteExpired(lastScannedHeight)
if (expiredCount > 0) twig("[cleanup] deleted $expiredCount expired transaction(s)!")
if (expiredCount > 0) {
twig("[cleanup] deleted $expiredCount expired transaction(s)!")
}
hasCleaned = hasCleaned || (expiredCount > 0)
if (hasCleaned) refreshAllBalances()
if (hasCleaned) {
refreshAllBalances()
}
twig("[cleanup] done refreshing and cleaning up pending transactions", -1)
}
@ -711,6 +715,7 @@ class SdkSynchronizer internal constructor(
txManager.isValidUnifiedAddress(address)
override suspend fun validateAddress(address: String): AddressType {
@Suppress("TooGenericExceptionCaught")
return try {
if (isValidShieldedAddr(address)) {
Shielded
@ -779,7 +784,10 @@ object DefaultSynchronizerFactory {
)
}
// TODO [#242]: Don't hard code page size. It is a workaround for Uncaught Exception: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. and is probably related to FlowPagedList
// TODO [#242]: Don't hard code page size. It is a workaround for Uncaught Exception:
// android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy
// can touch its views. and is probably related to FlowPagedList
// TODO [#242]: https://github.com/zcash/zcash-android-wallet-sdk/issues/242
private const val DEFAULT_PAGE_SIZE = 1000
suspend fun defaultTransactionRepository(initializer: Initializer): TransactionRepository =
PagedTransactionRepository.new(

View File

@ -22,6 +22,7 @@ import kotlinx.coroutines.runBlocking
* support for coroutines, we favor their use in the SDK and incorporate that choice into this
* contract.
*/
@Suppress("TooManyFunctions")
interface Synchronizer {
//

View File

@ -61,6 +61,7 @@ import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
import kotlin.time.Duration.Companion.days
/**
* Responsible for processing the compact blocks that are received from the lightwallet server. This class encapsulates
@ -78,6 +79,7 @@ import kotlin.math.roundToInt
*/
@OptIn(kotlinx.coroutines.ObsoleteCoroutinesApi::class)
@OpenForTesting
@Suppress("TooManyFunctions", "LargeClass")
class CompactBlockProcessor internal constructor(
val downloader: CompactBlockDownloader,
private val repository: TransactionRepository,
@ -219,12 +221,15 @@ class CompactBlockProcessor internal constructor(
when (result) {
BlockProcessingResult.Reconnecting -> {
val napTime = calculatePollInterval(true)
twig("Unable to process new blocks because we are disconnected! Attempting to reconnect in ${napTime}ms")
twig(
"Unable to process new blocks because we are disconnected! Attempting to " +
"reconnect in ${napTime}ms"
)
delay(napTime)
}
BlockProcessingResult.NoBlocksToProcess, BlockProcessingResult.FailedEnhance -> {
val noWorkDone =
currentInfo.lastDownloadRange?.isEmpty() ?: true && currentInfo.lastScanRange?.isEmpty() ?: true
val noWorkDone = currentInfo.lastDownloadRange?.isEmpty()
?: true && currentInfo.lastScanRange?.isEmpty() ?: true
val summary = if (noWorkDone) {
"Nothing to process: no new blocks to download or scan"
} else {
@ -232,13 +237,18 @@ class CompactBlockProcessor internal constructor(
}
consecutiveChainErrors.set(0)
val napTime = calculatePollInterval()
twig("$summary${if (result == BlockProcessingResult.FailedEnhance) " (but there were enhancement errors! We ignore those, for now. Memos in this block range are probably missing! This will be improved in a future release.)" else ""}! Sleeping for ${napTime}ms (latest height: ${currentInfo.networkBlockHeight}).")
twig(
"$summary${if (result == BlockProcessingResult.FailedEnhance) " (but there were" +
" enhancement errors! We ignore those, for now. Memos in this block range are" +
" probably missing! This will be improved in a future release.)" else ""}! Sleeping" +
" for ${napTime}ms (latest height: ${currentInfo.networkBlockHeight})."
)
delay(napTime)
}
is BlockProcessingResult.Error -> {
if (consecutiveChainErrors.get() >= RETRIES) {
val errorMessage =
"ERROR: unable to resolve reorg at height $result after ${consecutiveChainErrors.get()} correction attempts!"
val errorMessage = "ERROR: unable to resolve reorg at height $result after " +
"${consecutiveChainErrors.get()} correction attempts!"
fail(CompactBlockProcessorException.FailedReorgRepair(errorMessage))
} else {
handleChainError(result.failedAtHeight)
@ -313,9 +323,10 @@ class CompactBlockProcessor internal constructor(
*/
private suspend fun updateRanges(): Boolean = withContext(IO) {
try {
// TODO: rethink this and make it easier to understand what's happening. Can we reduce this
// so that we only work with actual changing info rather than periodic snapshots? Do we need
// to calculate these derived values every time?
// TODO [#683]: rethink this and make it easier to understand what's happening. Can we reduce this
// so that we only work with actual changing info rather than periodic snapshots? Do we need
// to calculate these derived values every time?
// TODO [#683]: https://github.com/zcash/zcash-android-wallet-sdk/issues/683
ProcessorInfo(
networkBlockHeight = downloader.getLatestBlockHeight(),
lastScannedHeight = getLastScannedHeight(),
@ -335,7 +346,10 @@ class CompactBlockProcessor internal constructor(
networkBlockHeight = initialInfo.networkBlockHeight,
lastScannedHeight = initialInfo.lastScannedHeight,
lastDownloadedHeight = initialInfo.lastDownloadedHeight,
lastScanRange = if (initialInfo.lastScannedHeight != null && initialInfo.networkBlockHeight != null) {
lastScanRange = if (
initialInfo.lastScannedHeight != null &&
initialInfo.networkBlockHeight != null
) {
initialInfo.lastScannedHeight + 1..initialInfo.networkBlockHeight
} else {
null
@ -392,6 +406,7 @@ class CompactBlockProcessor internal constructor(
Twig.sprout("enhancing")
twig("Enhancing transaction details for blocks $lastScanRange")
setState(Enhancing)
@Suppress("TooGenericExceptionCaught")
return try {
val newTxs = repository.findNewTransactions(lastScanRange)
if (newTxs.isEmpty()) {
@ -411,17 +426,19 @@ class CompactBlockProcessor internal constructor(
twig("Done enhancing transaction details")
BlockProcessingResult.Success
} catch (t: Throwable) {
twig("Failed to enhance due to $t")
t.printStackTrace()
twig("Failed to enhance due to: ${t.message} caused by: ${t.cause}")
BlockProcessingResult.FailedEnhance
} finally {
Twig.clip("enhancing")
}
}
// TODO: we still need a way to identify those transactions that failed to be enhanced
// TODO [#683]: we still need a way to identify those transactions that failed to be enhanced
// TODO [#683]: https://github.com/zcash/zcash-android-wallet-sdk/issues/683
private suspend fun enhance(transaction: ConfirmedTransaction) = withContext(Dispatchers.IO) {
var downloaded = false
@Suppress("TooGenericExceptionCaught")
try {
twig("START: enhancing transaction (id:${transaction.id} block:${transaction.minedHeight})")
downloader.fetchTransaction(transaction.rawTransactionId)?.let { tx ->
@ -478,12 +495,16 @@ class CompactBlockProcessor internal constructor(
if (onSetupErrorListener?.invoke(error) != true) {
throw error
} else {
twig("Warning: An ${error::class.java.simpleName} was encountered while verifying setup but it was ignored by the onSetupErrorHandler. Ignoring message: ${error.message}")
twig(
"Warning: An ${error::class.java.simpleName} was encountered while verifying setup but " +
"it was ignored by the onSetupErrorHandler. Ignoring message: ${error.message}"
)
}
}
}
private suspend fun updateBirthdayHeight() {
@Suppress("TooGenericExceptionCaught")
try {
val betterBirthday = calculateBirthdayHeight()
if (betterBirthday > birthdayHeight) {
@ -496,13 +517,17 @@ class CompactBlockProcessor internal constructor(
}
var failedUtxoFetches = 0
@Suppress("MagicNumber")
internal suspend fun refreshUtxos(tAddress: String, startHeight: BlockHeight): Int? =
withContext(IO) {
var count: Int? = null
// todo: cleanup the way that we prevent this from running excessively
// TODO [683]: cleanup the way that we prevent this from running excessively
// For now, try for about 3 blocks per app launch. If the service fails it is
// probably disabled on ligthtwalletd, so then stop trying until the next app launch.
// TODO [#683]: https://github.com/zcash/zcash-android-wallet-sdk/issues/683
if (failedUtxoFetches < 9) { // there are 3 attempts per block
@Suppress("TooGenericExceptionCaught")
try {
retryUpTo(3) {
val result = downloader.lightWalletService.fetchUtxos(tAddress, startHeight)
@ -510,10 +535,17 @@ class CompactBlockProcessor internal constructor(
}
} catch (e: Throwable) {
failedUtxoFetches++
twig("Warning: Fetching UTXOs is repeatedly failing! We will only try about ${(9 - failedUtxoFetches + 2) / 3} more times then give up for this session.")
twig(
"Warning: Fetching UTXOs is repeatedly failing! We will only try about " +
"${(9 - failedUtxoFetches + 2) / 3} more times then give up for this session. " +
"Exception message: ${e.message}, caused by: ${e.cause}."
)
}
} else {
twig("Warning: gave up on fetching UTXOs for this session. It seems to unavailable on lightwalletd.")
twig(
"Warning: gave up on fetching UTXOs for this session. It seems to unavailable on " +
"lightwalletd."
)
}
count
}
@ -530,6 +562,7 @@ class CompactBlockProcessor internal constructor(
twig("Checking for UTXOs above height $aboveHeight")
result.forEach { utxo: Service.GetAddressUtxosReply ->
twig("Found UTXO at height ${utxo.height.toInt()} with ${utxo.valueZat} zatoshi")
@Suppress("TooGenericExceptionCaught")
try {
rustBackend.putUtxo(
tAddress,
@ -540,9 +573,14 @@ class CompactBlockProcessor internal constructor(
BlockHeight(utxo.height)
)
} catch (t: Throwable) {
// TODO: more accurately track the utxos that were skipped (in theory, this could fail for other reasons)
// TODO [#683]: more accurately track the utxos that were skipped (in theory, this could fail for other
// reasons)
// TODO [#683]: https://github.com/zcash/zcash-android-wallet-sdk/issues/683
skipped++
twig("Warning: Ignoring transaction at height ${utxo.height} @ index ${utxo.index} because it already exists")
twig(
"Warning: Ignoring transaction at height ${utxo.height} @ index ${utxo.index} because " +
"it already exists. Exception message: ${t.message}, caused by: ${t.cause}."
)
}
}
// return the number of UTXOs that were downloaded
@ -554,7 +592,9 @@ class CompactBlockProcessor internal constructor(
*
* @param range the range of blocks to download.
*/
@VisibleForTesting // allow mocks to verify how this is called, rather than the downloader, which is more complex
@VisibleForTesting
// allow mocks to verify how this is called, rather than the downloader, which is more complex
@Suppress("MagicNumber")
internal suspend fun downloadNewBlocks(range: ClosedRange<BlockHeight>?) =
withContext<Unit>(IO) {
if (null == range || range.isEmpty()) {
@ -571,7 +611,10 @@ class CompactBlockProcessor internal constructor(
(if (missingBlockCount.rem(DOWNLOAD_BATCH_SIZE) == 0L) 0 else 1)
)
var progress: Int
twig("found $missingBlockCount missing blocks, downloading in $batches batches of $DOWNLOAD_BATCH_SIZE...")
twig(
"found $missingBlockCount missing blocks, downloading in $batches batches of " +
"$DOWNLOAD_BATCH_SIZE..."
)
for (i in 1..batches) {
retryUpTo(RETRIES, { CompactBlockProcessorException.FailedDownload(it) }) {
val end = BlockHeight.new(
@ -582,7 +625,10 @@ class CompactBlockProcessor internal constructor(
)
) // subtract 1 on the first value because the range is inclusive
var count = 0
twig("downloaded $downloadedBlockHeight..$end (batch $i of $batches) [${downloadedBlockHeight..end}]") {
twig(
"downloaded $downloadedBlockHeight..$end (batch $i of $batches) " +
"[${downloadedBlockHeight..end}]"
) {
count = downloader.downloadBlockRange(downloadedBlockHeight..end)
}
twig("downloaded $count blocks!")
@ -628,6 +674,7 @@ class CompactBlockProcessor internal constructor(
*
* @param range the range of blocks to scan.
*/
@Suppress("MagicNumber")
private suspend fun scanNewBlocks(range: ClosedRange<BlockHeight>?): Boolean = withContext(IO) {
if (null == range || range.isEmpty()) {
twig("no blocks to scan for range $range")
@ -649,16 +696,26 @@ class CompactBlockProcessor internal constructor(
val lastScannedHeight =
BlockHeight.new(network, range.start.value + metrics.cumulativeItems - 1)
val percentValue =
(lastScannedHeight.value - range.start.value) / (range.endInclusive.value - range.start.value + 1).toFloat() * 100.0f
val percent = "%.0f".format(percentValue.coerceAtMost(100f).coerceAtLeast(0f))
twig("batch scanned ($percent%): $lastScannedHeight/${range.endInclusive} | ${metrics.batchTime}ms, ${metrics.batchItems}blks, ${metrics.batchIps.format()}bps")
(lastScannedHeight.value - range.start.value) /
(range.endInclusive.value - range.start.value + 1).toFloat() * 100.0f
val percent = "%.0f".format(
percentValue.coerceAtMost(100f)
.coerceAtLeast(0f)
)
twig(
"batch scanned ($percent%): $lastScannedHeight/${range.endInclusive} | " +
"${metrics.batchTime}ms, ${metrics.batchItems}blks, ${metrics.batchIps.format()}bps"
)
if (currentInfo.lastScannedHeight != lastScannedHeight) {
scannedNewBlocks = true
updateProgress(lastScannedHeight = lastScannedHeight)
}
// if we made progress toward our scan, then keep trying
} while (result && scannedNewBlocks && lastScannedHeight < range.endInclusive)
twig("batch scan complete! Total time: ${metrics.cumulativeTime} Total blocks measured: ${metrics.cumulativeItems} Cumulative bps: ${metrics.cumulativeIps.format()}")
twig(
"batch scan complete! Total time: ${metrics.cumulativeTime} Total blocks measured: " +
"${metrics.cumulativeItems} Cumulative bps: ${metrics.cumulativeIps.format()}"
)
}
Twig.clip("scanning")
result
@ -704,7 +761,8 @@ class CompactBlockProcessor internal constructor(
}
private suspend fun handleChainError(errorHeight: BlockHeight) {
// TODO consider an error object containing hash information
// TODO [#683]: consider an error object containing hash information
// TODO [#683]: https://github.com/zcash/zcash-android-wallet-sdk/issues/683
printValidationErrorInfo(errorHeight)
determineLowerBound(errorHeight).let { lowerBound ->
twig("handling chain error at $errorHeight by rewinding to block $lowerBound")
@ -714,9 +772,10 @@ class CompactBlockProcessor internal constructor(
}
suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight {
// TODO: add a concept of original checkpoint height to the processor. For now, derive it
val originalCheckpoint =
lowerBoundHeight + MAX_REORG_SIZE + 2 // add one because we already have the checkpoint. Add one again because we delete ABOVE the block
// TODO [#683]: add a concept of original checkpoint height to the processor. For now, derive it
// add one because we already have the checkpoint. Add one again because we delete ABOVE the block
// TODO [#683]: https://github.com/zcash/zcash-android-wallet-sdk/issues/683
val originalCheckpoint = lowerBoundHeight + MAX_REORG_SIZE + 2
return if (height < originalCheckpoint) {
originalCheckpoint
} else {
@ -732,10 +791,10 @@ class CompactBlockProcessor internal constructor(
*/
suspend fun quickRewind() {
val height = max(currentInfo.lastScannedHeight, repository.lastScannedHeight())
val blocksPerDay = 60 * 60 * 24 * 1000 / ZcashSdk.BLOCK_INTERVAL_MILLIS.toInt()
val blocksPer14Days = 14.days.inWholeMilliseconds / ZcashSdk.BLOCK_INTERVAL_MILLIS.toInt()
val twoWeeksBack = BlockHeight.new(
network,
(height.value - blocksPerDay * 14).coerceAtLeast(lowerBoundHeight.value)
(height.value - blocksPer14Days).coerceAtLeast(lowerBoundHeight.value)
)
rewindToNearestHeight(twoWeeksBack, false)
}
@ -744,6 +803,7 @@ class CompactBlockProcessor internal constructor(
* @param alsoClearBlockCache when true, also clear the block cache which forces a redownload of
* blocks. Otherwise, the cached blocks will be used in the rescan, which in most cases, is fine.
*/
@Suppress("LongMethod")
suspend fun rewindToNearestHeight(
height: BlockHeight,
alsoClearBlockCache: Boolean = false
@ -753,19 +813,36 @@ class CompactBlockProcessor internal constructor(
val lastScannedHeight = currentInfo.lastScannedHeight
val lastLocalBlock = repository.lastScannedHeight()
val targetHeight = getNearestRewindHeight(height)
twig("Rewinding from $lastScannedHeight to requested height: $height using target height: $targetHeight with last local block: $lastLocalBlock")
if ((null == lastScannedHeight && targetHeight < lastLocalBlock) || (null != lastScannedHeight && targetHeight < lastScannedHeight)) {
twig(
"Rewinding from $lastScannedHeight to requested height: $height using target height: " +
"$targetHeight with last local block: $lastLocalBlock"
)
if (null == lastScannedHeight && targetHeight < lastLocalBlock) {
twig("Rewinding because targetHeight is less than lastLocalBlock.")
rustBackend.rewindToHeight(targetHeight)
} else if (null != lastScannedHeight && targetHeight < lastScannedHeight) {
twig("Rewinding because targetHeight is less than lastScannedHeight.")
rustBackend.rewindToHeight(targetHeight)
} else {
twig("not rewinding dataDb because the last scanned height is $lastScannedHeight and the last local block is $lastLocalBlock both of which are less than the target height of $targetHeight")
twig(
"not rewinding dataDb because the last scanned height is $lastScannedHeight and the" +
" last local block is $lastLocalBlock both of which are less than the target height of " +
"$targetHeight"
)
}
val currentNetworkBlockHeight = currentInfo.networkBlockHeight
if (alsoClearBlockCache) {
twig("Also clearing block cache back to $targetHeight. These rewound blocks will download in the next scheduled scan")
twig(
"Also clearing block cache back to $targetHeight. These rewound blocks will " +
"download in the next scheduled scan"
)
downloader.rewindToHeight(targetHeight)
// communicate that the wallet is no longer synced because it might remain this way for 20+ seconds because we only download on 20s time boundaries so we can't trigger any immediate action
// communicate that the wallet is no longer synced because it might remain this way for 20+
// seconds because we only download on 20s time boundaries so we can't trigger any immediate action
setState(Downloading)
if (null == currentNetworkBlockHeight) {
updateProgress(
@ -800,7 +877,11 @@ class CompactBlockProcessor internal constructor(
if (null != lastScannedHeight) {
val range = (targetHeight + 1)..lastScannedHeight
twig("We kept the cache blocks in place so we don't need to wait for the next scheduled download to rescan. Instead we will rescan and validate blocks ${range.start}..${range.endInclusive}")
twig(
"We kept the cache blocks in place so we don't need to wait for the next " +
"scheduled download to rescan. Instead we will rescan and validate blocks " +
"${range.start}..${range.endInclusive}"
)
if (validateAndScanNewBlocks(range) == BlockProcessingResult.Success) {
enhanceTransactionDetails(range)
}
@ -811,19 +892,27 @@ class CompactBlockProcessor internal constructor(
/** insightful function for debugging these critical errors */
private suspend fun printValidationErrorInfo(errorHeight: BlockHeight, count: Int = 11) {
// Note: blocks are public information so it's okay to print them but, still, let's not unless we're debugging something
// Note: blocks are public information so it's okay to print them but, still, let's not unless we're
// debugging something
if (!BuildConfig.DEBUG) return
var errorInfo = fetchValidationErrorInfo(errorHeight)
twig("validation failed at block ${errorInfo.errorHeight} which had hash ${errorInfo.actualPrevHash} but the expected hash was ${errorInfo.expectedPrevHash}")
twig(
"validation failed at block ${errorInfo.errorHeight} which had hash " +
"${errorInfo.actualPrevHash} but the expected hash was ${errorInfo.expectedPrevHash}"
)
errorInfo = fetchValidationErrorInfo(errorHeight + 1)
twig("The next block block: ${errorInfo.errorHeight} which had hash ${errorInfo.actualPrevHash} but the expected hash was ${errorInfo.expectedPrevHash}")
twig(
"The next block block: ${errorInfo.errorHeight} which had hash ${errorInfo.actualPrevHash} but " +
"the expected hash was ${errorInfo.expectedPrevHash}"
)
twig("=================== BLOCKS [$errorHeight..${errorHeight.value + count - 1}]: START ========")
repeat(count) { i ->
val height = errorHeight + i
val block = downloader.compactBlockStore.findCompactBlock(height)
// sometimes the initial block was inserted via checkpoint and will not appear in the cache. We can get the hash another way but prevHash is correctly null.
// sometimes the initial block was inserted via checkpoint and will not appear in the cache. We can get
// the hash another way but prevHash is correctly null.
val hash = block?.hash?.toByteArray()
?: (repository as PagedTransactionRepository).findBlockHash(height)
twig(
@ -858,7 +947,10 @@ class CompactBlockProcessor internal constructor(
private fun determineLowerBound(errorHeight: BlockHeight): BlockHeight {
val offset = min(MAX_REORG_SIZE, REWIND_DISTANCE * (consecutiveChainErrors.get() + 1))
return BlockHeight(max(errorHeight.value - offset, lowerBoundHeight.value)).also {
twig("offset = min($MAX_REORG_SIZE, $REWIND_DISTANCE * (${consecutiveChainErrors.get() + 1})) = $offset")
twig(
"offset = min($MAX_REORG_SIZE, $REWIND_DISTANCE * (${consecutiveChainErrors.get() + 1})) = " +
"$offset"
)
twig("lowerBound = max($errorHeight - $offset, $lowerBoundHeight) = $it")
}
}
@ -883,6 +975,7 @@ class CompactBlockProcessor internal constructor(
suspend fun calculateBirthdayHeight(): BlockHeight {
var oldestTransactionHeight: BlockHeight? = null
@Suppress("TooGenericExceptionCaught")
try {
val tempOldestTransactionHeight = repository.receivedTransactions
.first()
@ -890,10 +983,12 @@ class CompactBlockProcessor internal constructor(
?.minedBlockHeight
?: lowerBoundHeight
// to be safe adjust for reorgs (and generally a little cushion is good for privacy)
// so we round down to the nearest 100 and then subtract 100 to ensure that the result is always at least 100 blocks away
// so we round down to the nearest 100 and then subtract 100 to ensure that the result is always at least
// 100 blocks away
oldestTransactionHeight = BlockHeight.new(
network,
tempOldestTransactionHeight.value - tempOldestTransactionHeight.value.rem(ZcashSdk.MAX_REORG_SIZE) - ZcashSdk.MAX_REORG_SIZE.toLong()
tempOldestTransactionHeight.value -
tempOldestTransactionHeight.value.rem(MAX_REORG_SIZE) - MAX_REORG_SIZE.toLong()
)
} catch (t: Throwable) {
twig("failed to calculate birthday due to: $t")
@ -943,6 +1038,7 @@ class CompactBlockProcessor internal constructor(
*/
suspend fun getBalanceInfo(accountIndex: Int = 0): WalletBalance =
twigTask("checking balance info", -1) {
@Suppress("TooGenericExceptionCaught")
try {
val balanceTotal = rustBackend.getBalance(accountIndex)
twig("found total balance: $balanceTotal")
@ -1092,6 +1188,7 @@ class CompactBlockProcessor internal constructor(
/**
* The amount of scan progress from 0 to 100.
*/
@Suppress("MagicNumber")
val scanProgress
get() = when {
lastScannedHeight == null -> 0

View File

@ -26,7 +26,7 @@ import java.io.File
*
* @param context the application context
*/
@SuppressWarnings("TooManyFunctions")
@Suppress("TooManyFunctions")
internal class DatabaseCoordinator private constructor(context: Context) {
/*

View File

@ -1,3 +1,5 @@
@file:Suppress("TooManyFunctions")
package cash.z.ecc.android.sdk.db.entity
import android.text.format.DateUtils
@ -6,6 +8,7 @@ import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import androidx.room.RoomWarnings
import cash.z.ecc.android.sdk.internal.transaction.PersistentTransactionManager
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.Zatoshi
@ -103,6 +106,7 @@ data class PendingTransactionEntity(
val valueZatoshi: Zatoshi
get() = Zatoshi(value)
@Suppress("ComplexMethod")
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is PendingTransactionEntity) return false
@ -183,6 +187,7 @@ data class ConfirmedTransaction(
BlockHeight(minedHeight)
}
@Suppress("ComplexMethod")
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ConfirmedTransaction) return false
@ -352,21 +357,35 @@ fun PendingTransaction.isSubmitted(): Boolean {
}
fun PendingTransaction.isExpired(latestHeight: BlockHeight?, saplingActivationHeight: BlockHeight): Boolean {
// TODO: test for off-by-one error here. Should we use <= or <
if (latestHeight == null || latestHeight.value < saplingActivationHeight.value || expiryHeight < saplingActivationHeight.value) return false
// TODO [#687]: test for off-by-one error here. Should we use <= or <
// TODO [#687]: https://github.com/zcash/zcash-android-wallet-sdk/issues/687
if (latestHeight == null ||
latestHeight.value < saplingActivationHeight.value ||
expiryHeight < saplingActivationHeight.value
) {
return false
}
return expiryHeight < latestHeight.value
}
// if we don't have info on a pendingtx after 100 blocks then it's probably safe to stop polling!
@Suppress("MagicNumber")
fun PendingTransaction.isLongExpired(latestHeight: BlockHeight?, saplingActivationHeight: BlockHeight): Boolean {
if (latestHeight == null || latestHeight.value < saplingActivationHeight.value || expiryHeight < saplingActivationHeight.value) return false
if (latestHeight == null ||
latestHeight.value < saplingActivationHeight.value ||
expiryHeight < saplingActivationHeight.value
) {
return false
}
return (latestHeight.value - expiryHeight) > 100
}
fun PendingTransaction.isMarkedForDeletion(): Boolean {
return rawTransactionId == null && (errorCode ?: 0) == -9090
return rawTransactionId == null &&
(errorCode ?: 0) == PersistentTransactionManager.SAFE_TO_DELETE_ERROR_CODE
}
@Suppress("MagicNumber")
fun PendingTransaction.isSafeToDiscard(): Boolean {
// invalid dates shouldn't happen or should be temporary
if (createTime < 0) return false

View File

@ -68,7 +68,10 @@ sealed class CompactBlockProcessorException(message: String, cause: Throwable? =
"No data db file found at path $path. Verify " +
"that the data DB has been initialized via `rustBackend.initDataDb(path)`"
)
open class ConfigurationException(message: String, cause: Throwable?) : CompactBlockProcessorException(message, cause)
open class ConfigurationException(
message: String,
cause: Throwable?
) : CompactBlockProcessorException(message, cause)
class FileInsteadOfPath(fileName: String) : ConfigurationException(
"Invalid Path: the given path appears to be a" +
" file name instead of a path: $fileName. The RustBackend expects the absolutePath to the database rather" +
@ -87,7 +90,8 @@ sealed class CompactBlockProcessorException(message: String, cause: Throwable? =
"likely means a block was missed or a reorg was mishandled. See logs for details.",
cause
)
class Disconnected(cause: Throwable? = null) : CompactBlockProcessorException("Disconnected Error. Unable to download blocks due to ${cause?.message}", cause)
class Disconnected(cause: Throwable? = null) :
CompactBlockProcessorException("Disconnected Error. Unable to download blocks due to ${cause?.message}", cause)
object Uninitialized : CompactBlockProcessorException(
"Cannot process blocks because the wallet has not been" +
" initialized. Verify that the seed phrase was properly created or imported. If so, then this problem" +
@ -97,17 +101,41 @@ sealed class CompactBlockProcessorException(message: String, cause: Throwable? =
"Attempting to scan without an account. This is probably a setup error or a race condition."
)
open class EnhanceTransactionError(message: String, val height: BlockHeight?, cause: Throwable) : CompactBlockProcessorException(message, cause) {
class EnhanceTxDownloadError(height: BlockHeight?, cause: Throwable) : EnhanceTransactionError("Error while attempting to download a transaction to enhance", height, cause)
class EnhanceTxDecryptError(height: BlockHeight?, cause: Throwable) : EnhanceTransactionError("Error while attempting to decrypt and store a transaction to enhance", height, cause)
open class EnhanceTransactionError(
message: String,
val height: BlockHeight?,
cause: Throwable
) : CompactBlockProcessorException(message, cause) {
class EnhanceTxDownloadError(
height: BlockHeight?,
cause: Throwable
) : EnhanceTransactionError(
"Error while attempting to download a transaction to enhance",
height,
cause
)
class EnhanceTxDecryptError(
height: BlockHeight?,
cause: Throwable
) : EnhanceTransactionError(
"Error while attempting to decrypt and store a transaction to enhance",
height,
cause
)
}
class MismatchedNetwork(clientNetwork: String?, serverNetwork: String?) : CompactBlockProcessorException(
"Incompatible server: this client expects a server using $clientNetwork but it was $serverNetwork! Try updating the client or switching servers."
"Incompatible server: this client expects a server using $clientNetwork but it was $serverNetwork! Try " +
"updating the client or switching servers."
)
class MismatchedBranch(clientBranch: String?, serverBranch: String?, networkName: String?) : CompactBlockProcessorException(
"Incompatible server: this client expects a server following consensus branch $clientBranch on $networkName but it was $serverBranch! Try updating the client or switching servers."
class MismatchedBranch(
clientBranch: String?,
serverBranch: String?,
networkName: String?
) : CompactBlockProcessorException(
"Incompatible server: this client expects a server following consensus branch $clientBranch on $networkName " +
"but it was $serverBranch! Try updating the client or switching servers."
)
}
@ -123,7 +151,10 @@ sealed class BirthdayException(message: String, cause: Throwable? = null) : SdkE
class MissingBirthdayFilesException(directory: String) : BirthdayException(
"Cannot initialize wallet because no birthday files were found in the $directory directory."
)
class ExactBirthdayNotFoundException internal constructor(birthday: BlockHeight, nearestMatch: Checkpoint? = null) : BirthdayException(
class ExactBirthdayNotFoundException internal constructor(
birthday: BlockHeight,
nearestMatch: Checkpoint? = null
) : BirthdayException(
"Unable to find birthday that exactly matches $birthday.${
if (nearestMatch != null) {
" An exact match was request but the nearest match found was ${nearestMatch.height}."
@ -207,7 +238,11 @@ sealed class LightWalletException(message: String, cause: Throwable? = null) : S
)
open class ChangeServerException(message: String, cause: Throwable? = null) : SdkException(message, cause) {
class ChainInfoNotMatching(val propertyNames: String, val expectedInfo: Service.LightdInfo, val actualInfo: Service.LightdInfo) : ChangeServerException(
class ChainInfoNotMatching(
val propertyNames: String,
val expectedInfo: Service.LightdInfo,
val actualInfo: Service.LightdInfo
) : ChangeServerException(
"Server change error: the $propertyNames values did not match."
)
class StatusException(val status: Status, cause: Throwable? = null) : SdkException(status.toMessage(), cause) {
@ -215,7 +250,8 @@ sealed class LightWalletException(message: String, cause: Throwable? = null) : S
private fun Status.toMessage(): String {
return when (this.code) {
UNAVAILABLE -> {
"Error: the new server is unavailable. Verify that the host and port are correct. Failed with $this"
"Error: the new server is unavailable. Verify that the host and port are correct. Failed " +
"with $this"
}
else -> "Changing servers failed with status $this"
}
@ -234,15 +270,14 @@ sealed class TransactionEncoderException(message: String, cause: Throwable? = nu
"Cannot send funds due to missing spend or output params and attempting to download them failed."
)
class TransactionNotFoundException(transactionId: Long) : TransactionEncoderException(
"Unable to find transactionId " +
"$transactionId in the repository. This means the wallet created a transaction and then returned a row ID " +
"that does not actually exist. This is a scenario where the wallet should have thrown an exception but failed " +
"to do so."
"Unable to find transactionId $transactionId in the repository. This means the wallet created a transaction " +
"and then returned a row ID that does not actually exist. This is a scenario where the wallet should " +
"have thrown an exception but failed to do so."
)
class TransactionNotEncodedException(transactionId: Long) : TransactionEncoderException(
"The transaction returned by the wallet," +
" with id $transactionId, does not have any raw data. This is a scenario where the wallet should have thrown" +
" an exception but failed to do so."
" with id $transactionId, does not have any raw data. This is a scenario where the wallet should have " +
"thrown an exception but failed to do so."
)
class IncompleteScanException(lastScannedHeight: BlockHeight) : TransactionEncoderException(
"Cannot" +

View File

@ -4,13 +4,19 @@ import cash.z.ecc.android.sdk.model.BlockHeight
import kotlin.math.max
import kotlin.math.min
class BatchMetrics(val range: ClosedRange<BlockHeight>, val batchSize: Int, private val onMetricComplete: ((BatchMetrics, Boolean) -> Unit)? = null) {
class BatchMetrics(
val range: ClosedRange<BlockHeight>,
val batchSize: Int,
private val onMetricComplete: ((BatchMetrics, Boolean) -> Unit)? = null
) {
private var completedBatches = 0
private var rangeStartTime = 0L
private var batchStartTime = 0L
private var batchEndTime = 0L
private var rangeSize = range.endInclusive.value - range.start.value + 1
private fun now() = System.currentTimeMillis()
@Suppress("MagicNumber")
private fun ips(blocks: Long, time: Long) = 1000.0f * blocks / time
val isComplete get() = completedBatches * batchSize >= rangeSize

View File

@ -1,14 +1,17 @@
package cash.z.ecc.android.sdk.ext
import java.util.Locale
fun ByteArray.toHex(): String {
val sb = StringBuilder(size * 2)
for (b in this)
sb.append(String.format("%02x", b))
for (b in this) {
sb.append(String.format(Locale.ROOT, "%02x", b))
}
return sb.toString()
}
// Not used within the SDK, but is used by the Wallet app
@Suppress("unused")
@Suppress("unused", "MagicNumber")
fun String.fromHex(): ByteArray {
val len = length
val data = ByteArray(len / 2)

View File

@ -7,8 +7,11 @@ import java.util.Locale
* omitted since this is not the source of truth for branch information but rather a tool for
* printing that information to users.
*/
@Suppress("MagicNumber")
enum class ConsensusBranchId(val displayName: String, val id: Long, val hexId: String) {
// TODO: see if we can find a way to not rely on this separate source of truth (either stop converting from hex to display name in the apps or use Rust to get this info)
// TODO [#679]: see if we can find a way to not rely on this separate source of truth (either stop converting from
// hex to display name in the apps or use Rust to get this info)
// TODO [#679]: https://github.com/zcash/zcash-android-wallet-sdk/issues/679
SPROUT("Sprout", 0, "0"),
OVERWINTER("Overwinter", 0x5ba8_1b19, "5ba81b19"),
SAPLING("Sapling", 0x76b8_09bb, "76b809bb"),

View File

@ -1,9 +1,10 @@
@file:Suppress("NOTHING_TO_INLINE")
@file:Suppress("TooManyFunctions", "MatchingDeclarationName")
package cash.z.ecc.android.sdk.ext
import cash.z.ecc.android.sdk.ext.Conversions.USD_FORMATTER
import cash.z.ecc.android.sdk.ext.Conversions.ZEC_FORMATTER
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.Zatoshi
import java.math.BigDecimal
import java.math.MathContext
@ -18,8 +19,10 @@ import java.util.Locale
* accurately rounded values to the user.
*/
// TODO: provide a dynamic way to configure this globally for the SDK
// For now, just make these vars so at least they could be modified in one place
// TODO [#678]: provide a dynamic way to configure this globally for the SDK
// For now, just make these vars so at least they could be modified in one place
// TODO [#678]: https://github.com/zcash/zcash-android-wallet-sdk/issues/678
@Suppress("MagicNumber")
object Conversions {
var ONE_ZEC_IN_ZATOSHI = BigDecimal(Zatoshi.ZATOSHI_PER_ZEC, MathContext.DECIMAL128)
var ZEC_FORMATTER = NumberFormat.getInstance(Locale.getDefault()).apply {
@ -47,7 +50,7 @@ object Conversions {
* @return this Zatoshi value represented as ZEC, in a string with at least [minDecimals] and at
* most [maxDecimals]
*/
inline fun Zatoshi?.convertZatoshiToZecString(
fun Zatoshi?.convertZatoshiToZecString(
maxDecimals: Int = ZEC_FORMATTER.maximumFractionDigits,
minDecimals: Int = ZEC_FORMATTER.minimumFractionDigits
): String {
@ -65,7 +68,7 @@ inline fun Zatoshi?.convertZatoshiToZecString(
* @return this Double ZEC value represented as a string with at least [minDecimals] and at most
* [maxDecimals].
*/
inline fun Double?.toZecString(
fun Double?.toZecString(
maxDecimals: Int = ZEC_FORMATTER.maximumFractionDigits,
minDecimals: Int = ZEC_FORMATTER.minimumFractionDigits
): String {
@ -83,7 +86,7 @@ inline fun Double?.toZecString(
* @return this BigDecimal ZEC value represented as a string with at least [minDecimals] and at most
* [maxDecimals].
*/
inline fun BigDecimal?.toZecString(
fun BigDecimal?.toZecString(
maxDecimals: Int = ZEC_FORMATTER.maximumFractionDigits,
minDecimals: Int = ZEC_FORMATTER.minimumFractionDigits
): String {
@ -101,7 +104,7 @@ inline fun BigDecimal?.toZecString(
* @return this Double ZEC value represented as a string with at least [minDecimals] and at most
* [maxDecimals], which is 2 by default. Zero is always represented without any decimals.
*/
inline fun Double?.toUsdString(
fun Double?.toUsdString(
maxDecimals: Int = USD_FORMATTER.maximumFractionDigits,
minDecimals: Int = USD_FORMATTER.minimumFractionDigits
): String {
@ -123,7 +126,7 @@ inline fun Double?.toUsdString(
* @return this BigDecimal USD value represented as a string with at least [minDecimals] and at most
* [maxDecimals], which is 2 by default.
*/
inline fun BigDecimal?.toUsdString(
fun BigDecimal?.toUsdString(
maxDecimals: Int = USD_FORMATTER.maximumFractionDigits,
minDecimals: Int = USD_FORMATTER.minimumFractionDigits
): String {
@ -141,7 +144,7 @@ inline fun BigDecimal?.toUsdString(
*
* @return a currency formatter, appropriate for the default locale.
*/
inline fun currencyFormatter(maxDecimals: Int, minDecimals: Int): NumberFormat {
fun currencyFormatter(maxDecimals: Int, minDecimals: Int): NumberFormat {
return NumberFormat.getInstance(Locale.getDefault()).apply {
roundingMode = ZEC_FORMATTER.roundingMode
maximumFractionDigits = maxDecimals
@ -162,7 +165,7 @@ inline fun currencyFormatter(maxDecimals: Int, minDecimals: Int): NumberFormat {
* @return this Long Zatoshi value represented as ZEC using a BigDecimal with the given scale,
* rounded accurately out to 128 digits.
*/
inline fun Zatoshi?.convertZatoshiToZec(scale: Int = ZEC_FORMATTER.maximumFractionDigits): BigDecimal {
fun Zatoshi?.convertZatoshiToZec(scale: Int = ZEC_FORMATTER.maximumFractionDigits): BigDecimal {
return BigDecimal(this?.value ?: 0L, MathContext.DECIMAL128).divide(
Conversions.ONE_ZEC_IN_ZATOSHI,
MathContext.DECIMAL128
@ -176,7 +179,7 @@ inline fun Zatoshi?.convertZatoshiToZec(scale: Int = ZEC_FORMATTER.maximumFracti
* @return this ZEC value represented as Zatoshi, rounded accurately out to 128 digits, in order to
* minimize cumulative errors when applied repeatedly over a sequence of calculations.
*/
inline fun BigDecimal?.convertZecToZatoshi(): Zatoshi {
fun BigDecimal?.convertZecToZatoshi(): Zatoshi {
if (this == null) return Zatoshi(0L)
if (this < BigDecimal.ZERO) {
throw IllegalArgumentException(
@ -197,7 +200,7 @@ inline fun BigDecimal?.convertZecToZatoshi(): Zatoshi {
* @return this Double ZEC value converted into a BigDecimal, with the proper rounding mode for use
* with other formatting functions.
*/
inline fun Double?.toZec(decimals: Int = ZEC_FORMATTER.maximumFractionDigits): BigDecimal {
fun Double?.toZec(decimals: Int = ZEC_FORMATTER.maximumFractionDigits): BigDecimal {
return BigDecimal(this?.toString() ?: "0.0", MathContext.DECIMAL128).setScale(
decimals,
ZEC_FORMATTER.roundingMode
@ -214,7 +217,7 @@ inline fun Double?.toZec(decimals: Int = ZEC_FORMATTER.maximumFractionDigits): B
* @return this Double ZEC value converted into Zatoshi, with proper rounding and precision by
* leveraging an intermediate BigDecimal object.
*/
inline fun Double?.convertZecToZatoshi(decimals: Int = ZEC_FORMATTER.maximumFractionDigits): Zatoshi {
fun Double?.convertZecToZatoshi(decimals: Int = ZEC_FORMATTER.maximumFractionDigits): Zatoshi {
return this.toZec(decimals).convertZecToZatoshi()
}
@ -227,7 +230,7 @@ inline fun Double?.convertZecToZatoshi(decimals: Int = ZEC_FORMATTER.maximumFrac
*
* @return this BigDecimal ZEC adjusted to the default scale and rounding mode.
*/
inline fun BigDecimal?.toZec(decimals: Int = ZEC_FORMATTER.maximumFractionDigits): BigDecimal {
fun BigDecimal?.toZec(decimals: Int = ZEC_FORMATTER.maximumFractionDigits): BigDecimal {
return (this ?: BigDecimal.ZERO).setScale(decimals, ZEC_FORMATTER.roundingMode)
}
@ -240,7 +243,7 @@ inline fun BigDecimal?.toZec(decimals: Int = ZEC_FORMATTER.maximumFractionDigits
*
* @return this Double USD value converted into a BigDecimal, with proper rounding and precision.
*/
inline fun Double?.toUsd(decimals: Int = USD_FORMATTER.maximumFractionDigits): BigDecimal {
fun Double?.toUsd(decimals: Int = USD_FORMATTER.maximumFractionDigits): BigDecimal {
return BigDecimal(this?.toString() ?: "0.0", MathContext.DECIMAL128).setScale(
decimals,
USD_FORMATTER.roundingMode
@ -256,7 +259,7 @@ inline fun Double?.toUsd(decimals: Int = USD_FORMATTER.maximumFractionDigits): B
*
* @return this BigDecimal USD value converted into USD, with proper rounding and precision.
*/
inline fun BigDecimal?.toUsd(decimals: Int = USD_FORMATTER.maximumFractionDigits): BigDecimal {
fun BigDecimal?.toUsd(decimals: Int = USD_FORMATTER.maximumFractionDigits): BigDecimal {
return (this ?: BigDecimal.ZERO).setScale(decimals, USD_FORMATTER.roundingMode)
}
@ -268,7 +271,7 @@ inline fun BigDecimal?.toUsd(decimals: Int = USD_FORMATTER.maximumFractionDigits
*
* @return this BigDecimal USD value converted into USD, with proper rounding and precision.
*/
inline fun BigDecimal?.convertZecToUsd(zecPrice: BigDecimal): BigDecimal {
fun BigDecimal?.convertZecToUsd(zecPrice: BigDecimal): BigDecimal {
if (this == null) return BigDecimal.ZERO
if (this < BigDecimal.ZERO) {
throw IllegalArgumentException(
@ -287,7 +290,7 @@ inline fun BigDecimal?.convertZecToUsd(zecPrice: BigDecimal): BigDecimal {
*
* @return this BigDecimal USD value converted into ZEC, with proper rounding and precision.
*/
inline fun BigDecimal?.convertUsdToZec(zecPrice: BigDecimal): BigDecimal {
fun BigDecimal?.convertUsdToZec(zecPrice: BigDecimal): BigDecimal {
if (this == null) return BigDecimal.ZERO
if (this < BigDecimal.ZERO) {
throw IllegalArgumentException(
@ -310,7 +313,7 @@ inline fun BigDecimal?.convertUsdToZec(zecPrice: BigDecimal): BigDecimal {
* @return this BigDecimal value converted from one currency into the other, based on the given
* price.
*/
inline fun BigDecimal.convertCurrency(zecPrice: BigDecimal, isUsd: Boolean): BigDecimal {
fun BigDecimal.convertCurrency(zecPrice: BigDecimal, isUsd: Boolean): BigDecimal {
return if (isUsd) {
this.convertUsdToZec(zecPrice)
} else {
@ -323,15 +326,19 @@ inline fun BigDecimal.convertCurrency(zecPrice: BigDecimal, isUsd: Boolean): Big
*
* @return this string as a BigDecimal or null when parsing fails.
*/
inline fun String?.safelyConvertToBigDecimal(): BigDecimal? {
if (this.isNullOrEmpty()) return BigDecimal.ZERO
return try {
// ignore commas and whitespace
var sanitizedInput = this.filter { it.isDigit() or (it == '.') }
BigDecimal.ZERO.max(BigDecimal(sanitizedInput, MathContext.DECIMAL128))
} catch (t: Throwable) {
return null
fun String?.safelyConvertToBigDecimal(): BigDecimal? {
if (this.isNullOrEmpty()) {
return BigDecimal.ZERO
}
val result = try {
// ignore commas and whitespace
val sanitizedInput = this.filter { it.isDigit() or (it == '.') }
BigDecimal.ZERO.max(BigDecimal(sanitizedInput, MathContext.DECIMAL128))
} catch (nfe: NumberFormatException) {
twig("Exception while converting String to BigDecimal: ${nfe.message} caused by: ${nfe.cause}")
null
}
return result
}
/**
@ -343,7 +350,7 @@ inline fun String?.safelyConvertToBigDecimal(): BigDecimal? {
* @return the abbreviated string unless the string is too short, in which case the original string
* is returned.
*/
inline fun String.toAbbreviatedAddress(startLength: Int = 8, endLength: Int = 8) =
fun String.toAbbreviatedAddress(startLength: Int = 8, endLength: Int = 8) =
if (length > startLength + endLength) "${take(startLength)}${takeLast(endLength)}" else this
/**
@ -355,7 +362,7 @@ inline fun String.toAbbreviatedAddress(startLength: Int = 8, endLength: Int = 8)
*
* @return the masked version of this string, typically for use in logs.
*/
internal inline fun String.masked(addressCharsToShow: Int = 4): String =
internal fun String.masked(addressCharsToShow: Int = 4): String =
if (startsWith("ztest") || startsWith("zs")) "****${takeLast(addressCharsToShow)}"
else "***masked***"
@ -364,4 +371,4 @@ internal inline fun String.masked(addressCharsToShow: Int = 4): String =
*
* @return true when this function starts with 'z' rather than 't'.
*/
inline fun String?.isShielded() = this != null && startsWith('z')
fun String?.isShielded() = this != null && startsWith('z')

View File

@ -8,6 +8,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
* becomes easier to reduce privacy by segmenting the anonymity set of users, particularly as it
* relates to network requests.
*/
@Suppress("MagicNumber")
object ZcashSdk {
/**
@ -26,8 +27,8 @@ object ZcashSdk {
const val MAX_MEMO_SIZE = 512
/**
* The amount of blocks ahead of the current height where new transactions are set to expire. This value is controlled
* by the rust backend but it is helpful to know what it is set to and should be kept in sync.
* The amount of blocks ahead of the current height where new transactions are set to expire. This value is
* controlled by the rust backend but it is helpful to know what it is set to and should be kept in sync.
*/
const val EXPIRY_OFFSET = 20
@ -62,14 +63,14 @@ object ZcashSdk {
const val RETRIES = 5
/**
* The default maximum amount of time to wait during retry backoff intervals. Failed loops will never wait longer than
* this before retyring.
* The default maximum amount of time to wait during retry backoff intervals. Failed loops will never wait longer
* than this before retyring.
*/
const val MAX_BACKOFF_INTERVAL = 600_000L
/**
* Default number of blocks to rewind when a chain reorg is detected. This should be large enough to recover from the
* reorg but smaller than the theoretical max reorg size of 100.
* Default number of blocks to rewind when a chain reorg is detected. This should be large enough to recover from
* the reorg but smaller than the theoretical max reorg size of 100.
*/
const val REWIND_DISTANCE = 10
@ -95,5 +96,19 @@ object ZcashSdk {
*/
const val DEFAULT_SHIELD_FUNDS_MEMO_PREFIX = "shielding:"
/**
* The default alias used as part of a file name for the preferences and databases. This
* enables multiple wallets to exist on one device, which is also helpful for sweeping funds.
*/
const val DEFAULT_ALIAS: String = "zcash_sdk"
/**
* The minimum alias length to be valid for our use.
*/
const val ALIAS_MIN_LENGTH: Int = 1
/**
* The maximum alias length to be valid for our use.
*/
const val ALIAS_MAX_LENGTH: Int = 99
}

View File

@ -13,6 +13,7 @@ import okio.buffer
import okio.sink
import java.io.File
@Suppress("UtilityClassWithPublicConstructor")
class SaplingParamTool {
companion object {
@ -34,6 +35,7 @@ class SaplingParamTool {
}
}
if (hadError) {
@Suppress("TooGenericExceptionCaught")
try {
Bush.trunk.twigTask("attempting to download missing params") {
fetchParams(destinationDir)
@ -131,7 +133,8 @@ class SaplingParamTool {
* @return an http client suitable for downloading params data.
*/
private fun createHttpClient(): OkHttpClient {
// TODO: add logging and timeouts
// TODO [#686]: add logging and timeouts
// TODO [#686]: https://github.com/zcash/zcash-android-wallet-sdk/issues/686
return OkHttpClient()
}
}

View File

@ -1,6 +1,7 @@
@file:Suppress("NOTHING_TO_INLINE")
@file:Suppress("NOTHING_TO_INLINE", "MagicNumber")
package cash.z.ecc.android.sdk.internal
import java.util.Locale
import java.util.concurrent.CopyOnWriteArraySet
import kotlin.math.roundToLong
@ -95,12 +96,28 @@ inline fun twig(t: Throwable) = t.stackTraceToString().lines().forEach {
/**
* Times a tiny log.
*/
inline fun <R> twig(logMessage: String, priority: Int = 0, block: () -> R): R = Bush.trunk.twig(logMessage, priority, block)
inline fun <R> twig(
logMessage: String,
priority: Int = 0,
block: () -> R
): R = Bush.trunk.twig(
logMessage,
priority,
block
)
/**
* Meticulously times a tiny task.
*/
inline fun <R> twigTask(logMessage: String, priority: Int = 0, block: () -> R): R = Bush.trunk.twigTask(logMessage, priority, block)
inline fun <R> twigTask(
logMessage: String,
priority: Int = 0,
block: () -> R
): R = Bush.trunk.twigTask(
logMessage,
priority,
block
)
/**
* A tiny log that does nothing. No one hears this twig fall in the woods.
@ -145,7 +162,11 @@ open class TroubleshootingTwig(
*/
fun spiffy(stackFrame: Int = 4, tag: String = "@TWIG"): (String) -> String = { logMessage: String ->
val stack = Thread.currentThread().stackTrace[stackFrame]
val time = String.format("$tag %1\$tD %1\$tI:%1\$tM:%1\$tS.%1\$tN", System.currentTimeMillis())
val time = String.format(
Locale.ENGLISH,
"$tag %1\$tD %1\$tI:%1\$tM:%1\$tS.%1\$tN",
System.currentTimeMillis()
)
val className = stack.className.split(".").lastOrNull()?.split("\$")?.firstOrNull()
val tags = Bush.leaves.joinToString(" #", "#")
"$time[$className:${stack.lineNumber}]($tags) $logMessage"

View File

@ -56,7 +56,8 @@ open class CompactBlockDownloader private constructor(val compactBlockStore: Com
* @param height the height to which the data will rewind.
*/
suspend fun rewindToHeight(height: BlockHeight) =
// TODO: cancel anything in flight
// TODO [#685]: cancel anything in flight
// TODO [#685]: https://github.com/zcash/zcash-android-wallet-sdk/issues/685
compactBlockStore.rewindTo(height)
/**
@ -80,7 +81,7 @@ open class CompactBlockDownloader private constructor(val compactBlockStore: Com
try {
result = lightWalletService.getServerInfo()
} catch (e: StatusRuntimeException) {
retryUpTo(6) {
retryUpTo(GET_SERVER_INFO_RETRIES) {
twig("WARNING: reconnecting to service in response to failure (retry #${it + 1}): $e")
lightWalletService.reconnect()
result = lightWalletService.getServerInfo()
@ -93,6 +94,7 @@ open class CompactBlockDownloader private constructor(val compactBlockStore: Com
newService: LightWalletService,
errorHandler: (Throwable) -> Unit = { throw it }
) = withContext(IO) {
@Suppress("TooGenericExceptionCaught")
try {
val existing = lightWalletService.getServerInfo()
val new = newService.getServerInfo()
@ -139,6 +141,7 @@ open class CompactBlockDownloader private constructor(val compactBlockStore: Com
//
private suspend fun CoroutineScope.gracefullyShutdown(service: LightWalletService) = launch {
@Suppress("MagicNumber")
delay(2_000L)
tryWarn("Warning: error while shutting down service") {
service.shutdown()
@ -160,4 +163,8 @@ open class CompactBlockDownloader private constructor(val compactBlockStore: Com
it.add("chainName")
}
}
companion object {
private const val GET_SERVER_INFO_RETRIES = 6
}
}

View File

@ -54,6 +54,7 @@ abstract class DerivedDataDb : RoomDatabase() {
// Migrations
//
@Suppress("MagicNumber")
companion object {
val MIGRATION_3_4 = object : Migration(3, 4) {
@ -248,6 +249,7 @@ interface AccountDao {
* whether transactions are mined.
*/
@Dao
@Suppress("TooManyFunctions")
interface TransactionDao {
@Query("SELECT COUNT(id_tx) FROM transactions")
suspend fun count(): Int
@ -365,7 +367,8 @@ interface TransactionDao {
ON transactions.id_tx = sent_notes.tx
LEFT JOIN blocks
ON transactions.block = blocks.height
/* we want all received txs except those that are change and all sent transactions (even those that haven't been mined yet). Note: every entry in the 'send_notes' table has a non-null value for 'address' */
/* we want all received txs except those that are change and all sent transactions (even those that haven't been
mined yet). Note: every entry in the 'send_notes' table has a non-null value for 'address' */
WHERE ( sent_notes.address IS NULL
AND received_notes.is_change != 1 )
OR sent_notes.address IS NOT NULL
@ -421,7 +424,11 @@ interface TransactionDao {
LIMIT :limit
"""
)
suspend fun findAllTransactionsByRange(blockRangeStart: Long, blockRangeEnd: Long = blockRangeStart, limit: Int = Int.MAX_VALUE): List<ConfirmedTransaction>
suspend fun findAllTransactionsByRange(
blockRangeStart: Long,
blockRangeEnd: Long = blockRangeStart,
limit: Int = Int.MAX_VALUE
): List<ConfirmedTransaction>
// Experimental: cleanup cancelled transactions
// This should probably be a rust call but there's not a lot of bandwidth for this
@ -431,6 +438,7 @@ interface TransactionDao {
@Transaction
suspend fun cleanupCancelledTx(rawTransactionId: ByteArray): Boolean {
var success = false
@Suppress("TooGenericExceptionCaught")
try {
var hasInitialMatch = false
twig("[cleanup] cleanupCancelledTx starting...")
@ -452,6 +460,7 @@ interface TransactionDao {
@Transaction
suspend fun removeInvalidOutboundTransaction(transactionId: Long): Boolean {
var success = false
@Suppress("TooGenericExceptionCaught")
try {
twig("[cleanup] removing invalid transactionId:$transactionId")
val result = unspendTransactionNotes(transactionId)
@ -461,7 +470,8 @@ interface TransactionDao {
deleteSentNote(noteId)
}
// delete the UTXOs because these are effectively cached and we don't have a good way of knowing whether they're spent
// delete the UTXOs because these are effectively cached and we don't have a good way of knowing whether
// they're spent
deleteUtxos(transactionId).let { count ->
twig("[cleanup] removed $count UTXOs matching transactionId $transactionId")
}

View File

@ -40,6 +40,7 @@ abstract class PendingTransactionDb : RoomDatabase() {
* Data access object providing crud for pending transactions.
*/
@Dao
@Suppress("TooManyFunctions")
interface PendingTransactionDao {
@Insert(onConflict = OnConflictStrategy.ABORT)
suspend fun create(transaction: PendingTransactionEntity): Long
@ -72,7 +73,10 @@ interface PendingTransactionDao {
@Query("UPDATE pending_transactions SET minedHeight = :minedHeight WHERE id = :id")
suspend fun updateMinedHeight(id: Long, minedHeight: Long)
@Query("UPDATE pending_transactions SET raw = :raw, rawTransactionId = :rawTransactionId, expiryHeight = :expiryHeight WHERE id = :id")
@Query(
"UPDATE pending_transactions SET raw = :raw, rawTransactionId = :rawTransactionId," +
" expiryHeight = :expiryHeight WHERE id = :id"
)
suspend fun updateEncoding(id: Long, raw: ByteArray, rawTransactionId: ByteArray, expiryHeight: Long?)
@Query("UPDATE pending_transactions SET errorMessage = :errorMessage, errorCode = :errorCode WHERE id = :id")

View File

@ -2,10 +2,13 @@
package cash.z.ecc.android.sdk.internal.ext
import java.util.Locale
internal fun ByteArray.toHexReversed(): String {
val sb = StringBuilder(size * 2)
var i = size - 1
while (i >= 0)
sb.append(String.format("%02x", this[i--]))
while (i >= 0) {
sb.append(String.format(Locale.ROOT, "%02x", this[i--]))
}
return sb.toString()
}

View File

@ -2,6 +2,7 @@ package cash.z.ecc.android.sdk.internal.ext
import cash.z.ecc.android.sdk.internal.twig
@Suppress("SwallowedException", "TooGenericExceptionCaught")
internal inline fun <R> tryNull(block: () -> R): R? {
return try {
block()
@ -17,6 +18,7 @@ internal inline fun <R> tryNull(block: () -> R): R? {
* @param ifContains only convert an exception into a warning if it contains the given text
* @param unlessContains convert all exceptions into warnings unless they contain the given text
*/
@Suppress("TooGenericExceptionCaught")
internal inline fun <R> tryWarn(
message: String,
ifContains: String? = null,

View File

@ -1,21 +1,25 @@
package cash.z.ecc.android.sdk.internal.ext
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.provider.Settings
import cash.z.ecc.android.sdk.internal.twig
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
@Deprecated(message = InsecureWarning.message)
class SampleSpendingKeyProvider(private val seedValue: String) : ReadWriteProperty<Any?, String> {
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
twig("Set value called on property: $property, with value: $value.")
}
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
// dynamically generating keys, based on seed is out of scope for this sample
if (seedValue != "dummyseed") throw IllegalStateException("This sample provider only supports the dummy seed")
if (seedValue != "dummyseed") {
error("This sample provider only supports the dummy seed")
}
return "secret-extended-key-test1q0f0urnmqqqqpqxlree5urprcmg9pdgvr2c88qhm862etv65eu84r9zwannpz4g88299xyhv7wf9" +
"xkecag653jlwwwyxrymfraqsnz8qfgds70qjammscxxyl7s7p9xz9w906epdpy8ztsjd7ez7phcd5vj7syx68sjskqs8j9lef2uu" +
"acghsh8puuvsy9u25pfvcdznta33qe6xh5lrlnhdkgymnpdug4jm6tpf803cad6tqa9c0ewq9l03fqxatevm97jmuv8u0ccxjews5"
@ -79,7 +83,7 @@ class BlockingProvider<T>(var value: T, val delay: Long = 5000L) : ReadWriteProp
* https://github.com/scottyab/AESCrypt-Android/blob/master/aescrypt/src/main/java/com/scottyab/aescrypt/AESCrypt.java
* https://github.com/iamMehedi/Secured-Preference-Store
*/
@SuppressLint("HardwareIds")
@Suppress("HardwareIds", "UtilityClassWithPublicConstructor")
@Deprecated(message = InsecureWarning.message)
class SeedGenerator {
companion object {

View File

@ -26,12 +26,15 @@ suspend inline fun retryUpTo(
) {
var failedAttempts = 0
while (failedAttempts <= retries) {
@Suppress("TooGenericExceptionCaught")
try {
block(failedAttempts)
return
} catch (t: Throwable) {
failedAttempts++
if (failedAttempts > retries) throw exceptionWrapper(t)
if (failedAttempts > retries) {
throw exceptionWrapper(t)
}
val duration = (initialDelayMillis.toDouble() * Math.pow(2.0, failedAttempts.toDouble() - 1)).toLong()
twig("failed due to $t retrying ($failedAttempts/$retries) in ${duration}s...")
delay(duration)
@ -52,6 +55,7 @@ suspend inline fun retryUpTo(
inline fun retrySimple(retries: Int = 2, sleepTime: Long = 20L, block: (Int) -> Unit) {
var failedAttempts = 0
while (failedAttempts <= retries) {
@Suppress("TooGenericExceptionCaught")
try {
block(failedAttempts)
return
@ -73,14 +77,17 @@ inline fun retrySimple(retries: Int = 2, sleepTime: Long = 20L, block: (Int) ->
* @param maxDelayMillis the maximum delay between retries.
* @param block the logic to run once and then run again if it fails.
*/
@Suppress("MagicNumber")
suspend inline fun retryWithBackoff(
noinline onErrorListener: ((Throwable) -> Boolean)? = null,
initialDelayMillis: Long = 1000L,
maxDelayMillis: Long = MAX_BACKOFF_INTERVAL,
block: () -> Unit
) {
var sequence = 0 // count up to the max and then reset to half. So that we don't repeat the max but we also don't repeat too much.
// count up to the max and then reset to half. So that we don't repeat the max but we also don't repeat too much.
var sequence = 0
while (true) {
@Suppress("TooGenericExceptionCaught")
try {
block()
return
@ -92,7 +99,10 @@ suspend inline fun retryWithBackoff(
sequence++
// initialDelay^(sequence/4) + jitter
var duration = Math.pow(initialDelayMillis.toDouble(), (sequence.toDouble() / 4.0)).toLong() + Random.nextLong(1000L)
var duration = Math.pow(
initialDelayMillis.toDouble(),
(sequence.toDouble() / 4.0)
).toLong() + Random.nextLong(1000L)
if (duration > maxDelayMillis) {
duration = maxDelayMillis - Random.nextLong(1000L) // include jitter but don't exceed max delay
sequence /= 2

View File

@ -86,7 +86,7 @@ class FlowPagedListBuilder<Key, Value>(
return when (this) {
is ExecutorCoroutineDispatcher -> executor
is MainCoroutineDispatcher -> MainThreadExecutor()
else -> throw IllegalStateException("Unable to create executor based on dispatcher: $this")
else -> error("Unable to create executor based on dispatcher: $this")
}
}

View File

@ -63,11 +63,9 @@ class LightWalletGrpcService private constructor(
override fun submitTransaction(spendTransaction: ByteArray): Service.SendResponse {
if (spendTransaction.isEmpty()) {
return Service.SendResponse.newBuilder().setErrorCode(3000)
.setErrorMessage(
"ERROR: failed to submit transaction because it was empty" +
" so this request was ignored on the client-side."
)
return Service.SendResponse.newBuilder()
.setErrorCode(EMPTY_TRANSACTION_ERROR_CODE)
.setErrorMessage(EMPTY_TRANSACTION_ERROR_MESSAGE)
.build()
}
val request =
@ -138,6 +136,10 @@ class LightWalletGrpcService private constructor(
}
companion object {
private const val EMPTY_TRANSACTION_ERROR_CODE = 3000
private const val EMPTY_TRANSACTION_ERROR_MESSAGE = "ERROR: failed to submit transaction because it was" +
" empty so this request was ignored on the client-side."
fun new(context: Context, lightWalletEndpoint: LightWalletEndpoint): LightWalletGrpcService {
val channel = createDefaultChannel(context, lightWalletEndpoint)

View File

@ -71,7 +71,10 @@ interface LightWalletService {
*
* @return a list of transactions that correspond to the given address for the given range.
*/
fun getTAddressTransactions(tAddress: String, blockHeightRange: ClosedRange<BlockHeight>): List<Service.RawTransaction>
fun getTAddressTransactions(
tAddress: String,
blockHeightRange: ClosedRange<BlockHeight>
): List<Service.RawTransaction>
/**
* Reconnect to the same or a different server. This is useful when the connection is

View File

@ -30,6 +30,7 @@ import java.io.File
*
* @param pageSize transactions per page. This influences pre-fetch and memory configuration.
*/
@Suppress("TooManyFunctions")
internal class PagedTransactionRepository private constructor(
private val zcashNetwork: ZcashNetwork,
private val db: DerivedDataDb,
@ -105,11 +106,14 @@ internal class PagedTransactionRepository private constructor(
}
}
// TODO: begin converting these into Data Access API. For now, just collect the desired operations and iterate/refactor, later
// TODO [#681]: begin converting these into Data Access API. For now, just collect the desired
// operations and iterate/refactor, later
// TODO [#681]: https://github.com/zcash/zcash-android-wallet-sdk/issues/681
suspend fun findBlockHash(height: BlockHeight): ByteArray? = blocks.findHashByHeight(height.value)
suspend fun getTransactionCount(): Int = transactions.count()
// TODO: convert this into a wallet repository rather than "transaction repository"
// TODO [#681]: convert this into a wallet repository rather than "transaction repository"
// TODO [#681]: https://github.com/zcash/zcash-android-wallet-sdk/issues/681
companion object {
@Suppress("LongParameterList")
@ -149,8 +153,11 @@ internal class PagedTransactionRepository private constructor(
.addMigrations(DerivedDataDb.MIGRATION_5_6)
.addMigrations(DerivedDataDb.MIGRATION_6_7)
.build().also {
// TODO: document why we do this. My guess is to catch database issues early or to trigger migrations--I forget why it was added but there was a good reason?
// TODO [#681]: document why we do this. My guess is to catch database issues early or to trigger
// migrations--I forget why it was added but there was a good reason?
// TODO [#681]: https://github.com/zcash/zcash-android-wallet-sdk/issues/681
withContext(SdkDispatchers.DATABASE_IO) {
// TODO [#649]: StrictMode policy violation: LeakedClosableViolation
// TODO [#649]: https://github.com/zcash/zcash-android-wallet-sdk/issues/649
it.openHelper.writableDatabase.beginTransaction()
it.openHelper.writableDatabase.endTransaction()
@ -190,7 +197,8 @@ internal class PagedTransactionRepository private constructor(
rustBackend: RustBackend,
checkpoint: Checkpoint
) {
// TODO: consider converting these to typed exceptions in the welding layer
// TODO [#681]: consider converting these to typed exceptions in the welding layer
// TODO [#681]: https://github.com/zcash/zcash-android-wallet-sdk/issues/681
tryWarn(
"Warning: did not initialize the blocks table. It probably was already initialized.",
ifContains = "table is not empty"
@ -208,11 +216,13 @@ internal class PagedTransactionRepository private constructor(
rustBackend: RustBackend,
viewingKeys: List<UnifiedFullViewingKey>
) {
// TODO: consider converting these to typed exceptions in the welding layer
// TODO [#681]: consider converting these to typed exceptions in the welding layer
// TODO [#681]: https://github.com/zcash/zcash-android-wallet-sdk/issues/681
tryWarn(
"Warning: did not initialize the accounts table. It probably was already initialized.",
ifContains = "table is not empty"
) {
@Suppress("SpreadOperator")
rustBackend.initAccountsTable(*viewingKeys.toTypedArray())
twig("Initialized the accounts table with ${viewingKeys.size} viewingKey(s)")
}

View File

@ -21,8 +21,6 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import kotlin.math.max
/**
@ -36,6 +34,7 @@ import kotlin.math.max
* id.
* @property service the lightwallet service used to submit transactions.
*/
@Suppress("TooManyFunctions")
class PersistentTransactionManager(
db: PendingTransactionDb,
internal val encoder: TransactionEncoder,
@ -85,6 +84,7 @@ class PersistentTransactionManager(
memo = memo.toByteArray(),
accountIndex = fromAccountIndex
)
@Suppress("TooGenericExceptionCaught")
try {
safeUpdate("creating tx in DB") {
tx = findById(create(tx))!!
@ -113,6 +113,8 @@ class PersistentTransactionManager(
): PendingTransaction = withContext(Dispatchers.IO) {
twig("managing the creation of a transaction")
var tx = pendingTx as PendingTransactionEntity
@Suppress("TooGenericExceptionCaught")
try {
twig("beginning to encode transaction with : $encoder")
val encodedTx = encoder.createTransaction(
@ -150,6 +152,7 @@ class PersistentTransactionManager(
): PendingTransaction {
twig("managing the creation of a shielding transaction")
var tx = pendingTx as PendingTransactionEntity
@Suppress("TooGenericExceptionCaught")
try {
twig("beginning to encode shielding transaction with : $encoder")
val encodedTx = encoder.createShieldingTransaction(
@ -186,11 +189,17 @@ class PersistentTransactionManager(
" transaction found that matches the one being submitted. Verify that the" +
" transaction still exists among the set of pending transactions."
)
@Suppress("TooGenericExceptionCaught")
try {
// do nothing if failed or cancelled
when {
tx.isFailedEncoding() -> twig("Warning: this transaction will not be submitted because it failed to be encoded.")
tx.isCancelled() -> twig("Warning: ignoring cancelled transaction with id ${tx.id}. We will not submit it to the network because it has been cancelled.")
tx.isFailedEncoding() ->
twig("Warning: this transaction will not be submitted because it failed to be encoded.")
tx.isCancelled() ->
twig(
"Warning: ignoring cancelled transaction with id ${tx.id}. We will not submit it to" +
" the network because it has been cancelled."
)
else -> {
twig("submitting transaction with memo: ${tx.memo} amount: ${tx.value}", -1)
val response = service.submitTransaction(tx.raw)
@ -260,7 +269,11 @@ class PersistentTransactionManager(
withContext(IO) {
twig("[cleanup] marking pendingTx $id for deletion")
removeRawTransactionId(id)
updateError(id, "safe to delete", -9090)
updateError(
id,
SAFE_TO_DELETE_ERROR_MESSAGE,
SAFE_TO_DELETE_ERROR_CODE
)
}
}
@ -285,19 +298,23 @@ class PersistentTransactionManager(
//
/**
* Updating the pending transaction is often done at the end of a function but still should
* happen within a try/catch block, surrounded by logging. So this helps with that while also
* ensuring that no other coroutines are concurrently interacting with the DAO.
* Updating the pending transaction is often done at the end of a function but still should happen within a
* try/catch block, surrounded by logging. So this helps with that while also ensuring that no other coroutines are
* concurrently interacting with the DAO.
*/
private suspend fun <R> safeUpdate(logMessage: String = "", priority: Int = 0, block: suspend PendingTransactionDao.() -> R): R? {
private suspend fun <R> safeUpdate(
logMessage: String = "",
priority: Int = 0,
block: suspend PendingTransactionDao.() -> R
): R? {
@Suppress("TooGenericExceptionCaught")
return try {
twig(logMessage, priority)
pendingTransactionDao { block() }
} catch (t: Throwable) {
val stacktrace = StringWriter().also { t.printStackTrace(PrintWriter(it)) }.toString()
twig(
"Unknown error while attempting to '$logMessage':" +
" ${t.message} caused by: ${t.cause} stacktrace: $stacktrace",
" ${t.message} caused by: ${t.cause} stacktrace: ${t.stackTrace}",
priority
)
null
@ -318,5 +335,8 @@ class PersistentTransactionManager(
/** Error code for an error while submitting a transaction */
const val ERROR_SUBMITTING = 3000
private const val SAFE_TO_DELETE_ERROR_MESSAGE = "safe to delete"
const val SAFE_TO_DELETE_ERROR_CODE = -9090
}
}

View File

@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.Flow
* particularly after failed attempts or dropped connectivity. The intent is to help see outbound
* transactions through to completion.
*/
@Suppress("TooManyFunctions")
interface OutboundTransactionManager {
/**
* Initialize a spend with the main purpose of creating an idea to use for tracking it until

View File

@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.Flow
/**
* Repository of wallet transactions, providing an agnostic interface to the underlying information.
*/
@Suppress("TooManyFunctions")
interface TransactionRepository {
/**

View File

@ -124,6 +124,7 @@ internal class WalletTransactionEncoder(
"creating transaction to spend $amount zatoshi to" +
" ${toAddress.masked()} with memo $memo"
) {
@Suppress("TooGenericExceptionCaught")
try {
val branchId = getConsensusBranchId()
SaplingParamTool.ensureParams((rustBackend as RustBackend).pathParamsDir)
@ -137,7 +138,7 @@ internal class WalletTransactionEncoder(
memo
)
} catch (t: Throwable) {
twig("${t.message}")
twig("Caught exception while creating transaction ${t.message}, caused by: ${t.cause}.")
throw t
}
}.also { result ->
@ -151,6 +152,7 @@ internal class WalletTransactionEncoder(
memo: ByteArray? = byteArrayOf()
): Long {
return twigTask("creating transaction to shield all UTXOs") {
@Suppress("TooGenericExceptionCaught")
try {
SaplingParamTool.ensureParams((rustBackend as RustBackend).pathParamsDir)
twig("params exist! attempting to shield...")
@ -160,9 +162,10 @@ internal class WalletTransactionEncoder(
memo
)
} catch (t: Throwable) {
// TODO: if this error matches: Insufficient balance (have 0, need 1000 including fee)
// then consider custom error that says no UTXOs existed to shield
twig("Shield failed due to: ${t.message}")
// TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee)
// then consider custom error that says no UTXOs existed to shield
// TODO [#680]: https://github.com/zcash/zcash-android-wallet-sdk/issues/680
twig("Shield failed due to: ${t.message}, caused by: ${t.cause}.")
throw t
}
}.also { result ->

View File

@ -31,6 +31,7 @@ internal class NativeLibraryLoader(private val libraryName: String) {
}
}
@Suppress("TooGenericExceptionCaught")
private suspend fun loadRustLibrary() {
try {
withContext(Dispatchers.IO) {

View File

@ -20,6 +20,7 @@ import java.io.File
* not be called directly by code outside of the SDK. Instead, one of the higher-level components
* should be used such as Wallet.kt or CompactBlockProcessor.kt.
*/
@Suppress("TooManyFunctions")
internal class RustBackend private constructor(
override val network: ZcashNetwork,
val birthdayHeight: BlockHeight,
@ -67,6 +68,7 @@ internal class RustBackend private constructor(
numberOfAccounts: Int
): Array<UnifiedFullViewingKey> {
return DerivationTool.deriveUnifiedFullViewingKeys(seed, network, numberOfAccounts).apply {
@Suppress("SpreadOperator")
initAccountsTable(*this)
}
}
@ -96,7 +98,10 @@ internal class RustBackend private constructor(
}
override suspend fun getTransparentAddress(account: Int, index: Int): String {
throw NotImplementedError("TODO: implement this at the zcash_client_sqlite level. But for now, use DerivationTool, instead to derive addresses from seeds")
throw NotImplementedError(
"TODO: implement this at the zcash_client_sqlite level. But for now, use " +
"DerivationTool, instead to derive addresses from seeds"
)
}
override suspend fun getBalance(account: Int): Zatoshi {
@ -325,15 +330,18 @@ internal class RustBackend private constructor(
// * We're able to keep the "unsafe" byteArray functions private and wrap them in typeSafe
// * equivalents and, eventually, surface any parse errors (for now, errors are only logged).
// */
// override fun parseTransactionDataList(tdl: LocalRpcTypes.TransactionDataList): LocalRpcTypes.TransparentTransactionList {
// return try {
// // serialize the list, send it over to rust and get back a serialized set of results that we parse out and return
// return LocalRpcTypes.TransparentTransactionList.parseFrom(parseTransactionDataList(tdl.toByteArray()))
// } catch (t: Throwable) {
// twig("ERROR: failed to parse transaction data list due to: $t caused by: ${t.cause}")
// LocalRpcTypes.TransparentTransactionList.newBuilder().build()
// }
// }
// override fun parseTransactionDataList(
// tdl: LocalRpcTypes.TransactionDataList
// ): LocalRpcTypes.TransparentTransactionList {
// return try {
// // serialize the list, send it over to rust and get back a serialized set of results that we parse out
// // and return
// return LocalRpcTypes.TransparentTransactionList.parseFrom(parseTransactionDataList(tdl.toByteArray()))
// } catch (t: Throwable) {
// twig("ERROR: failed to parse transaction data list due to: $t caused by: ${t.cause}")
// LocalRpcTypes.TransparentTransactionList.newBuilder().build()
// }
// }
/**
* Exposes all of the librustzcash functions along with helpers for loading the static library.
@ -390,6 +398,7 @@ internal class RustBackend private constructor(
): Boolean
@JvmStatic
@Suppress("LongParameterList")
private external fun initBlocksTable(
dbDataPath: String,
height: Long,
@ -483,6 +492,7 @@ internal class RustBackend private constructor(
)
@JvmStatic
@Suppress("LongParameterList")
private external fun createToAddress(
dbDataPath: String,
consensusBranchId: Long,
@ -516,6 +526,7 @@ internal class RustBackend private constructor(
private external fun branchIdForHeight(height: Long, networkId: Int): Long
@JvmStatic
@Suppress("LongParameterList")
private external fun putUtxo(
dbDataPath: String,
tAddress: String,

View File

@ -13,10 +13,12 @@ import cash.z.ecc.android.sdk.type.UnifiedFullViewingKey
* It is not documented because it is not intended to be used, directly.
* Instead, use the synchronizer or one of its subcomponents.
*/
@Suppress("TooManyFunctions")
internal interface RustBackendWelding {
val network: ZcashNetwork
@Suppress("LongParameterList")
suspend fun createToAddress(
consensusBranchId: Long,
account: Int,
@ -75,6 +77,7 @@ internal interface RustBackendWelding {
*/
suspend fun validateCombinedChain(): BlockHeight?
@Suppress("LongParameterList")
suspend fun putUtxo(
tAddress: String,
txId: ByteArray,
@ -84,7 +87,10 @@ internal interface RustBackendWelding {
height: BlockHeight
): Boolean
suspend fun clearUtxos(tAddress: String, aboveHeightInclusive: BlockHeight = BlockHeight(network.saplingActivationHeight.value)): Boolean
suspend fun clearUtxos(
tAddress: String,
aboveHeightInclusive: BlockHeight = BlockHeight(network.saplingActivationHeight.value)
): Boolean
suspend fun getDownloadedUtxoBalance(address: String): WalletBalance

View File

@ -44,7 +44,8 @@ data class BlockHeight internal constructor(val value: Long) : Comparable<BlockH
/**
* @param zcashNetwork Network to use for the block height.
* @param blockHeight The block height. Must be in range of a UInt32 AND must be greater than the network's sapling activation height.
* @param blockHeight The block height. Must be in range of a UInt32 AND must be greater than the network's
* sapling activation height.
*/
fun new(zcashNetwork: ZcashNetwork, blockHeight: Long): BlockHeight {
require(blockHeight >= zcashNetwork.saplingActivationHeight.value) {

View File

@ -34,7 +34,8 @@ internal object CheckpointTool {
network: ZcashNetwork,
birthdayHeight: BlockHeight?
): Checkpoint {
// TODO: potentially pull from shared preferences first
// TODO [#684]: potentially pull from shared preferences first
// TODO [#684]: https://github.com/zcash/zcash-android-wallet-sdk/issues/684
return loadCheckpointFromAssets(context, network, birthdayHeight)
}
@ -125,7 +126,8 @@ internal object CheckpointTool {
}
/**
* @param treeFiles A list of files, sorted in descending order based on `int` value of the first part of the filename.
* @param treeFiles A list of files, sorted in descending order based on `int` value of the first part of
* the filename.
*/
@VisibleForTesting
internal suspend fun getFirstValidWalletBirthday(
@ -136,6 +138,7 @@ internal object CheckpointTool {
): Checkpoint {
var lastException: Exception? = null
treeFiles.forEach { treefile ->
@Suppress("TooGenericExceptionCaught")
try {
val jsonString = withContext(Dispatchers.IO) {
context.assets.open("$directory/$treefile").use { inputStream ->
@ -157,7 +160,8 @@ internal object CheckpointTool {
lastException = exception
if (IS_FALLBACK_ON_FAILURE) {
// TODO: If we ever add crash analytics hooks, this would be something to report
// TODO [#684]: If we ever add crash analytics hooks, this would be something to report
// TODO [#684]: https://github.com/zcash/zcash-android-wallet-sdk/issues/684
twig("Malformed birthday file $t")
} else {
throw exception

View File

@ -5,8 +5,10 @@ import cash.z.ecc.android.sdk.jni.RustBackendWelding
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.type.UnifiedFullViewingKey
@Suppress("UtilityClassWithPublicConstructor")
class DerivationTool {
@Suppress("TooManyFunctions")
companion object : RustBackendWelding.Derivation {
/**
@ -18,7 +20,11 @@ class DerivationTool {
*
* @return the UFVKs derived from the seed, encoded as Strings.
*/
override suspend fun deriveUnifiedFullViewingKeys(seed: ByteArray, network: ZcashNetwork, numberOfAccounts: Int): Array<UnifiedFullViewingKey> =
override suspend fun deriveUnifiedFullViewingKeys(
seed: ByteArray,
network: ZcashNetwork,
numberOfAccounts: Int
): Array<UnifiedFullViewingKey> =
withRustBackendLoaded {
deriveUnifiedFullViewingKeysFromSeed(seed, numberOfAccounts, networkId = network.id).map {
UnifiedFullViewingKey(it)
@ -32,7 +38,10 @@ class DerivationTool {
*
* @return the viewing key that corresponds to the spending key.
*/
override suspend fun deriveViewingKey(spendingKey: String, network: ZcashNetwork): String = withRustBackendLoaded {
override suspend fun deriveViewingKey(
spendingKey: String,
network: ZcashNetwork
): String = withRustBackendLoaded {
deriveExtendedFullViewingKey(spendingKey, networkId = network.id)
}
@ -45,10 +54,13 @@ class DerivationTool {
*
* @return the spending keys that correspond to the seed, formatted as Strings.
*/
override suspend fun deriveSpendingKeys(seed: ByteArray, network: ZcashNetwork, numberOfAccounts: Int): Array<String> =
withRustBackendLoaded {
deriveExtendedSpendingKeys(seed, numberOfAccounts, networkId = network.id)
}
override suspend fun deriveSpendingKeys(
seed: ByteArray,
network: ZcashNetwork,
numberOfAccounts: Int
): Array<String> = withRustBackendLoaded {
deriveExtendedSpendingKeys(seed, numberOfAccounts, networkId = network.id)
}
/**
* Given a seed and account index, return the associated Unified Address.
@ -72,26 +84,45 @@ class DerivationTool {
*
* @return the address that corresponds to the viewing key.
*/
override suspend fun deriveUnifiedAddress(viewingKey: String, network: ZcashNetwork): String = withRustBackendLoaded {
override suspend fun deriveUnifiedAddress(
viewingKey: String,
network: ZcashNetwork
): String = withRustBackendLoaded {
deriveUnifiedAddressFromViewingKey(viewingKey, networkId = network.id)
}
// WIP probably shouldn't be used just yet. Why?
// - because we need the private key associated with this seed and this function doesn't return it.
// - the underlying implementation needs to be split out into a few lower-level calls
override suspend fun deriveTransparentAddress(seed: ByteArray, network: ZcashNetwork, account: Int, index: Int): String = withRustBackendLoaded {
override suspend fun deriveTransparentAddress(
seed: ByteArray,
network: ZcashNetwork,
account: Int,
index: Int
): String = withRustBackendLoaded {
deriveTransparentAddressFromSeed(seed, account, index, networkId = network.id)
}
override suspend fun deriveTransparentAddressFromPublicKey(publicKey: String, network: ZcashNetwork): String = withRustBackendLoaded {
override suspend fun deriveTransparentAddressFromPublicKey(
publicKey: String,
network: ZcashNetwork
): String = withRustBackendLoaded {
deriveTransparentAddressFromPubKey(pk = publicKey, networkId = network.id)
}
override suspend fun deriveTransparentAddressFromAccountPrivateKey(privateKey: String, network: ZcashNetwork, index: Int): String = withRustBackendLoaded {
override suspend fun deriveTransparentAddressFromAccountPrivateKey(
privateKey: String,
network: ZcashNetwork,
index: Int
): String = withRustBackendLoaded {
deriveTransparentAddressFromAccountPrivKey(sk = privateKey, index = index, networkId = network.id)
}
override suspend fun deriveTransparentAccountPrivateKey(seed: ByteArray, network: ZcashNetwork, account: Int): String = withRustBackendLoaded {
override suspend fun deriveTransparentAccountPrivateKey(
seed: ByteArray,
network: ZcashNetwork,
account: Int
): String = withRustBackendLoaded {
deriveTransparentAccountPrivKeyFromSeed(seed, account, networkId = network.id)
}
@ -142,7 +173,12 @@ class DerivationTool {
private external fun deriveUnifiedAddressFromViewingKey(key: String, networkId: Int): String
@JvmStatic
private external fun deriveTransparentAddressFromSeed(seed: ByteArray, account: Int, index: Int, networkId: Int): String
private external fun deriveTransparentAddressFromSeed(
seed: ByteArray,
account: Int,
index: Int,
networkId: Int
): String
@JvmStatic
private external fun deriveTransparentAddressFromPubKey(pk: String, networkId: Int): String
@ -151,6 +187,10 @@ class DerivationTool {
private external fun deriveTransparentAddressFromAccountPrivKey(sk: String, index: Int, networkId: Int): String
@JvmStatic
private external fun deriveTransparentAccountPrivKeyFromSeed(seed: ByteArray, account: Int, networkId: Int): String
private external fun deriveTransparentAccountPrivKeyFromSeed(
seed: ByteArray,
account: Int,
networkId: Int
): String
}
}

View File

@ -1,10 +1,10 @@
package cash.z.ecc.android.sdk.ext
import cash.z.ecc.android.sdk.model.Zatoshi
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.math.BigDecimal
import java.math.MathContext
import kotlin.test.assertEquals
internal class ConversionsTest {

View File

@ -1,13 +0,0 @@
@file:Suppress("ktlint:filename")
package cash.z.wallet
import org.mockito.Mockito
/**
* Use in place of `any()` to fix the issue with mockito `any` returning null (so you can't pass it to functions that
* take a non-null param)
*
* TODO: perhaps submit this function to the mockito kotlin project because it allows the use of non-null 'any()'
*/
internal fun <T> anyNotNull() = Mockito.any<T>() as T

View File

@ -1,464 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>ComplexCondition:CompactBlockProcessor.kt$CompactBlockProcessor$(null == lastScannedHeight &amp;&amp; targetHeight &lt; lastLocalBlock) || (null != lastScannedHeight &amp;&amp; targetHeight &lt; lastScannedHeight)</ID>
<ID>ComplexMethod:SdkSynchronizer.kt$SdkSynchronizer$private suspend fun refreshPendingTransactions()</ID>
<ID>ComplexMethod:SendFragment.kt$SendFragment$private fun onPendingTxUpdated(pendingTransaction: PendingTransaction?)</ID>
<ID>ComplexMethod:Transactions.kt$ConfirmedTransaction$override fun equals(other: Any?): Boolean</ID>
<ID>ComplexMethod:Transactions.kt$PendingTransactionEntity$override fun equals(other: Any?): Boolean</ID>
<ID>DestructuringDeclarationWithTooManyEntries:TestnetIntegrationTest.kt$TestnetIntegrationTest$val (height, hash, time, tree) = runBlocking { CheckpointTool.loadNearest( context, synchronizer.network, saplingActivation + 1 ) }</ID>
<ID>EmptyCatchBlock:SdkSynchronizer.kt$SdkSynchronizer${ }</ID>
<ID>EmptyFunctionBlock:MainActivity.kt$MainActivity${ }</ID>
<ID>EmptyFunctionBlock:Placeholders.kt$SampleSpendingKeyProvider${ }</ID>
<ID>EmptyFunctionBlock:ReproduceZ2TFailureTest.kt$ReproduceZ2TFailureTest${ }</ID>
<ID>EmptyFunctionBlock:SampleCodeTest.kt$SampleCodeTest${ }</ID>
<ID>ForbiddenComment:BalancePrinterUtil.kt$BalancePrinterUtil$// TODO: clear the dataDb but leave the cacheDb</ID>
<ID>ForbiddenComment:CheckpointTool.kt$CheckpointTool$// TODO: If we ever add crash analytics hooks, this would be something to report</ID>
<ID>ForbiddenComment:CheckpointTool.kt$CheckpointTool$// TODO: potentially pull from shared preferences first</ID>
<ID>ForbiddenComment:CompactBlockDownloader.kt$CompactBlockDownloader$// TODO: cancel anything in flight</ID>
<ID>ForbiddenComment:CompactBlockProcessor.kt$CompactBlockProcessor$// TODO: add a concept of original checkpoint height to the processor. For now, derive it</ID>
<ID>ForbiddenComment:CompactBlockProcessor.kt$CompactBlockProcessor$// TODO: more accurately track the utxos that were skipped (in theory, this could fail for other reasons)</ID>
<ID>ForbiddenComment:CompactBlockProcessor.kt$CompactBlockProcessor$// TODO: rethink this and make it easier to understand what's happening. Can we reduce this</ID>
<ID>ForbiddenComment:CompactBlockProcessor.kt$CompactBlockProcessor$// TODO: we still need a way to identify those transactions that failed to be enhanced</ID>
<ID>ForbiddenComment:CompactBlockProcessor.kt$CompactBlockProcessor$// todo: cleanup the way that we prevent this from running excessively</ID>
<ID>ForbiddenComment:ConsensusBranchId.kt$ConsensusBranchId.SPROUT$// TODO: see if we can find a way to not rely on this separate source of truth (either stop converting from hex to display name in the apps or use Rust to get this info)</ID>
<ID>ForbiddenComment:CurrencyFormatter.kt$Conversions$// TODO: provide a dynamic way to configure this globally for the SDK</ID>
<ID>ForbiddenComment:DarksideApi.kt$DarksideApi$// // TODO: change this service to accept ints as heights, like everywhere else</ID>
<ID>ForbiddenComment:GetAddressFragment.kt$GetAddressFragment$// TODO: show an example with the synchronizer</ID>
<ID>ForbiddenComment:PagedTransactionRepository.kt$PagedTransactionRepository$// TODO: begin converting these into Data Access API. For now, just collect the desired operations and iterate/refactor, later</ID>
<ID>ForbiddenComment:PagedTransactionRepository.kt$PagedTransactionRepository$// TODO: convert this into a wallet repository rather than "transaction repository"</ID>
<ID>ForbiddenComment:PagedTransactionRepository.kt$PagedTransactionRepository.Companion$// TODO: consider converting these to typed exceptions in the welding layer</ID>
<ID>ForbiddenComment:PagedTransactionRepository.kt$PagedTransactionRepository.Companion$// TODO: document why we do this. My guess is to catch database issues early or to trigger migrations--I forget why it was added but there was a good reason?</ID>
<ID>ForbiddenComment:SampleCodeTest.kt$SampleCodeTest$// TODO: call: Mnemonic::from_phrase(seed_phrase, Language::English).unwrap().entropy()</ID>
<ID>ForbiddenComment:SampleCodeTest.kt$SampleCodeTest$// TODO: call: bip39::Seed::new(&amp;Mnemonic::from_entropy(&amp;seed_bytes, Language::English).unwrap(), "")</ID>
<ID>ForbiddenComment:SampleCodeTest.kt$SampleCodeTest$// TODO: let mnemonic = Mnemonic::from_entropy(entropy, Language::English).unwrap();</ID>
<ID>ForbiddenComment:SampleCodeTest.kt$SampleCodeTest$// TODO: log(seedPhrase.asRawEntropy().asBip39seed())</ID>
<ID>ForbiddenComment:SaplingParamTool.kt$SaplingParamTool.Companion$// TODO: add logging and timeouts</ID>
<ID>ForbiddenComment:SdkSynchronizer.kt$SdkSynchronizer$// TODO: clean this up and handle enhancing gracefully</ID>
<ID>ForbiddenComment:SdkSynchronizer.kt$SdkSynchronizer$// TODO: refresh orchard balance</ID>
<ID>ForbiddenComment:SdkSynchronizer.kt$SdkSynchronizer$// TODO: this would be the place to clear out any stale pending transactions. Remove filter</ID>
<ID>ForbiddenComment:SdkSynchronizer.kt$SdkSynchronizer$// TODO: turn this section into the data access API. For now, just aggregate all the things that we want to do with the underlying data</ID>
<ID>ForbiddenComment:SetupTest.kt$SetupTest.Companion$// // TODO: fix this</ID>
<ID>ForbiddenComment:TestUtils.kt$* Use in place of `any()` to fix the issue with mockito `any` returning null (so you can't pass it to functions that * take a non-null param) * * TODO: perhaps submit this function to the mockito kotlin project because it allows the use of non-null 'any()'</ID>
<ID>ForbiddenComment:TestWallet.kt$TestWallet.Backups.DEFAULT$// TODO: get the proper birthday values for these wallets</ID>
<ID>ForbiddenComment:Transactions.kt$// TODO: test for off-by-one error here. Should we use &lt;= or &lt;</ID>
<ID>ForbiddenComment:WalletTransactionEncoder.kt$WalletTransactionEncoder$// TODO: if this error matches: Insufficient balance (have 0, need 1000 including fee)</ID>
<ID>FunctionParameterNaming:GetBlockFragment.kt$GetBlockFragment$_unused: View? = null</ID>
<ID>FunctionParameterNaming:GetBlockRangeFragment.kt$GetBlockRangeFragment$_unused: View</ID>
<ID>ImplicitDefaultLocale:BlockExt.kt$String.format("%02x", b)</ID>
<ID>ImplicitDefaultLocale:BlockExt.kt$String.format("%02x", this[i--])</ID>
<ID>ImplicitDefaultLocale:Twig.kt$TroubleshootingTwig.Companion$String.format("$tag %1\$tD %1\$tI:%1\$tM:%1\$tS.%1\$tN", System.currentTimeMillis())</ID>
<ID>InvalidPackageDeclaration:OpenForTesting.kt$package cash.z.ecc.android.sdk.annotation</ID>
<ID>LargeClass:CompactBlockProcessor.kt$CompactBlockProcessor</ID>
<ID>LongMethod:SdkSynchronizer.kt$SdkSynchronizer$private suspend fun refreshPendingTransactions()</ID>
<ID>LongParameterList:PagedTransactionRepository.kt$PagedTransactionRepository.Companion$( appContext: Context, zcashNetwork: ZcashNetwork, pageSize: Int = 10, rustBackend: RustBackend, birthday: Checkpoint, viewingKeys: List&lt;UnifiedViewingKey>, overwriteVks: Boolean = false )</ID>
<ID>LongParameterList:RustBackend.kt$RustBackend.Companion$( dbDataPath: String, account: Int, extsk: String, tsk: String, memo: ByteArray, spendParamsPath: String, outputParamsPath: String, networkId: Int )</ID>
<ID>LongParameterList:RustBackend.kt$RustBackend.Companion$( dbDataPath: String, consensusBranchId: Long, account: Int, extsk: String, to: String, value: Long, memo: ByteArray, spendParamsPath: String, outputParamsPath: String, networkId: Int )</ID>
<ID>LongParameterList:RustBackend.kt$RustBackend.Companion$( dbDataPath: String, height: Long, hash: String, time: Long, saplingTree: String, networkId: Int )</ID>
<ID>LongParameterList:RustBackend.kt$RustBackend.Companion$( dbDataPath: String, tAddress: String, txId: ByteArray, index: Int, script: ByteArray, value: Long, height: Long, networkId: Int )</ID>
<ID>LongParameterList:RustBackendWelding.kt$RustBackendWelding$( consensusBranchId: Long, account: Int, extsk: String, to: String, value: Long, memo: ByteArray? = byteArrayOf() )</ID>
<ID>LongParameterList:RustBackendWelding.kt$RustBackendWelding$( tAddress: String, txId: ByteArray, index: Int, script: ByteArray, value: Long, height: BlockHeight )</ID>
<ID>MagicNumber:BatchMetrics.kt$BatchMetrics$1000.0f</ID>
<ID>MagicNumber:BlockExt.kt$16</ID>
<ID>MagicNumber:BlockExt.kt$4</ID>
<ID>MagicNumber:CompactBlockDownloader.kt$CompactBlockDownloader$2_000L</ID>
<ID>MagicNumber:CompactBlockDownloader.kt$CompactBlockDownloader$6</ID>
<ID>MagicNumber:CompactBlockProcessor.kt$CompactBlockProcessor$100</ID>
<ID>MagicNumber:CompactBlockProcessor.kt$CompactBlockProcessor$100.0f</ID>
<ID>MagicNumber:CompactBlockProcessor.kt$CompactBlockProcessor$1000</ID>
<ID>MagicNumber:CompactBlockProcessor.kt$CompactBlockProcessor$100f</ID>
<ID>MagicNumber:CompactBlockProcessor.kt$CompactBlockProcessor$14</ID>
<ID>MagicNumber:CompactBlockProcessor.kt$CompactBlockProcessor$24</ID>
<ID>MagicNumber:CompactBlockProcessor.kt$CompactBlockProcessor$3</ID>
<ID>MagicNumber:CompactBlockProcessor.kt$CompactBlockProcessor$60</ID>
<ID>MagicNumber:CompactBlockProcessor.kt$CompactBlockProcessor$9</ID>
<ID>MagicNumber:CompactBlockProcessor.kt$CompactBlockProcessor.ProcessorInfo$100</ID>
<ID>MagicNumber:CompactBlockProcessor.kt$CompactBlockProcessor.ProcessorInfo$100.0f</ID>
<ID>MagicNumber:ConsensusBranchId.kt$ConsensusBranchId.BLOSSOM$0x2bb4_0e60</ID>
<ID>MagicNumber:ConsensusBranchId.kt$ConsensusBranchId.CANOPY$0xe9ff_75a6</ID>
<ID>MagicNumber:ConsensusBranchId.kt$ConsensusBranchId.HEARTWOOD$0xf5b9_230b</ID>
<ID>MagicNumber:ConsensusBranchId.kt$ConsensusBranchId.OVERWINTER$0x5ba8_1b19</ID>
<ID>MagicNumber:ConsensusBranchId.kt$ConsensusBranchId.SAPLING$0x76b8_09bb</ID>
<ID>MagicNumber:CurrencyFormatter.kt$Conversions$6</ID>
<ID>MagicNumber:DemoConstants.kt$DemoConstants$0.000018</ID>
<ID>MagicNumber:DemoConstants.kt$DemoConstants$0.00017</ID>
<ID>MagicNumber:DemoConstants.kt$DemoConstants$1075590</ID>
<ID>MagicNumber:DerivedDataDb.kt$DerivedDataDb.Companion.&lt;no name provided>$3</ID>
<ID>MagicNumber:DerivedDataDb.kt$DerivedDataDb.Companion.&lt;no name provided>$4</ID>
<ID>MagicNumber:DerivedDataDb.kt$DerivedDataDb.Companion.&lt;no name provided>$5</ID>
<ID>MagicNumber:DerivedDataDb.kt$DerivedDataDb.Companion.&lt;no name provided>$6</ID>
<ID>MagicNumber:DerivedDataDb.kt$DerivedDataDb.Companion.&lt;no name provided>$7</ID>
<ID>MagicNumber:Ext.kt$1000L</ID>
<ID>MagicNumber:GetBalanceFragment.kt$GetBalanceFragment$100</ID>
<ID>MagicNumber:GetBalanceFragment.kt$GetBalanceFragment$12</ID>
<ID>MagicNumber:GetBlockRangeFragment.kt$GetBlockRangeFragment$1000</ID>
<ID>MagicNumber:GetBlockRangeFragment.kt$GetBlockRangeFragment$1000.0</ID>
<ID>MagicNumber:GetPrivateKeyFragment.kt$GetPrivateKeyFragment$5</ID>
<ID>MagicNumber:Initializer.kt$99</ID>
<ID>MagicNumber:LightWalletGrpcService.kt$LightWalletGrpcService$3000</ID>
<ID>MagicNumber:ListTransactionsFragment.kt$ListTransactionsFragment$100</ID>
<ID>MagicNumber:ListUtxosFragment.kt$ListUtxosFragment$100</ID>
<ID>MagicNumber:PersistentTransactionManager.kt$PersistentTransactionManager$9090</ID>
<ID>MagicNumber:SdkSynchronizer.kt$SdkSynchronizer$5</ID>
<ID>MagicNumber:SendFragment.kt$SendFragment$100</ID>
<ID>MagicNumber:SendFragment.kt$SendFragment$12</ID>
<ID>MagicNumber:TransactionViewHolder.kt$TransactionViewHolder$1000L</ID>
<ID>MagicNumber:TransactionViewHolder.kt$TransactionViewHolder$180f</ID>
<ID>MagicNumber:Transactions.kt$100</ID>
<ID>MagicNumber:Transactions.kt$30</ID>
<ID>MagicNumber:Transactions.kt$9090</ID>
<ID>MagicNumber:Twig.kt$10L</ID>
<ID>MagicNumber:Twig.kt$1e5</ID>
<ID>MagicNumber:Twig.kt$TroubleshootingTwig$6</ID>
<ID>MagicNumber:UtxoViewHolder.kt$UtxoViewHolder$1000L</ID>
<ID>MagicNumber:WalletService.kt$1000L</ID>
<ID>MagicNumber:WalletService.kt$4.0</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$10</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$100</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$150</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$1_000L</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$20</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$20_000L</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$5</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$512</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$600_000L</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$75_000L</ID>
<ID>MatchingDeclarationName:CurrencyFormatter.kt$Conversions</ID>
<ID>MaxLineLength:BatchMetrics.kt$BatchMetrics$class</ID>
<ID>MaxLineLength:BlockHeight.kt$BlockHeight.Companion$*</ID>
<ID>MaxLineLength:BranchIdTest.kt$BranchIdTest$assertEquals("Invalid branch ID for $networkName at height $height on ${rustBackend.network.networkName}", branchId, actual)</ID>
<ID>MaxLineLength:BranchIdTest.kt$BranchIdTest$assertEquals("Invalid branch Id Hex value for $networkName at height $height on ${rustBackend.network.networkName}", branchHex, clientBranch)</ID>
<ID>MaxLineLength:BranchIdTest.kt$BranchIdTest.Companion$val mainnetBackend = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Mainnet, ZcashNetwork.Mainnet.saplingActivationHeight) }</ID>
<ID>MaxLineLength:BranchIdTest.kt$BranchIdTest.Companion$val testnetBackend = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Testnet, ZcashNetwork.Testnet.saplingActivationHeight) }</ID>
<ID>MaxLineLength:ChangeServiceTest.kt$ChangeServiceTest$"Exception was of the wrong type. Expected ${ChainInfoNotMatching::class.simpleName} but was ${caughtException!!::class.simpleName}"</ID>
<ID>MaxLineLength:ChangeServiceTest.kt$ChangeServiceTest$downloader</ID>
<ID>MaxLineLength:CheckpointTool.kt$CheckpointTool$* @param treeFiles A list of files, sorted in descending order based on `int` value of the first part of the filename.</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$"ERROR: unable to resolve reorg at height $result after ${consecutiveChainErrors.get()} correction attempts!"</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$(lastScannedHeight.value - range.start.value) / (range.endInclusive.value - range.start.value + 1).toFloat() * 100.0f</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$// Note: blocks are public information so it's okay to print them but, still, let's not unless we're debugging something</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$// TODO: more accurately track the utxos that were skipped (in theory, this could fail for other reasons)</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$// communicate that the wallet is no longer synced because it might remain this way for 20+ seconds because we only download on 20s time boundaries so we can't trigger any immediate action</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$// so we round down to the nearest 100 and then subtract 100 to ensure that the result is always at least 100 blocks away</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$// sometimes the initial block was inserted via checkpoint and will not appear in the cache. We can get the hash another way but prevHash is correctly null.</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$currentInfo.lastDownloadRange?.isEmpty() ?: true &amp;&amp; currentInfo.lastScanRange?.isEmpty() ?: true</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$if</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$lastScanRange</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$lowerBoundHeight + MAX_REORG_SIZE + 2</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$tempOldestTransactionHeight.value - tempOldestTransactionHeight.value.rem(ZcashSdk.MAX_REORG_SIZE) - ZcashSdk.MAX_REORG_SIZE.toLong()</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("$summary${if (result == BlockProcessingResult.FailedEnhance) " (but there were enhancement errors! We ignore those, for now. Memos in this block range are probably missing! This will be improved in a future release.)" else ""}! Sleeping for ${napTime}ms (latest height: ${currentInfo.networkBlockHeight}).")</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("Also clearing block cache back to $targetHeight. These rewound blocks will download in the next scheduled scan")</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("Rewinding from $lastScannedHeight to requested height: $height using target height: $targetHeight with last local block: $lastLocalBlock")</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("The next block block: ${errorInfo.errorHeight} which had hash ${errorInfo.actualPrevHash} but the expected hash was ${errorInfo.expectedPrevHash}")</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("Unable to process new blocks because we are disconnected! Attempting to reconnect in ${napTime}ms")</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("Warning: An ${error::class.java.simpleName} was encountered while verifying setup but it was ignored by the onSetupErrorHandler. Ignoring message: ${error.message}")</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("Warning: Fetching UTXOs is repeatedly failing! We will only try about ${(9 - failedUtxoFetches + 2) / 3} more times then give up for this session.")</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("Warning: Ignoring transaction at height ${utxo.height} @ index ${utxo.index} because it already exists")</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("We kept the cache blocks in place so we don't need to wait for the next scheduled download to rescan. Instead we will rescan and validate blocks ${range.start}..${range.endInclusive}")</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("batch scan complete! Total time: ${metrics.cumulativeTime} Total blocks measured: ${metrics.cumulativeItems} Cumulative bps: ${metrics.cumulativeIps.format()}")</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("batch scanned ($percent%): $lastScannedHeight/${range.endInclusive} | ${metrics.batchTime}ms, ${metrics.batchItems}blks, ${metrics.batchIps.format()}bps")</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("found $missingBlockCount missing blocks, downloading in $batches batches of $DOWNLOAD_BATCH_SIZE...")</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("not rewinding dataDb because the last scanned height is $lastScannedHeight and the last local block is $lastLocalBlock both of which are less than the target height of $targetHeight")</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("validation failed at block ${errorInfo.errorHeight} which had hash ${errorInfo.actualPrevHash} but the expected hash was ${errorInfo.expectedPrevHash}")</ID>
<ID>MaxLineLength:ConsensusBranchId.kt$ConsensusBranchId.SPROUT$// TODO: see if we can find a way to not rely on this separate source of truth (either stop converting from hex to display name in the apps or use Rust to get this info)</ID>
<ID>MaxLineLength:DarksideApi.kt$DarksideApi$Darkside.DarksideEmptyBlocks.newBuilder().setHeight(startHeight.value).setCount(count).setNonce(nonce).build()</ID>
<ID>MaxLineLength:DarksideApi.kt$DarksideApi$onNext(it.newBuilderForType().setData(it.data).setHeight(tipHeight.value).build())</ID>
<ID>MaxLineLength:DarksideApi.kt$DarksideApi$twig("resetting darksidewalletd with saplingActivation=$saplingActivationHeight branchId=$branchId chainName=$chainName")</ID>
<ID>MaxLineLength:DarksideApi.kt$DarksideApi.EmptyResponse$if (error != null) throw RuntimeException("Server responded with an error: $error caused by ${error?.cause}")</ID>
<ID>MaxLineLength:DarksideTestCoordinator.kt$DarksideTestCoordinator$if</ID>
<ID>MaxLineLength:DarksideTestCoordinator.kt$DarksideTestCoordinator$}</ID>
<ID>MaxLineLength:DarksideTestCoordinator.kt$DarksideTestCoordinator.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-large-reorg.txt"</ID>
<ID>MaxLineLength:DarksideTestCoordinator.kt$DarksideTestCoordinator.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-small-reorg.txt"</ID>
<ID>MaxLineLength:DarksideTestCoordinator.kt$DarksideTestCoordinator.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"</ID>
<ID>MaxLineLength:DarksideTestCoordinator.kt$DarksideTestCoordinator.Companion$"still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"</ID>
<ID>MaxLineLength:DarksideTestCoordinator.kt$DarksideTestCoordinator.DarksideChainMaker$.</ID>
<ID>MaxLineLength:DarksideTestCoordinator.kt$DarksideTestCoordinator.DarksideTestValidator$assertTrue("invalid available balance. Expected a minimum of $available but found ${balance?.available}", available &lt;= balance?.available?.value!!)</ID>
<ID>MaxLineLength:DarksideTestCoordinator.kt$DarksideTestCoordinator.DarksideTestValidator$assertTrue("invalid total balance. Expected a minimum of $total but found ${balance?.total}", total &lt;= balance?.total?.value!!)</ID>
<ID>MaxLineLength:DarksideTestPrerequisites.kt$DarksideTestPrerequisites$"Darkside tests are configured to only run on the Android Emulator. Please see https://github.com/zcash/zcash-android-wallet-sdk/blob/master/docs/tests/Darkside.md"</ID>
<ID>MaxLineLength:DemoConstants.kt$DemoConstants$"wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame"</ID>
<ID>MaxLineLength:DemoConstants.kt$DemoConstants$// corresponds to seed: urban kind wise collect social marble riot primary craft lucky head cause syrup odor artist decorate rhythm phone style benefit portion bus truck top</ID>
<ID>MaxLineLength:DerivationTool.kt$DerivationTool.Companion$override suspend</ID>
<ID>MaxLineLength:DerivationTool.kt$DerivationTool.Companion$private</ID>
<ID>MaxLineLength:DerivedDataDb.kt$TransactionDao$ /* we want all received txs except those that are change and all sent transactions (even those that haven't been mined yet). Note: every entry in the 'send_notes' table has a non-null value for 'address' */</ID>
<ID>MaxLineLength:DerivedDataDb.kt$TransactionDao$// delete the UTXOs because these are effectively cached and we don't have a good way of knowing whether they're spent</ID>
<ID>MaxLineLength:DerivedDataDb.kt$TransactionDao$suspend</ID>
<ID>MaxLineLength:Exceptions.kt$BirthdayException.ExactBirthdayNotFoundException$class</ID>
<ID>MaxLineLength:Exceptions.kt$CompactBlockProcessorException$ConfigurationException : CompactBlockProcessorException</ID>
<ID>MaxLineLength:Exceptions.kt$CompactBlockProcessorException$Disconnected : CompactBlockProcessorException</ID>
<ID>MaxLineLength:Exceptions.kt$CompactBlockProcessorException.EnhanceTransactionError$EnhanceTxDecryptError : EnhanceTransactionError</ID>
<ID>MaxLineLength:Exceptions.kt$CompactBlockProcessorException.EnhanceTransactionError$EnhanceTxDownloadError : EnhanceTransactionError</ID>
<ID>MaxLineLength:Exceptions.kt$CompactBlockProcessorException.EnhanceTransactionError$open</ID>
<ID>MaxLineLength:Exceptions.kt$CompactBlockProcessorException.MismatchedBranch$"Incompatible server: this client expects a server following consensus branch $clientBranch on $networkName but it was $serverBranch! Try updating the client or switching servers."</ID>
<ID>MaxLineLength:Exceptions.kt$CompactBlockProcessorException.MismatchedBranch$class</ID>
<ID>MaxLineLength:Exceptions.kt$CompactBlockProcessorException.MismatchedNetwork$"Incompatible server: this client expects a server using $clientNetwork but it was $serverNetwork! Try updating the client or switching servers."</ID>
<ID>MaxLineLength:Exceptions.kt$LightWalletException.ChangeServerException.ChainInfoNotMatching$class</ID>
<ID>MaxLineLength:Exceptions.kt$LightWalletException.ChangeServerException.StatusException.Companion$"Error: the new server is unavailable. Verify that the host and port are correct. Failed with $this"</ID>
<ID>MaxLineLength:Exceptions.kt$TransactionEncoderException.TransactionNotEncodedException$" with id $transactionId, does not have any raw data. This is a scenario where the wallet should have thrown"</ID>
<ID>MaxLineLength:Exceptions.kt$TransactionEncoderException.TransactionNotFoundException$"$transactionId in the repository. This means the wallet created a transaction and then returned a row ID "</ID>
<ID>MaxLineLength:Exceptions.kt$TransactionEncoderException.TransactionNotFoundException$"that does not actually exist. This is a scenario where the wallet should have thrown an exception but failed "</ID>
<ID>MaxLineLength:GetAddressFragment.kt$GetAddressFragment$val taddress = DerivationTool.deriveTransparentAddress(seed, ZcashNetwork.fromResources(requireApplicationContext()))</ID>
<ID>MaxLineLength:GetAddressFragment.kt$GetAddressFragment$val zaddress = DerivationTool.deriveShieldedAddress(seed, ZcashNetwork.fromResources(requireApplicationContext()))</ID>
<ID>MaxLineLength:GetAddressFragment.kt$GetAddressFragment$viewingKey = runBlocking { DerivationTool.deriveUnifiedViewingKeys(seed, ZcashNetwork.fromResources(requireApplicationContext())).first() }</ID>
<ID>MaxLineLength:GetBalanceFragment.kt$GetBalanceFragment$val viewingKey = runBlocking { DerivationTool.deriveUnifiedViewingKeys(seed, ZcashNetwork.fromResources(requireApplicationContext())).first() }</ID>
<ID>MaxLineLength:GetBlockFragment.kt$GetBlockFragment$val newHeight = min(binding.textBlockHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)</ID>
<ID>MaxLineLength:GetBlockRangeFragment.kt$GetBlockRangeFragment$val end = max(binding.textEndHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)</ID>
<ID>MaxLineLength:GetBlockRangeFragment.kt$GetBlockRangeFragment$val start = max(binding.textStartHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/4dcc95dd0a2f1f51bd64bb9f729b423c6de1690664a1b6614c75925e781662f7.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/71935e29127a7de0b96081f4c8a42a9c11584d83adedfaab414362a6f3d965cf.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/75f2cdd2ff6a94535326abb5d9e663d53cbfa5f31ebb24b4d7e420e9440d41a2.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/7690c8ec740c1be3c50e2aedae8bf907ac81141ae8b6a134c1811706c73f49a6.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/8f064d23c66dc36e32445e5f3b50e0f32ac3ddb78cff21fb521eb6c19c07c99a.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/d2e7be14bbb308f9d4d68de424d622cbf774226d01cd63cc6f155fafd5cd212c.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e6566be3a4f9a80035dab8e1d97e40832a639e3ea938fb7972ea2f8482ff51ce.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e9527891b5d43d1ac72f2c0a3ac18a33dc5a0529aec04fa600616ed35f8123f8.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/34e507cab780546f980176f3ff2695cd404917508c7e5ee18cc1d2ff3858cb08.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/4eaa902279f8380914baf5bcc470d8b7c11d84fda809f67f517a7cb48912b87b.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/6edf869063eccff3345676b0fed9f1aa6988fb2524e3d9ca7420a13cfadcd76c.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/72a29d7db511025da969418880b749f7fc0fc910cdb06f52193b5fa5c0401d9d.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/73c5edf8ffba774d99155121ccf07e67fbcf14284458f7e732751fea60d3bcbc.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/c9e35e6ff444b071d63bf9bab6480409d6361760445c8a28d24179adb35c2495.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/de97394ae220c28a33ba78b944e82dabec8cb404a4407650b134b3d5950358c0.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/ff6ea36765dc29793775c7aa71de19fca039c5b5b873a0497866e9c4bc48af01.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/t-shielded-spend.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$private const val tx663174 = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$private const val tx663188 = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt"</ID>
<ID>MaxLineLength:InboundTxTests.kt$InboundTxTests.Companion$private const val txIndexReorg = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-index-reorg/t1.txt"</ID>
<ID>MaxLineLength:InitializerTest.kt$InitializerTest$// "zxviews1qv886f6hqqqqpqy2ajg9sm22vs4gm4hhajthctfkfws34u45pjtut3qmz0eatpqzvllgsvlk3x0y35ktx5fnzqqzueyph20k3328kx46y3u5xs4750cwuwjuuccfp7la6rh8yt2vjz6tylsrwzy3khtjjzw7etkae6gw3vq608k7quka4nxkeqdxxsr9xxdagv2rhhwugs6w0cquu2ykgzgaln2vyv6ah3ram2h6lrpxuznyczt2xl3lyxcwlk4wfz5rh7wzfd7642c2ae5d7"</ID>
<ID>MaxLineLength:InitializerTest.kt$InitializerTest$// "zxviews1qvn6j50dqqqqpqxqkvqgx2sp63jccr4k5t8zefadpzsu0yy73vczfznwc794xz6lvy3yp5ucv43lww48zz95ey5vhrsq83dqh0ky9junq0cww2wjp9c3cd45n5l5x8l2g9atnx27e9jgyy8zasjy26gugjtefphan9al3tx208m8ekev5kkx3ug6pd0qk4gq4j4wfuxajn388pfpq54wklwktqkyjz9e6gam0n09xjc35ncd3yah5aa9ezj55lk4u7v7hn0v86vz7ygq4qj2v",</ID>
<ID>MaxLineLength:InitializerTest.kt$InitializerTest$// assertEquals("Height should equal sapling activation height when defaultToOldestHeight is true", ZcashSdk.SAPLING_ACTIVATION_HEIGHT, initializer.birthday.height)</ID>
<ID>MaxLineLength:InitializerTest.kt$InitializerTest$// assertEquals("Incorrect height used for import.", ZcashSdk.SAPLING_ACTIVATION_HEIGHT, initializer.birthday.height)</ID>
<ID>MaxLineLength:InitializerTest.kt$InitializerTest$// assertNotEquals("Height should not equal sapling activation height when defaultToOldestHeight is false", ZcashSdk.SAPLING_ACTIVATION_HEIGHT, h)</ID>
<ID>MaxLineLength:LightWalletService.kt$LightWalletService$fun</ID>
<ID>MaxLineLength:ListUtxosFragment.kt$ListUtxosFragment$binding.inputRangeStart.setText(ZcashNetwork.fromResources(requireApplicationContext()).saplingActivationHeight.toString())</ID>
<ID>MaxLineLength:MaintainedTest.kt$TestPurpose.DARKSIDE$* These tests require a running instance of [darksidewalletd](https://github.com/zcash/lightwalletd/blob/master/docs/darksidewalletd.md).</ID>
<ID>MaxLineLength:MultiAccountIntegrationTest.kt$// private val secondKey = "zxviews1q0w208wwqqqqpqyxp978kt2qgq5gcyx4er907zhczxpepnnhqn0a47ztefjnk65w2573v7g5fd3hhskrg7srpxazfvrj4n2gm4tphvr74a9xnenpaxy645dmuqkevkjtkf5jld2f7saqs3xyunwquhksjpqwl4zx8zj73m8gk2d5d30pck67v5hua8u3chwtxyetmzjya8jdjtyn2aum7au0agftfh5q9m4g596tev9k365s84jq8n3laa5f4palt330dq0yede053sdyfv6l"</ID>
<ID>MaxLineLength:MultiAccountTest.kt$// private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"</ID>
<ID>MaxLineLength:MultiAccountTest.kt$// private val secondKey = "zxviews1q0w208wwqqqqpqyxp978kt2qgq5gcyx4er907zhczxpepnnhqn0a47ztefjnk65w2573v7g5fd3hhskrg7srpxazfvrj4n2gm4tphvr74a9xnenpaxy645dmuqkevkjtkf5jld2f7saqs3xyunwquhksjpqwl4zx8zj73m8gk2d5d30pck67v5hua8u3chwtxyetmzjya8jdjtyn2aum7au0agftfh5q9m4g596tev9k365s84jq8n3laa5f4palt330dq0yede053sdyfv6l"</ID>
<ID>MaxLineLength:MultiAccountTest.kt$// twig("&lt;DONE importing viewing key>&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;")</ID>
<ID>MaxLineLength:MultiAccountTest.kt$// twig("&lt;importing viewing key>&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;")</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "average talent frozen work brand output major soldier witness keen brown bind indicate burden furnace long crime joke inhale chronic ordinary renew boat flame",</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "echo viable panic unaware stay magnet cake museum yellow abandon mountain height lunch advance tongue market bamboo cushion okay morning minute icon obtain december",</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "profit save black expose rude feature early rocket alter borrow finish october few duty flush kick spell bean burden enforce bitter theme silent uphold",</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "renew enlist travel stand trust execute decade surge follow push student school focus woman ripple movie that bitter plug same index wife spread differ"</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "secret-extended-key-main1qdtp7dwfqqqqpqq3zxegnzc6qtacjp4m6qhyz7typdw9h9smra3rn322dkhyfg8kk26p0fcjuklryw0ed6falf6c7dwqehleca0xf6m6tlnv5zdjx7lqs4xmseqjz0fvk273aczatxxjaqmy3kv8wtzcc6pf6qtrjy5g2mqgs3cj9f8zrhu0ukzf9gn2arr02kzdct0jh5ee3zjch3xscjv34pzkgpueuq0pyl706alssuchqu4jmjm22fcq3htlwxt3f3hdytne7mgacmaq6",</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "secret-extended-key-main1qv85jn3hqqqqpq9jam3g232ylvvhy8e5vdhp0x9zjppr49sw6awwrm3a3d8l9j9estq9a548lguf0n9fsjs7c96uaymhysuzeek5eg8un0fk8umxszxstm0xfq77x68yjk4t4j7h2xqqjf8nmkx0va3cphnhxpvd0l5dhzgyxryeleayay6m09zpt0dem8hkazlw5jk6gedrakp9z7wzq2ptf6aqkft6z02mtrnq4a5pguwp4m8xkh52wz0r3naeycnqllnvsn8ag5qru36vk",</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "secret-extended-key-main1qvfmgpzjqqqqpqqnpl2s9n774mrv72zsuw73km9x6ax2s26d0d0ua20nuxvkexa4lzc4n8a3zfvyn2qns37fx00avdtjewghmxz5nc2ey738nrpu4pqqnwysmcls5yek94lf03d5jtsa25nmuln4xjvu6e4g0yrr6xesp9cr6uyj6whwarpcca9exzr7wzltelq5tusn3x3jchjyk6cj09xyctjzykp902w4x23zdsf46d3fn9rtkgm0rmek296c5nhuzf99a2x6umqvf4man",</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "secret-extended-key-main1qwhel8pxqqqqpqxjl3cqu2z8hu0tqdd5qchkrdtsjuce9egdqlpu7eff2rn3gknm0mdwr9358t3dlcf47vakdwewxy64k7ds7y3k455rfch7s2x8mfesjsxptyfvc9heme3zj08wwdk4l9mwce92lvrl797wmmddt65ygwcqlvvpqs7s2x0cnhemhnwzhx5ccakfgxfym0w8dxglq4h6pwukf2az6lcm38346qc5s9rgx6s988fr0kxnqg0c6g6zlxa2wpc7jh0gz7qx7zl33"</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "secret-extended-key-main1qws7ryw7qqqqpqq77dmhl9tufzdsgy8hcjq8kxjtgkfwwgqn4a26ahmhmjqueptd2pt49qhm63lt8v93tlqzw7psmkvqqfm6xdnc2qwkflfcenqs7s4sj2yn0c75n982wjrf5k5h37vt3wxwr3pqnjk426lltctrms2uqmqgkl4706c6dmfxg2cmsdlzlt9ykpvacaterq4alljr3efke7k46xcrg4pxc02ezj0txwqjjve23nqqp7t5n5qat4d8569krxgkcd852uqxj5ljt",</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "unit ice dial annual duty feature smoke expose hard joy globe just accuse inner fog cash neutral forum strategy crash subject hurdle lecture sand",</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "zxviews1qdtp7dwfqqqqpqq3zxegnzc6qtacjp4m6qhyz7typdw9h9smra3rn322dkhyfg8kktk66k7zaj9tt5j6e58enx89pwry4rxwmcuzqyxlsap965r5gxpt604chmjyuhder6xwu3tx0h608as5sgxapqdqa6v6hy6qzh9fft0ns3cj9f8zrhu0ukzf9gn2arr02kzdct0jh5ee3zjch3xscjv34pzkgpueuq0pyl706alssuchqu4jmjm22fcq3htlwxt3f3hdytne7mgscrz5m",</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "zxviews1qv85jn3hqqqqpq9jam3g232ylvvhy8e5vdhp0x9zjppr49sw6awwrm3a3d8l9j9es2ed9h29r6ta5tzt53j2y0ex84lzns0thp7n9wzutjapq29chfewqz34q5g6545f8jf0e69jcg9eyv66s8pt3y5dwxg9nrezz8q9j9fwxryeleayay6m09zpt0dem8hkazlw5jk6gedrakp9z7wzq2ptf6aqkft6z02mtrnq4a5pguwp4m8xkh52wz0r3naeycnqllnvsn8ag5q73pqgd",</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "zxviews1qvfmgpzjqqqqpqqnpl2s9n774mrv72zsuw73km9x6ax2s26d0d0ua20nuxvkexa4lq5fsc6psl8csspyqrlwfeuele5crlwpyjufgkzyy6ffw8hc52hn04jzru6mntms8c2cm255gu200zx4pmz06k3s90jatwehazl465tf6uyj6whwarpcca9exzr7wzltelq5tusn3x3jchjyk6cj09xyctjzykp902w4x23zdsf46d3fn9rtkgm0rmek296c5nhuzf99a2x6umqr804k9",</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "zxviews1qwhel8pxqqqqpqxjl3cqu2z8hu0tqdd5qchkrdtsjuce9egdqlpu7eff2rn3gknm0msw7ug6qp4ynppscvv6hfm2nkf42lhz8la5et3zsej84xafcn0xdd9ms452hfjp4tljshtffscsl68wgdv3j5nnelxsdcle5rnwkuz6lvvpqs7s2x0cnhemhnwzhx5ccakfgxfym0w8dxglq4h6pwukf2az6lcm38346qc5s9rgx6s988fr0kxnqg0c6g6zlxa2wpc7jh0gz7q4ysx0l"</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// "zxviews1qws7ryw7qqqqpqq77dmhl9tufzdsgy8hcjq8kxjtgkfwwgqn4a26ahmhmjqueptd2pmq3f73pm8uaa25aze5032qw4dppkx4l625xcjcm94d5e65fcq4j2uptnjuqpyu2rvud88dtjwseglgzfe5l4te2xw62yq4tv62d2f6kl4706c6dmfxg2cmsdlzlt9ykpvacaterq4alljr3efke7k46xcrg4pxc02ezj0txwqjjve23nqqp7t5n5qat4d8569krxgkcd852uqg2t2vn",</ID>
<ID>MaxLineLength:MultiRecipientIntegrationTest.kt$// val pong: Service.PingResponse = rustPoc.sendMoney(Service.PingResponse.newBuilder().setEntry(10).setEntry(11).build())</ID>
<ID>MaxLineLength:OutboundTransactionsTest.kt$// private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"</ID>
<ID>MaxLineLength:PagedTransactionRepository.kt$PagedTransactionRepository$// TODO: begin converting these into Data Access API. For now, just collect the desired operations and iterate/refactor, later</ID>
<ID>MaxLineLength:PagedTransactionRepository.kt$PagedTransactionRepository.Companion$// TODO: document why we do this. My guess is to catch database issues early or to trigger migrations--I forget why it was added but there was a good reason?</ID>
<ID>MaxLineLength:PendingTransactionDb.kt$PendingTransactionDao$@Query("UPDATE pending_transactions SET raw = :raw, rawTransactionId = :rawTransactionId, expiryHeight = :expiryHeight WHERE id = :id")</ID>
<ID>MaxLineLength:PersistentTransactionManager.kt$PersistentTransactionManager$private suspend</ID>
<ID>MaxLineLength:PersistentTransactionManager.kt$PersistentTransactionManager$tx.isCancelled() -> twig("Warning: ignoring cancelled transaction with id ${tx.id}. We will not submit it to the network because it has been cancelled.")</ID>
<ID>MaxLineLength:PersistentTransactionManager.kt$PersistentTransactionManager$tx.isFailedEncoding() -> twig("Warning: this transaction will not be submitted because it failed to be encoded.")</ID>
<ID>MaxLineLength:ReorgBasicTest.kt$// private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"</ID>
<ID>MaxLineLength:ReorgBasicTest.kt$// private const val reorgUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-small-reorg.txt"</ID>
<ID>MaxLineLength:ReorgLargeTest.kt$// // don't start until after we enter the darkside (otherwise the we find no blocks to begin with and sleep for an interval)</ID>
<ID>MaxLineLength:ReorgLargeTest.kt$// assertTrue("Error: Cannot complete test because the server is disconnected.", it != Synchronizer.Status.DISCONNECTED)</ID>
<ID>MaxLineLength:ReorgLargeTest.kt$// private const val seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"</ID>
<ID>MaxLineLength:ReorgLargeTest.kt$// validating blocks in range 663172..663202 in db: /data/user/0/cash.z.ecc.android.sdk.test/databases/ReorgTest22_Cache.db</ID>
<ID>MaxLineLength:ReorgLargeTest.kt$// validating blocks in range 663192..663202 in db: /data/user/0/cash.z.ecc.android.sdk.test/databases/ReorgTest22_Cache.db</ID>
<ID>MaxLineLength:ReorgLargeTest.kt$// validating blocks in range 663202..663202 in db: /data/user/0/cash.z.ecc.android.sdk.test/databases/ReorgTest22_Cache.db</ID>
<ID>MaxLineLength:RustBackend.kt$RustBackend$// // serialize the list, send it over to rust and get back a serialized set of results that we parse out and return</ID>
<ID>MaxLineLength:RustBackend.kt$RustBackend$// override fun parseTransactionDataList(tdl: LocalRpcTypes.TransactionDataList): LocalRpcTypes.TransparentTransactionList {</ID>
<ID>MaxLineLength:RustBackend.kt$RustBackend$throw NotImplementedError("TODO: implement this at the zcash_client_sqlite level. But for now, use DerivationTool, instead to derive addresses from seeds")</ID>
<ID>MaxLineLength:RustBackendWelding.kt$RustBackendWelding$suspend fun clearUtxos(tAddress: String, aboveHeightInclusive: BlockHeight = BlockHeight(network.saplingActivationHeight.value)): Boolean</ID>
<ID>MaxLineLength:SanityTest.kt$SanityTest$"${wallet.endpoint} ${wallet.networkName} Lightwalletd is too far behind. Downloader height $downloaderHeight is more than 10 blocks behind block explorer height $expectedHeight"</ID>
<ID>MaxLineLength:SanityTest.kt$SanityTest$assertTrue("$networkName failed to return a proper block. Height was ${block.height} but we expected $height", block.height == height.value)</ID>
<ID>MaxLineLength:SanityTest.kt$SanityTest.Companion$"zxviews1q0hxkupsqqqqpqzsffgrk2smjuccedua7zswf5e3rgtv3ga9nhvhjug670egshd6me53r5n083s2m9mf4va4z7t39ltd3wr7hawnjcw09eu85q0ammsg0tsgx24p4ma0uvr4p8ltx5laum2slh2whc23ctwlnxme9w4dw92kalwk5u4wyem8dynknvvqvs68ktvm8qh7nx9zg22xfc77acv8hk3qqll9k3x4v2fa26puu2939ea7hy4hh60ywma69xtqhcy4037ne8g2sg8sq"</ID>
<ID>MaxLineLength:SanityTest.kt$SanityTest.Companion$"zxviewtestsapling1qv0ue89kqqqqpqqyt4cl5wvssx4wqq30e5m948p07dnwl9x3u75vvnzvjwwpjkrf8yk2gva0kkxk9p8suj4xawlzw9pajuxgap83wykvsuyzfrm33a2p2m4jz2205kgzx0l2lj2kyegtnuph6crkyvyjqmfxut84nu00wxgrstu5fy3eu49nzl8jzr4chmql4ysgg2t8htn9dtvxy8c7wx9rvcerqsjqm6lqln9syk3g8rr3xpy3l4nj0kawenzpcdtnv9qmy98vdhqzaf063"</ID>
<ID>MaxLineLength:SdkSynchronizer.kt$DefaultSynchronizerFactory$// TODO [#242]: Don't hard code page size. It is a workaround for Uncaught Exception: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. and is probably related to FlowPagedList</ID>
<ID>MaxLineLength:SdkSynchronizer.kt$SdkSynchronizer$// TODO: turn this section into the data access API. For now, just aggregate all the things that we want to do with the underlying data</ID>
<ID>MaxLineLength:SdkSynchronizer.kt$SdkSynchronizer$twig("[cleanup] FOUND EXPIRED pendingTX (lastScanHeight: $lastScannedHeight expiryHeight: ${it.expiryHeight}): and ${it.id} ${if (result > 0) "successfully removed" else "failed to remove"} it")</ID>
<ID>MaxLineLength:SendFragment.kt$SendFragment$pendingTransaction.isFailedEncoding() -> "ERROR: failed to encode transaction! (id: $id)".also { isSending = false }</ID>
<ID>MaxLineLength:SendFragment.kt$SendFragment$pendingTransaction.isFailedSubmit() -> "ERROR: failed to submit transaction! (id: $id)".also { isSending = false }</ID>
<ID>MaxLineLength:SendFragment.kt$SendFragment$spendingKey = runBlocking { DerivationTool.deriveSpendingKeys(seed, ZcashNetwork.fromResources(requireApplicationContext())).first() }</ID>
<ID>MaxLineLength:SetupTest.kt$SetupTest$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"</ID>
<ID>MaxLineLength:SetupTest.kt$SetupTest.Companion$private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"</ID>
<ID>MaxLineLength:ShieldFundsSample.kt$ShieldFundsSample$val SEED_PHRASE = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" // \"//\"deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"//"deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"</ID>
<ID>MaxLineLength:SmokeTest.kt$SmokeTest$Assert.assertEquals("Invalid CacheDB file", "/data/user/0/cash.z.ecc.android.sdk.test/databases/TestWallet_testnet_Cache.db", wallet.initializer.rustBackend.pathCacheDb)</ID>
<ID>MaxLineLength:SmokeTest.kt$SmokeTest$Assert.assertEquals("Invalid CacheDB params dir", "/data/user/0/cash.z.ecc.android.sdk.test/cache/params", wallet.initializer.rustBackend.pathParamsDir)</ID>
<ID>MaxLineLength:SmokeTest.kt$SmokeTest$Assert.assertEquals("Invalid DataDB file", "/data/user/0/cash.z.ecc.android.sdk.test/databases/TestWallet_testnet_Data.db", wallet.initializer.rustBackend.pathDataDb)</ID>
<ID>MaxLineLength:SmokeTest.kt$SmokeTest$Assert.assertEquals("Invalid extfvk", "zxviewtestsapling1qv0ue89kqqqqpqqyt4cl5wvssx4wqq30e5m948p07dnwl9x3u75vvnzvjwwpjkrf8yk2gva0kkxk9p8suj4xawlzw9pajuxgap83wykvsuyzfrm33a2p2m4jz2205kgzx0l2lj2kyegtnuph6crkyvyjqmfxut84nu00wxgrstu5fy3eu49nzl8jzr4chmql4ysgg2t8htn9dtvxy8c7wx9rvcerqsjqm6lqln9syk3g8rr3xpy3l4nj0kawenzpcdtnv9qmy98vdhqzaf063", wallet.initializer.viewingKeys[0].extfvk)</ID>
<ID>MaxLineLength:SmokeTest.kt$SmokeTest$Assert.assertEquals("Invalid extpub", "0234965f30c8611253d035f44e68d4e2ce82150e8665c95f41ccbaf916b16c69d8", wallet.initializer.viewingKeys[0].extpub)</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/4dcc95dd0a2f1f51bd64bb9f729b423c6de1690664a1b6614c75925e781662f7.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/71935e29127a7de0b96081f4c8a42a9c11584d83adedfaab414362a6f3d965cf.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/75f2cdd2ff6a94535326abb5d9e663d53cbfa5f31ebb24b4d7e420e9440d41a2.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/7690c8ec740c1be3c50e2aedae8bf907ac81141ae8b6a134c1811706c73f49a6.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/8f064d23c66dc36e32445e5f3b50e0f32ac3ddb78cff21fb521eb6c19c07c99a.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/d2e7be14bbb308f9d4d68de424d622cbf774226d01cd63cc6f155fafd5cd212c.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e6566be3a4f9a80035dab8e1d97e40832a639e3ea938fb7972ea2f8482ff51ce.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e9527891b5d43d1ac72f2c0a3ac18a33dc5a0529aec04fa600616ed35f8123f8.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/34e507cab780546f980176f3ff2695cd404917508c7e5ee18cc1d2ff3858cb08.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/4eaa902279f8380914baf5bcc470d8b7c11d84fda809f67f517a7cb48912b87b.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/6edf869063eccff3345676b0fed9f1aa6988fb2524e3d9ca7420a13cfadcd76c.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/72a29d7db511025da969418880b749f7fc0fc910cdb06f52193b5fa5c0401d9d.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/73c5edf8ffba774d99155121ccf07e67fbcf14284458f7e732751fea60d3bcbc.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/c9e35e6ff444b071d63bf9bab6480409d6361760445c8a28d24179adb35c2495.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/de97394ae220c28a33ba78b944e82dabec8cb404a4407650b134b3d5950358c0.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/ff6ea36765dc29793775c7aa71de19fca039c5b5b873a0497866e9c4bc48af01.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/t-shielded-spend.txt"</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet$suspend</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups.ALICE$"quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely life cliff spirit admit weekend message recipe trumpet impact kitten"</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups.BOB$"canvas wine sugar acquire garment spy tongue odor hole cage year habit bullet make label human unit option top calm neutral try vocal arena"</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups.DEFAULT$"column rhythm acoustic gym cost fit keen maze fence seed mail medal shrimp tell relief clip cannon foster soldier shallow refuse lunar parrot banana"</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups.DEV_WALLET$"still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups.SAMPLE_WALLET$"input frown warm senior anxiety abuse yard prefer churn reject people glimpse govern glory crumble swallow verb laptop switch trophy inform friend permit purpose"</ID>
<ID>MaxLineLength:TestnetIntegrationTest.kt$TestnetIntegrationTest.Companion$private const val seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"</ID>
<ID>MaxLineLength:TestnetIntegrationTest.kt$TestnetIntegrationTest.Companion$runBlocking { config.importWallet(seed, BlockHeight.new(ZcashNetwork.Testnet, birthdayHeight), ZcashNetwork.Testnet, lightWalletEndpoint) }</ID>
<ID>MaxLineLength:TransactionViewHolder.kt$TransactionViewHolder$icon.setColorFilter(ContextCompat.getColor(itemView.context, if (isInbound) R.color.tx_inbound else R.color.tx_outbound))</ID>
<ID>MaxLineLength:Transactions.kt$if (latestHeight == null || latestHeight.value &lt; saplingActivationHeight.value || expiryHeight &lt; saplingActivationHeight.value) return false</ID>
<ID>MaxLineLength:TransparentRestoreSample.kt$TransparentRestoreSample$// Assert.assertTrue("Not enough funds to run sample. Expected at least $TX_VALUE Zatoshi but found $value. Try adding funds to $address", value >= TX_VALUE)</ID>
<ID>MaxLineLength:TransparentRestoreSample.kt$TransparentRestoreSample$// twig("FOUND utxo balance of total: ${walletBalance.totalZatoshi} available: ${walletBalance.availableZatoshi}")</ID>
<ID>MaxLineLength:TransparentRestoreSample.kt$TransparentRestoreSample$// walletA.send(TX_VALUE, walletA.transparentAddress, "${TransparentRestoreSample::class.java.simpleName} z->t")</ID>
<ID>MaxLineLength:TransparentRestoreSample.kt$TransparentRestoreSample$Assert.assertTrue("Not enough funds to run sample. Expected some Zatoshi but found ${tbalance.available}. Try adding funds to $address", tbalance.available.value > 0)</ID>
<ID>MaxLineLength:TransparentTest.kt$TransparentTest$assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPublicKey(uvk.extpub, network = network))</ID>
<ID>MaxLineLength:TransparentTest.kt$TransparentTest.Companion$const val PHRASE = "deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"</ID>
<ID>MaxLineLength:TransparentTest.kt$TransparentTest.Companion.ExpectedTestnet$override val zAddr = "ztestsapling1wn3tw9w5rs55x5yl586gtk72e8hcfdq8zsnjzcu8p7ghm8lrx54axc74mvm335q7lmy3g0sqje6"</ID>
<ID>MaxLineLength:Twig.kt$inline</ID>
<ID>MaxLineLength:WalletService.kt$var duration = Math.pow(initialDelayMillis.toDouble(), (sequence.toDouble() / 4.0)).toLong() + Random.nextLong(1000L)</ID>
<ID>MaxLineLength:WalletService.kt$var sequence = 0 // count up to the max and then reset to half. So that we don't repeat the max but we also don't repeat too much.</ID>
<ID>MaxLineLength:ZcashSdk.kt$ZcashSdk$*</ID>
<ID>MaxLineLength:build.gradle.kts$// Tricky: fix: By default, the kotlin_module name will not include the version (in classes.jar/META-INF). Instead it has a colon, which breaks compilation on Windows. This is one way to set it explicitly to the proper value. See https://github.com/zcash/zcash-android-wallet-sdk/issues/222 for more info.</ID>
<ID>MaxLineLength:build.gradle.kts$// solves error: Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules jetified-guava-26.0-android.jar (com.google.guava:guava:26.0-android) and listenablefuture-1.0.jar (com.google.guava:listenablefuture:1.0)</ID>
<ID>MaxLineLength:build.gradle.kts$// used by 'ru.gildor.corutines.okhttp.await' (to make simple suspended requests) and breaks on versions higher than 3.8.0</ID>
<ID>MayBeConst:DemoConstants.kt$DemoConstants$// corresponds to address: zs15tzaulx5weua5c7l47l4pku2pw9fzwvvnsp4y80jdpul0y3nwn5zp7tmkcclqaca3mdjqjkl7hx val initialSeedWords: String = "wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame"</ID>
<ID>MayBeConst:DemoConstants.kt$DemoConstants$// corresponds to address: ztestsapling1zhqvuq8zdwa8nsnde7074kcfsat0w25n08jzuvz5skzcs6h9raxu898l48xwr8fmkny3zqqrgd9 val initialSeedWords: String = "wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame"</ID>
<ID>MayBeConst:DemoConstants.kt$DemoConstants$// corresponds to seed: urban kind wise collect social marble riot primary craft lucky head cause syrup odor artist decorate rhythm phone style benefit portion bus truck top val toAddress: String = "zs1lcdmue7rewgvzh3jd09sfvwq3sumu6hkhpk53q94kcneuffjkdg9e3tyxrugkmpza5c3c5e6eqh"</ID>
<ID>MayBeConst:DemoConstants.kt$DemoConstants$// corresponds to seed: urban kind wise collect social marble riot primary craft lucky head cause syrup odor artist decorate rhythm phone style benefit portion bus truck top val toAddress: String = "ztestsapling1ddttvrm6ueug4vwlczs8daqjaul60aur4udnvcz9qdnjt9ekt2tsxheqvv3mn50wvhmzj4ge9rl"</ID>
<ID>MayBeConst:DemoConstants.kt$DemoConstants$val sendAmount: Double = 0.000018</ID>
<ID>MayBeConst:DemoConstants.kt$DemoConstants$val sendAmount: Double = 0.00017</ID>
<ID>MayBeConst:DemoConstants.kt$DemoConstants$val utxoEndHeight: Int = 1075590</ID>
<ID>MayBeConst:TestnetIntegrationTest.kt$TestnetIntegrationTest.Companion$val address = "zs1m30y59wxut4zk9w24d6ujrdnfnl42hpy0ugvhgyhr8s0guszutqhdj05c7j472dndjstulph74m"</ID>
<ID>MayBeConst:TestnetIntegrationTest.kt$TestnetIntegrationTest.Companion$val toAddress = "zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0"</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * Default amount of time, in milliseconds, to poll for new blocks. Typically, this should be about half the average * block time. */ val POLL_INTERVAL = 20_000L</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * Default attempts at retrying. */ val RETRIES = 5</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * Default number of blocks to rewind when a chain reorg is detected. This should be large enough to recover from the * reorg but smaller than the theoretical max reorg size of 100. */ val REWIND_DISTANCE = 10</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * Default size of batches of blocks to scan via librustzcash. The smaller this number the more granular information * can be provided about scan state. Unfortunately, it may also lead to a lot of overhead during scanning. */ val SCAN_BATCH_SIZE = 150</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * Estimate of the time between blocks. */ val BLOCK_INTERVAL_MILLIS = 75_000L</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * File name for the sapling output params */ val OUTPUT_PARAM_FILE_NAME = "sapling-output.params"</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * File name for the sappling spend params */ val SPEND_PARAM_FILE_NAME = "sapling-spend.params"</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * The Url that is used by default in zcashd. * We'll want to make this externally configurable, rather than baking it into the SDK but * this will do for now, since we're using a cloudfront URL that already redirects. */ val CLOUD_PARAM_DIR_URL = "https://z.cash/downloads/"</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * The amount of blocks ahead of the current height where new transactions are set to expire. This value is controlled * by the rust backend but it is helpful to know what it is set to and should be kept in sync. */ val EXPIRY_OFFSET = 20</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * The default maximum amount of time to wait during retry backoff intervals. Failed loops will never wait longer than * this before retyring. */ val MAX_BACKOFF_INTERVAL = 600_000L</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * The default memo to use when shielding transparent funds. */ val DEFAULT_SHIELD_FUNDS_MEMO_PREFIX = "shielding:"</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * The maximum length of a memo. */ val MAX_MEMO_SIZE = 512</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * The theoretical maximum number of blocks in a reorg, due to other bottlenecks in the protocol design. */ val MAX_REORG_SIZE = 100</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$val DB_CACHE_NAME = "Cache.db"</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$val DB_DATA_NAME = "Data.db"</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$val DEFAULT_ALIAS: String = "ZcashSdk"</ID>
<ID>PrintStackTrace:CompactBlockProcessor.kt$CompactBlockProcessor$t</ID>
<ID>PrintStackTrace:PersistentTransactionManager.kt$PersistentTransactionManager$t</ID>
<ID>ReturnCount:CurrencyFormatter.kt$inline fun String?.safelyConvertToBigDecimal(): BigDecimal?</ID>
<ID>ReturnCount:MainActivity.kt$MainActivity$fun getClipboardText(): String?</ID>
<ID>SpreadOperator:Initializer.kt$Initializer.Config$( *DerivationTool.deriveUnifiedViewingKeys( seed, network, numberOfAccounts ) )</ID>
<ID>SpreadOperator:PagedTransactionRepository.kt$PagedTransactionRepository.Companion$(*viewingKeys.toTypedArray())</ID>
<ID>SpreadOperator:RustBackend.kt$RustBackend$(*this)</ID>
<ID>SwallowedException:CompactBlockProcessor.kt$CompactBlockProcessor$e: Throwable</ID>
<ID>SwallowedException:CompactBlockProcessor.kt$CompactBlockProcessor$t: Throwable</ID>
<ID>SwallowedException:CurrencyFormatter.kt$t: Throwable</ID>
<ID>SwallowedException:Ext.kt$t: Throwable</ID>
<ID>SwallowedException:SdkSynchronizer.kt$SdkSynchronizer$t: Throwable</ID>
<ID>SwallowedException:SharedViewModel.kt$SharedViewModel$t: Throwable</ID>
<ID>TooGenericExceptionCaught:CheckpointTool.kt$CheckpointTool$t: Throwable</ID>
<ID>TooGenericExceptionCaught:CompactBlockDownloader.kt$CompactBlockDownloader$t: Throwable</ID>
<ID>TooGenericExceptionCaught:CompactBlockProcessor.kt$CompactBlockProcessor$e: Throwable</ID>
<ID>TooGenericExceptionCaught:CompactBlockProcessor.kt$CompactBlockProcessor$t: Throwable</ID>
<ID>TooGenericExceptionCaught:CurrencyFormatter.kt$t: Throwable</ID>
<ID>TooGenericExceptionCaught:DerivedDataDb.kt$TransactionDao$t: Throwable</ID>
<ID>TooGenericExceptionCaught:Ext.kt$t: Throwable</ID>
<ID>TooGenericExceptionCaught:GetBlockFragment.kt$GetBlockFragment$t: Throwable</ID>
<ID>TooGenericExceptionCaught:GetBlockRangeFragment.kt$GetBlockRangeFragment$t: Throwable</ID>
<ID>TooGenericExceptionCaught:ListUtxosFragment.kt$ListUtxosFragment$t: Throwable</ID>
<ID>TooGenericExceptionCaught:NativeLibraryLoader.kt$NativeLibraryLoader$e: Throwable</ID>
<ID>TooGenericExceptionCaught:PersistentTransactionManager.kt$PersistentTransactionManager$t: Throwable</ID>
<ID>TooGenericExceptionCaught:SaplingParamTool.kt$SaplingParamTool.Companion$e: Throwable</ID>
<ID>TooGenericExceptionCaught:SdkSynchronizer.kt$SdkSynchronizer$t: Throwable</ID>
<ID>TooGenericExceptionCaught:SdkSynchronizer.kt$SdkSynchronizer$tError: Throwable</ID>
<ID>TooGenericExceptionCaught:SdkSynchronizer.kt$SdkSynchronizer$zError: Throwable</ID>
<ID>TooGenericExceptionCaught:SharedViewModel.kt$SharedViewModel$t: Throwable</ID>
<ID>TooGenericExceptionCaught:WalletService.kt$t: Throwable</ID>
<ID>TooGenericExceptionCaught:WalletTransactionEncoder.kt$WalletTransactionEncoder$t: Throwable</ID>
<ID>TooGenericExceptionThrown:DarksideApi.kt$DarksideApi.EmptyResponse$throw RuntimeException("Server responded with an error: $error caused by ${error?.cause}")</ID>
<ID>TooManyFunctions:CompactBlockProcessor.kt$CompactBlockProcessor</ID>
<ID>TooManyFunctions:CurrencyFormatter.kt$cash.z.ecc.android.sdk.ext.CurrencyFormatter.kt</ID>
<ID>TooManyFunctions:DerivationTool.kt$DerivationTool$Companion : Derivation</ID>
<ID>TooManyFunctions:DerivedDataDb.kt$TransactionDao</ID>
<ID>TooManyFunctions:HomeFragment.kt$HomeFragment : BaseDemoFragment</ID>
<ID>TooManyFunctions:Initializer.kt$Initializer$Companion : Erasable</ID>
<ID>TooManyFunctions:Initializer.kt$Initializer$Config</ID>
<ID>TooManyFunctions:ListTransactionsFragment.kt$ListTransactionsFragment : BaseDemoFragment</ID>
<ID>TooManyFunctions:ListUtxosFragment.kt$ListUtxosFragment : BaseDemoFragment</ID>
<ID>TooManyFunctions:MainActivity.kt$MainActivity : AppCompatActivityOnPrimaryClipChangedListenerDrawerListener</ID>
<ID>TooManyFunctions:PagedTransactionRepository.kt$PagedTransactionRepository : TransactionRepository</ID>
<ID>TooManyFunctions:PendingTransactionDb.kt$PendingTransactionDao</ID>
<ID>TooManyFunctions:PersistentTransactionManager.kt$PersistentTransactionManager : OutboundTransactionManager</ID>
<ID>TooManyFunctions:RustBackend.kt$RustBackend : RustBackendWelding</ID>
<ID>TooManyFunctions:RustBackend.kt$RustBackend$Companion</ID>
<ID>TooManyFunctions:RustBackendWelding.kt$RustBackendWelding</ID>
<ID>TooManyFunctions:SdkSynchronizer.kt$SdkSynchronizer : Synchronizer</ID>
<ID>TooManyFunctions:SendFragment.kt$SendFragment : BaseDemoFragment</ID>
<ID>TooManyFunctions:Synchronizer.kt$Synchronizer</ID>
<ID>TooManyFunctions:TransactionManager.kt$OutboundTransactionManager</ID>
<ID>TooManyFunctions:TransactionRepository.kt$TransactionRepository</ID>
<ID>TooManyFunctions:Transactions.kt$cash.z.ecc.android.sdk.db.entity.Transactions.kt</ID>
<ID>UnusedPrivateMember:BalancePrinterUtil.kt$BalancePrinterUtil$private fun downloadNewBlocks(range: IntRange)</ID>
<ID>UnusedPrivateMember:BalancePrinterUtil.kt$BalancePrinterUtil$private suspend fun deleteDb(dbName: String)</ID>
<ID>UnusedPrivateMember:CompactBlockProcessor.kt$CompactBlockProcessor$fastIntervalDesired: Boolean = false</ID>
<ID>UnusedPrivateMember:DarksideTest.kt$DarksideTest$name: String = javaClass.simpleName</ID>
<ID>UnusedPrivateMember:DarksideTestCoordinator.kt$DarksideTestCoordinator.Companion$// Block URLS private const val beforeReorg = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"</ID>
<ID>UnusedPrivateMember:DarksideTestCoordinator.kt$DarksideTestCoordinator.Companion$private const val largeReorg = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-large-reorg.txt"</ID>
<ID>UnusedPrivateMember:DarksideTestCoordinator.kt$DarksideTestCoordinator.Companion$private const val smallReorg = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-small-reorg.txt"</ID>
<ID>UnusedPrivateMember:DataDbScannerUtil.kt$DataDbScannerUtil$private fun cacheBlocks()</ID>
<ID>UnusedPrivateMember:DataDbScannerUtil.kt$DataDbScannerUtil$private fun deleteDb(dbName: String)</ID>
<ID>UnusedPrivateMember:DataDbScannerUtil.kt$DataDbScannerUtil$private val alias = "ScannerUtil"</ID>
<ID>UnusedPrivateMember:DataDbScannerUtil.kt$DataDbScannerUtil$private val host = "lightd-main.zecwallet.co"</ID>
<ID>UnusedPrivateMember:DataDbScannerUtil.kt$DataDbScannerUtil$private val port = 443</ID>
<ID>UnusedPrivateMember:DerivationTool.kt$DerivationTool.Companion$networkId: Int = ZcashNetwork.Mainnet.id</ID>
<ID>UnusedPrivateMember:DerivationTool.kt$DerivationTool.Companion$viewingKey: UnifiedViewingKey</ID>
<ID>UnusedPrivateMember:GetBlockFragment.kt$GetBlockFragment$_unused: View? = null</ID>
<ID>UnusedPrivateMember:GetBlockRangeFragment.kt$GetBlockRangeFragment$_unused: View</ID>
<ID>UnusedPrivateMember:HomeFragment.kt$HomeFragment$private val homeViewModel: HomeViewModel by viewModels()</ID>
<ID>UnusedPrivateMember:HomeFragment.kt$HomeFragment$unused: View</ID>
<ID>UnusedPrivateMember:InboundTxTests.kt$InboundTxTests$val overwriteBlockCount = 5</ID>
<ID>UnusedPrivateMember:InboundTxTests.kt$InboundTxTests.Companion$private const val lastBlockHash = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a"</ID>
<ID>UnusedPrivateMember:InboundTxTests.kt$InboundTxTests.Companion$private const val txIndexReorg = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-index-reorg/t1.txt"</ID>
<ID>UnusedPrivateMember:InboundTxTests.kt$InboundTxTests.Companion$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" )</ID>
<ID>UnusedPrivateMember:InboundTxTests.kt$InboundTxTests.Companion$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" )</ID>
<ID>UnusedPrivateMember:Initializer.kt$Initializer.Companion$onCriticalErrorHandler: ((Throwable?) -> Boolean)?</ID>
<ID>UnusedPrivateMember:Initializer.kt$Initializer.Companion$private fun onCriticalError(onCriticalErrorHandler: ((Throwable?) -> Boolean)?, error: Throwable)</ID>
<ID>UnusedPrivateMember:MainActivity.kt$MainActivity$view: View</ID>
<ID>UnusedPrivateMember:PersistentTransactionManager.kt$PersistentTransactionManager$priority: Int = 0</ID>
<ID>UnusedPrivateMember:SdkSynchronizer.kt$SdkSynchronizer$private fun onFailedSend(error: Throwable): Boolean</ID>
<ID>UnusedPrivateMember:SdkSynchronizer.kt$SdkSynchronizer$unused: CoroutineContext?</ID>
<ID>UnusedPrivateMember:SendFragment.kt$SendFragment$unused: View</ID>
<ID>UnusedPrivateMember:SetupTest.kt$SetupTest.Companion$private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"</ID>
<ID>UnusedPrivateMember:SetupTest.kt$SetupTest.Companion$private const val firstBlock = 663150</ID>
<ID>UnusedPrivateMember:SetupTest.kt$SetupTest.Companion$private const val lastBlock = 663200</ID>
<ID>UnusedPrivateMember:SetupTest.kt$SetupTest.Companion$private const val lastBlockHash = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a"</ID>
<ID>UnusedPrivateMember:TestnetIntegrationTest.kt$TestnetIntegrationTest.Companion$private const val seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"</ID>
<ID>UnusedPrivateMember:TestnetIntegrationTest.kt$TestnetIntegrationTest.Companion$private const val targetHeight = 663250</ID>
<ID>UnusedPrivateMember:TransactionCounterUtil.kt$TransactionCounterUtil$private val network = ZcashNetwork.Mainnet</ID>
<ID>UseCheckOrError:FlowPagedListBuilder.kt$FlowPagedListBuilder$throw IllegalStateException("Unable to create executor based on dispatcher: $this")</ID>
<ID>UseCheckOrError:Placeholders.kt$SampleSpendingKeyProvider$throw IllegalStateException("This sample provider only supports the dummy seed")</ID>
<ID>UtilityClassWithPublicConstructor:DerivationTool.kt$DerivationTool</ID>
<ID>UtilityClassWithPublicConstructor:Placeholders.kt$SeedGenerator</ID>
<ID>UtilityClassWithPublicConstructor:SaplingParamTool.kt$SaplingParamTool</ID>
<ID>VariableNaming:ShieldFundsSample.kt$ShieldFundsSample$val SEED_PHRASE = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" // \"//\"deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"//"deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"</ID>
<ID>VariableNaming:TransparentRestoreSample.kt$TransparentRestoreSample$val TX_VALUE = Zatoshi(ZcashSdk.MINERS_FEE.value / 2)</ID>
</CurrentIssues>
</SmellBaseline>