Simplify Synchronizer instantiation

fix

synchronizer
This commit is contained in:
Carter Jernigan 2022-10-06 13:48:21 -04:00 committed by Carter Jernigan
parent 597cc43886
commit 3b826f8f6a
19 changed files with 310 additions and 684 deletions

View File

@ -3,7 +3,6 @@ package cash.z.ecc.android.sdk.darkside.test
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.db.entity.isPending
@ -67,12 +66,16 @@ class TestWallet(
runBlocking { DerivationTool.deriveSpendingKeys(seed, network = network)[0] }
private val transparentAccountPrivateKey =
runBlocking { DerivationTool.deriveTransparentAccountPrivateKey(seed, network = network) }
val initializer = runBlocking {
Initializer.new(context) { config ->
runBlocking { config.importWallet(seed, startHeight, network, endpoint, alias = alias) }
}
}
val synchronizer: SdkSynchronizer = runBlocking { Synchronizer.new(initializer) } as SdkSynchronizer
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
context,
network,
alias,
endpoint,
seed,
startHeight
)
as
SdkSynchronizer
val service = (synchronizer.processor.downloader.lightWalletService as LightWalletGrpcService)
val available get() = synchronizer.saplingBalances.value?.available

View File

@ -1,9 +1,9 @@
package cash.z.wallet.sdk.sample.demoapp
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.db.entity.isFailure
import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.ext.convertZecToZatoshi
import cash.z.ecc.android.sdk.ext.toHex
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
@ -14,6 +14,7 @@ import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.Mainnet
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.model.defaultForNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.runBlocking
@ -160,8 +161,14 @@ class SampleCodeTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val synchronizer: Synchronizer = run {
val initializer = runBlocking { Initializer.new(context) {} }
Synchronizer.newBlocking(initializer)
val network = ZcashNetwork.fromResources(context)
Synchronizer.newBlocking(
context,
network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
seed = seed,
birthday = null
)
}
@BeforeClass

View File

@ -5,7 +5,6 @@ import android.view.LayoutInflater
import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetAddressBinding
@ -49,19 +48,14 @@ class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
).first()
}
// using the ViewingKey to initialize
runBlocking {
Initializer.new(requireApplicationContext(), null) {
val network = ZcashNetwork.fromResources(requireApplicationContext())
it.newWallet(
viewingKey,
network = network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
)
}
}.let { initializer ->
synchronizer = Synchronizer.newBlocking(initializer)
}
val network = ZcashNetwork.fromResources(requireApplicationContext())
synchronizer = Synchronizer.newBlocking(
requireApplicationContext(),
network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
seed = seed,
birthday = network.saplingActivationHeight
)
}
private fun displayAddress() {

View File

@ -5,7 +5,6 @@ import android.view.LayoutInflater
import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
@ -18,9 +17,7 @@ import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.model.defaultForNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.runBlocking
/**
* Displays the available balance && total balance associated with the seed defined by the default config.
@ -46,27 +43,14 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
// have the seed stored
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
// converting seed into viewingKey
val viewingKey = runBlocking {
DerivationTool.deriveUnifiedFullViewingKeys(
seed,
ZcashNetwork.fromResources(requireApplicationContext())
).first()
}
// using the ViewingKey to initialize
runBlocking {
Initializer.new(requireApplicationContext(), null) {
val network = ZcashNetwork.fromResources(requireApplicationContext())
it.newWallet(
viewingKey,
network = network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
)
}
}.let { initializer ->
synchronizer = Synchronizer.newBlocking(initializer, seed)
}
val network = ZcashNetwork.fromResources(requireApplicationContext())
synchronizer = Synchronizer.newBlocking(
requireApplicationContext(),
network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
seed = seed,
birthday = null
)
}
override fun onResume() {

View File

@ -8,7 +8,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
@ -33,7 +32,6 @@ import kotlinx.coroutines.runBlocking
*/
@Suppress("TooManyFunctions")
class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBinding>() {
private lateinit var initializer: Initializer
private lateinit var synchronizer: Synchronizer
private lateinit var adapter: TransactionAdapter
private lateinit var address: String
@ -51,28 +49,20 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
// Use a BIP-39 library to convert a seed phrase into a byte array. Most wallets already
// have the seed stored
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
initializer = Initializer.newBlocking(
requireApplicationContext(),
Initializer.Config {
val network = ZcashNetwork.fromResources(requireApplicationContext())
runBlocking {
it.importWallet(
seed,
birthday = null,
network = network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
)
}
}
)
val network = ZcashNetwork.fromResources(requireApplicationContext())
address = runBlocking {
DerivationTool.deriveUnifiedAddress(
seed,
ZcashNetwork.fromResources(requireApplicationContext())
)
}
synchronizer = Synchronizer.newBlocking(initializer, seed)
synchronizer = Synchronizer.newBlocking(
requireApplicationContext(),
network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
seed = seed,
birthday = null
)
}
private fun initTransactionUI() {

View File

@ -8,7 +8,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
@ -28,7 +27,6 @@ import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlin.math.max
@ -47,7 +45,6 @@ import kotlin.math.max
@Suppress("TooManyFunctions")
class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
private lateinit var seed: ByteArray
private lateinit var initializer: Initializer
private lateinit var synchronizer: Synchronizer
private lateinit var adapter: UtxoAdapter
private val address: String = "t1RwbKka1CnktvAJ1cSqdn7c6PXWG4tZqgd"
@ -66,20 +63,15 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
// Use a BIP-39 library to convert a seed phrase into a byte array. Most wallets already
// have the seed stored
seed = Mnemonics.MnemonicCode(sharedViewModel.seedPhrase.value).toSeed()
initializer = runBlocking {
val network = ZcashNetwork.fromResources(requireApplicationContext())
Initializer.new(requireApplicationContext()) {
runBlocking {
it.newWallet(
seed,
network = network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
)
}
it.alias = "Demo_Utxos"
}
}
synchronizer = runBlocking { Synchronizer.new(initializer, seed) }
val network = ZcashNetwork.fromResources(requireApplicationContext())
synchronizer = Synchronizer.newBlocking(
requireApplicationContext(),
network,
alias = "Demo_Utxos",
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
seed = seed,
birthday = null
)
}
override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -8,7 +8,6 @@ import android.widget.TextView
import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
@ -68,20 +67,14 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
// have the seed stored
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
runBlocking {
Initializer.new(requireApplicationContext()) {
val network = ZcashNetwork.fromResources(requireApplicationContext())
runBlocking {
it.newWallet(
seed,
network = network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
)
}
}
}.let { initializer ->
synchronizer = Synchronizer.newBlocking(initializer, seed)
}
val network = ZcashNetwork.fromResources(requireApplicationContext())
synchronizer = Synchronizer.newBlocking(
requireApplicationContext(),
network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
seed = seed,
birthday = null
)
spendingKey = runBlocking {
DerivationTool.deriveSpendingKeys(seed, ZcashNetwork.fromResources(requireApplicationContext())).first()
}

View File

@ -1,19 +1,11 @@
package cash.z.ecc.android.sdk.ext
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.util.SimpleMnemonics
import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
import ru.gildor.coroutines.okhttp.await
import kotlin.test.assertNotNull
fun Initializer.Config.seedPhrase(seedPhrase: String, network: ZcashNetwork) {
runBlocking { setSeed(SimpleMnemonics().toSeed(seedPhrase.toCharArray()), network) }
}
object BlockExplorer {
suspend fun fetchLatestHeight(): Long {
val client = OkHttpClient()

View File

@ -1,5 +1,7 @@
package cash.z.ecc.android.sdk.integration
import androidx.test.core.app.ApplicationProvider
import cash.z.ecc.android.sdk.DefaultSynchronizerFactory
import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
@ -13,7 +15,6 @@ import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import kotlin.test.DefaultAsserter.assertEquals
import kotlin.test.DefaultAsserter.assertTrue
// TODO [#650]: https://github.com/zcash/zcash-android-wallet-sdk/issues/650
@ -36,44 +37,35 @@ class SanityTest(
@Test
fun testFilePaths() {
val rustBackend = runBlocking {
DefaultSynchronizerFactory.defaultRustBackend(
ApplicationProvider.getApplicationContext(),
ZcashNetwork.Testnet,
"TestWallet",
TestWallet.Backups.SAMPLE_WALLET.testnetBirthday
)
}
assertTrue(
"$name has invalid DataDB file",
wallet.initializer.rustBackend.dataDbFile.absolutePath.endsWith(
rustBackend.dataDbFile.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_${networkName}_${DatabaseCoordinator.DB_DATA_NAME}"
)
)
assertTrue(
"$name has invalid CacheDB file",
wallet.initializer.rustBackend.cacheDbFile.absolutePath.endsWith(
rustBackend.cacheDbFile.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_${networkName}_${DatabaseCoordinator.DB_CACHE_NAME}"
)
)
assertTrue(
"$name has invalid CacheDB params dir",
wallet.initializer.rustBackend.pathParamsDir.endsWith(
rustBackend.pathParamsDir.endsWith(
"cache/params"
)
)
}
@Test
fun testBirthday() {
assertEquals(
"$name has invalid birthday height",
birthday,
wallet.initializer.checkpoint.height
)
}
@Test
fun testViewingKeys() {
assertEquals(
"$name has invalid encoding",
encoding,
wallet.initializer.viewingKeys[0].encoding
)
}
@Test
@Ignore(
"This test needs to be refactored to a separate test module. It causes SSLHandshakeException: Chain " +

View File

@ -1,13 +1,15 @@
package cash.z.ecc.android.sdk.integration
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.LargeTest
import androidx.test.filters.MediumTest
import cash.z.ecc.android.sdk.DefaultSynchronizerFactory
import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.util.TestWallet
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Ignore
import org.junit.Test
@ -22,38 +24,32 @@ class SmokeTest {
@Test
fun testFilePaths() {
val rustBackend = runBlocking {
DefaultSynchronizerFactory.defaultRustBackend(
ApplicationProvider.getApplicationContext(),
ZcashNetwork.Testnet,
"TestWallet",
TestWallet.Backups.SAMPLE_WALLET.testnetBirthday
)
}
assertTrue(
"Invalid DataDB file",
wallet.initializer.rustBackend.dataDbFile.absolutePath.endsWith(
rustBackend.dataDbFile.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_DATA_NAME}"
)
)
assertTrue(
"Invalid CacheDB file",
wallet.initializer.rustBackend.cacheDbFile.absolutePath.endsWith(
rustBackend.cacheDbFile.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_CACHE_NAME}"
)
)
assertTrue(
"Invalid CacheDB params dir",
wallet.initializer.rustBackend.pathParamsDir.endsWith("cache/params")
rustBackend.pathParamsDir.endsWith("cache/params")
)
}
@Test
fun testBirthday() {
assertEquals(
"Invalid birthday height",
1_330_000,
wallet.initializer.checkpoint.height.value
)
}
@Test
fun testViewingKeys() {
assertEquals("Invalid encoding", "uviewtest1m3cyp6tdy3rewtpqazdxlsqkmu7xjedtqmp4da8mvxm87h4as38v5kz4ulw7x7nmgv5d8uwk743a5zt7aurtz2z2g74fu740ecp5fhdgakm6hgzr5jzcl75cmddlufmjpykrpkzj84yz8j5qe9c5935qt2tvd9dpx3m0zw5dwn3t2dtsdyqvy5jstf88w799qre549yyxw7dvk3murm3568ah6wqg5tdjka2ujtgct4q62hw7mfcxcyaeu8l6882hxkt9x4025mx3w35whcrmpxy8fqsh62esatczj8awxtrgnj8h2vj65r8595qt9jl4gz84w4mja74tymt8xxaguckeam", wallet.initializer.viewingKeys[0].encoding)
}
// This test takes an extremely long time
// Does its runtime grow over time based on growth of the blockchain?
@Test

View File

@ -2,7 +2,6 @@ package cash.z.ecc.android.sdk.integration
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.Synchronizer.Status.SYNCED
import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess
@ -63,7 +62,7 @@ class TestnetIntegrationTest : ScopedTest() {
@Test
@Ignore("This test is broken")
fun getAddress() = runBlocking {
assertEquals(address, synchronizer.getAddress())
assertEquals(address, synchronizer.getCurrentAddress())
}
// This is an extremely slow test; it is disabled so that we can get CI set up
@ -117,29 +116,33 @@ class TestnetIntegrationTest : ScopedTest() {
}
companion object {
init { Twig.plant(TroubleshootingTwig()) }
init {
Twig.plant(TroubleshootingTwig())
}
val lightWalletEndpoint = LightWalletEndpoint("lightwalletd.testnet.z.cash", 9087, true)
private const val birthdayHeight = 963150L
private const val targetHeight = 663250
private const val seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"
private const val 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"
val seed = "cash.z.ecc.android.sdk.integration.IntegrationTest.seed.value.64bytes".toByteArray()
val address = "zs1m30y59wxut4zk9w24d6ujrdnfnl42hpy0ugvhgyhr8s0guszutqhdj05c7j472dndjstulph74m"
val toAddress = "zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0"
private val context = InstrumentationRegistry.getInstrumentation().context
private val initializer = runBlocking {
Initializer.new(context) { config ->
config.setNetwork(ZcashNetwork.Testnet, lightWalletEndpoint)
runBlocking { config.importWallet(seed, BlockHeight.new(ZcashNetwork.Testnet, birthdayHeight), ZcashNetwork.Testnet, lightWalletEndpoint) }
}
}
private lateinit var synchronizer: Synchronizer
@JvmStatic
@BeforeClass
fun startUp() {
synchronizer = Synchronizer.newBlocking(initializer)
synchronizer = Synchronizer.newBlocking(
context,
ZcashNetwork.Testnet,
lightWalletEndpoint =
lightWalletEndpoint,
seed = seed,
birthday = BlockHeight.new(ZcashNetwork.Testnet, birthdayHeight)
)
synchronizer.start(classScope)
}
}

View File

@ -1,7 +1,6 @@
package cash.z.ecc.android.sdk.util
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
import cash.z.ecc.android.sdk.internal.Twig
@ -14,11 +13,9 @@ import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.model.defaultForNetwork
import cash.z.ecc.android.sdk.tool.CheckpointTool
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import okio.buffer
import okio.source
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
@ -83,11 +80,7 @@ class BalancePrinterUtil {
mnemonics.toSeed(seedPhrase.toCharArray())
}.collect { seed ->
// TODO: clear the dataDb but leave the cacheDb
val initializer = Initializer.new(context) { config ->
val endpoint = LightWalletEndpoint.defaultForNetwork(network)
runBlocking { config.importWallet(seed, birthdayHeight, network, endpoint) }
config.alias = alias
}
/*
what I need to do right now
- for each seed
@ -103,7 +96,14 @@ class BalancePrinterUtil {
- can we be more stateless and thereby improve the flexibility of this code?!!!
*/
synchronizer?.stop()
synchronizer = Synchronizer.new(initializer).apply {
synchronizer = Synchronizer.new(
context,
network,
lightWalletEndpoint = LightWalletEndpoint
.defaultForNetwork(network),
seed = seed,
birthday = birthdayHeight
).apply {
start()
}
@ -142,16 +142,7 @@ class BalancePrinterUtil {
// }
@Throws(IOException::class)
fun readLines() = flow<String> {
val seedFile = javaClass.getResourceAsStream("/utils/seeds.txt")!!
seedFile.source().buffer().use { source ->
var line: String? = source.readUtf8Line()
while (line != null) {
emit(line)
line = source.readUtf8Line()
}
}
}
fun readLines() = javaClass.getResourceAsStream("/utils/seeds.txt")!!.bufferedReader().lineSequence().asFlow()
// private fun initWallet(seed: String): Wallet {
// val spendingKeyProvider = Delegates.notNull<String>()

View File

@ -1,13 +1,14 @@
package cash.z.ecc.android.sdk.util
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.model.BlockHeight
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 kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
@ -65,27 +66,17 @@ class DataDbScannerUtil {
@Test
@Ignore("This test is broken")
fun scanExistingDb() {
synchronizer = run {
val initializer = runBlocking {
Initializer.new(context) {
it.setBirthdayHeight(
BlockHeight.new(
ZcashNetwork.Mainnet,
birthdayHeight
),
false
)
}
}
val synchronizer = runBlocking {
Synchronizer.new(
initializer
)
}
synchronizer
}
synchronizer = Synchronizer.newBlocking(
context,
ZcashNetwork.Mainnet,
lightWalletEndpoint = LightWalletEndpoint
.defaultForNetwork(ZcashNetwork.Mainnet),
seed = byteArrayOf(),
birthday = BlockHeight.new(
ZcashNetwork.Mainnet,
birthdayHeight
)
)
println("sync!")
synchronizer.start()

View File

@ -3,7 +3,6 @@ package cash.z.ecc.android.sdk.util
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.db.entity.isPending
@ -67,12 +66,14 @@ class TestWallet(
runBlocking { DerivationTool.deriveSpendingKeys(seed, network = network)[0] }
private val transparentAccountPrivateKey =
runBlocking { DerivationTool.deriveTransparentAccountPrivateKey(seed, network = network) }
val initializer = runBlocking {
Initializer.new(context) { config ->
runBlocking { config.importWallet(seed, startHeight, network, endpoint, alias = alias) }
}
}
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(initializer) as SdkSynchronizer
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
context,
network,
alias,
lightWalletEndpoint = endpoint,
seed = seed,
startHeight
) as SdkSynchronizer
val service = (synchronizer.processor.downloader.lightWalletService as LightWalletGrpcService)
val available get() = synchronizer.saplingBalances.value?.available
@ -109,7 +110,12 @@ class TestWallet(
return this
}
suspend fun send(address: String = transparentAddress, memo: String = "", amount: Zatoshi = Zatoshi(500L), fromAccountIndex: Int = 0): TestWallet {
suspend fun send(
address: String = transparentAddress,
memo: String = "",
amount: Zatoshi = Zatoshi(500L),
fromAccountIndex: Int = 0
): TestWallet {
Twig.sprout("$alias sending")
synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo, fromAccountIndex)
.takeWhile { it.isPending(null) }

View File

@ -1,434 +0,0 @@
package cash.z.ecc.android.sdk
import android.content.Context
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.exception.InitializerException
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.internal.ext.getCacheDirSuspend
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.CheckpointTool
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.UnifiedFullViewingKey
import kotlinx.coroutines.runBlocking
import java.io.File
/**
* Simplified Initializer focused on starting from a ViewingKey.
*/
@Suppress("LongParameterList", "unused")
class Initializer private constructor(
val context: Context,
internal val rustBackend: RustBackend,
val network: ZcashNetwork,
val alias: String,
val lightWalletEndpoint: LightWalletEndpoint,
val viewingKeys: List<UnifiedFullViewingKey>,
val overwriteVks: Boolean,
internal val checkpoint: Checkpoint
) {
suspend fun erase() = erase(context, network, alias)
@Suppress("TooManyFunctions")
class Config private constructor(
val viewingKeys: MutableList<UnifiedFullViewingKey> = mutableListOf(),
var alias: String = ZcashSdk.DEFAULT_ALIAS
) {
var birthdayHeight: BlockHeight? = null
private set
lateinit var network: ZcashNetwork
private set
lateinit var lightWalletEndpoint: LightWalletEndpoint
private set
/**
* Determines the default behavior for null birthdays. When null, nothing has been specified
* so a null birthdayHeight value is an error. When false, null birthdays will be replaced
* with the most recent checkpoint height available (typically, the latest `*.json` file in
* `assets/co.electriccoin.zcash/checkpoint/`). When true, null birthdays will be replaced with the oldest
* reasonable height where a transaction could exist (typically, sapling activation but
* better approximations could be devised in the future, such as the date when the first
* BIP-39 zcash wallets came online).
*/
var defaultToOldestHeight: Boolean? = null
private set
var overwriteVks: Boolean = false
private set
constructor(block: (Config) -> Unit) : this() {
block(this)
}
//
// Birthday functions
//
/**
* Set the birthday height for this configuration. When the height is not known, the wallet
* can either default to the latest known birthday (in order to sync new wallets faster) or
* the oldest possible birthday (in order to import a wallet with an unknown birthday
* without skipping old transactions).
*
* @param height nullable birthday height to use for this configuration.
* @param defaultToOldestHeight determines how a null birthday height will be
* interpreted. Typically, `false` for new wallets and `true` for restored wallets because
* new wallets want to load quickly but restored wallets want to find all possible
* transactions. Again, this value is only considered when [height] is null.
*
*/
fun setBirthdayHeight(height: BlockHeight?, defaultToOldestHeight: Boolean): Config =
apply {
this.birthdayHeight = height
this.defaultToOldestHeight = defaultToOldestHeight
}
/**
* Load the most recent checkpoint available. This is useful for new wallets.
*/
fun newWalletBirthday(): Config = apply {
birthdayHeight = null
defaultToOldestHeight = false
}
/**
* Load the birthday checkpoint closest to the given wallet birthday. This is useful when
* importing a pre-existing wallet. It is the same as calling
* `birthdayHeight = importedHeight`.
*/
fun importedWalletBirthday(importedHeight: BlockHeight?): Config = apply {
birthdayHeight = importedHeight
defaultToOldestHeight = true
}
//
// Viewing key functions
//
/**
* Add viewing keys to the set of accounts to monitor. Note: Using more than one viewing key
* is not currently well supported. Consider it an alpha-preview feature that might work but
* probably has serious bugs.
*/
fun setViewingKeys(
vararg unifiedFullViewingKeys: UnifiedFullViewingKey,
overwrite: Boolean = false
): Config = apply {
overwriteVks = overwrite
viewingKeys.apply {
clear()
addAll(unifiedFullViewingKeys)
}
}
fun setOverwriteKeys(isOverwrite: Boolean) {
overwriteVks = isOverwrite
}
/**
* Add viewing key to the set of accounts to monitor. Note: Using more than one viewing key
* is not currently well supported. Consider it an alpha-preview feature that might work but
* probably has serious bugs.
*/
fun addViewingKey(unifiedFullViewingKey: UnifiedFullViewingKey): Config = apply {
viewingKeys.add(unifiedFullViewingKey)
}
//
// Convenience functions
//
/**
* Set the server and the network property at the same time to prevent them from getting out
* of sync. Ultimately, this determines which host a synchronizer will use in order to
* connect to lightwalletd.
*
* @param network the Zcash network to use. Either testnet or mainnet.
* @param lightWalletEndpoint the light wallet endpoint to use.
*/
fun setNetwork(
network: ZcashNetwork,
lightWalletEndpoint: LightWalletEndpoint
): Config = apply {
this.network = network
this.lightWalletEndpoint = lightWalletEndpoint
}
/**
* Import a wallet using the first viewing key derived from the given seed.
*/
suspend fun importWallet(
seed: ByteArray,
birthday: BlockHeight?,
network: ZcashNetwork,
lightWalletEndpoint: LightWalletEndpoint,
alias: String = ZcashSdk.DEFAULT_ALIAS
): Config =
importWallet(
DerivationTool.deriveUnifiedFullViewingKeys(seed, network = network)[0],
birthday,
network,
lightWalletEndpoint,
alias
)
/**
* Default function for importing a wallet.
*/
@Suppress("LongParameterList")
fun importWallet(
viewingKey: UnifiedFullViewingKey,
birthday: BlockHeight?,
network: ZcashNetwork,
lightWalletEndpoint: LightWalletEndpoint,
alias: String = ZcashSdk.DEFAULT_ALIAS
): Config = apply {
setViewingKeys(viewingKey)
setNetwork(network, lightWalletEndpoint)
importedWalletBirthday(birthday)
this.alias = alias
}
/**
* Create a new wallet using the first viewing key derived from the given seed.
*/
suspend fun newWallet(
seed: ByteArray,
network: ZcashNetwork,
lightWalletEndpoint: LightWalletEndpoint,
alias: String = ZcashSdk.DEFAULT_ALIAS
): Config = newWallet(
DerivationTool.deriveUnifiedFullViewingKeys(seed, network)[0],
network,
lightWalletEndpoint,
alias
)
/**
* Default function for creating a new wallet.
*/
fun newWallet(
viewingKey: UnifiedFullViewingKey,
network: ZcashNetwork,
lightWalletEndpoint: LightWalletEndpoint,
alias: String = ZcashSdk.DEFAULT_ALIAS
): Config = apply {
setViewingKeys(viewingKey)
setNetwork(network, lightWalletEndpoint)
newWalletBirthday()
this.alias = alias
}
/**
* Convenience method for setting thew viewingKeys from a given seed. This is the same as
* calling `setViewingKeys` with the keys that match this seed.
*/
@Suppress("SpreadOperator")
suspend fun setSeed(
seed: ByteArray,
network: ZcashNetwork,
numberOfAccounts: Int = 1
): Config =
apply {
@Suppress("SpreadOperator")
setViewingKeys(
*DerivationTool.deriveUnifiedFullViewingKeys(
seed,
network,
numberOfAccounts
)
)
}
/**
* Sets the network from a network id, throwing an exception if the id is not recognized.
*
* @param networkId the ID of the network corresponding to the [ZcashNetwork] enum.
* Typically, it is 0 for testnet and 1 for mainnet.
*/
fun setNetworkId(networkId: Int): Config = apply {
network = ZcashNetwork.from(networkId)
}
//
// Validation helpers
//
fun validate(): Config = apply {
validateAlias(alias)
validateViewingKeys()
validateBirthday()
}
private fun validateBirthday() {
// if birthday is missing then we need to know how to interpret it
// so defaultToOldestHeight ought to be set, in that case
if (birthdayHeight == null && defaultToOldestHeight == null) {
throw InitializerException.MissingDefaultBirthdayException
}
// allow either null or a value greater than the activation height
if (
(birthdayHeight?.value ?: network.saplingActivationHeight.value)
< network.saplingActivationHeight.value
) {
throw InitializerException.InvalidBirthdayHeightException(birthdayHeight, network)
}
}
private fun validateViewingKeys() {
require(viewingKeys.isNotEmpty()) {
"Unified Viewing keys are required. Ensure that the unified viewing keys or seed" +
" have been set on this Initializer."
}
viewingKeys.forEach {
DerivationTool.validateUnifiedFullViewingKey(it)
}
}
companion object
}
companion object : SdkSynchronizer.Erasable {
suspend fun new(appContext: Context, config: Config) = new(appContext, null, config)
fun newBlocking(appContext: Context, config: Config) = runBlocking {
new(
appContext,
null,
config
)
}
suspend fun new(
appContext: Context,
onCriticalErrorHandler: ((Throwable?) -> Boolean)? = null,
block: (Config) -> Unit
) = new(appContext, onCriticalErrorHandler, Config(block))
@Suppress("UNUSED_PARAMETER")
suspend fun new(
context: Context,
onCriticalErrorHandler: ((Throwable?) -> Boolean)?,
config: Config
): Initializer {
config.validate()
val loadedCheckpoint = run {
val height = config.birthdayHeight
?: if (config.defaultToOldestHeight == true) {
config.network.saplingActivationHeight
} else {
null
}
CheckpointTool.loadNearest(
context,
config.network,
height
)
}
val rustBackend = initRustBackend(context, config.network, config.alias, loadedCheckpoint.height)
return Initializer(
context.applicationContext,
rustBackend,
config.network,
config.alias,
config.lightWalletEndpoint,
config.viewingKeys,
config.overwriteVks,
loadedCheckpoint
)
}
private fun onCriticalError(onCriticalErrorHandler: ((Throwable?) -> Boolean)?, error: Throwable) {
twig("********")
twig("******** INITIALIZER ERROR: $error")
if (error.cause != null) twig("******** caused by ${error.cause}")
if (error.cause?.cause != null) twig("******** caused by ${error.cause?.cause}")
twig("********")
twig(error)
if (onCriticalErrorHandler == null) {
twig(
"WARNING: a critical error occurred on the Initializer but no callback is " +
"registered to be notified of critical errors! THIS IS PROBABLY A MISTAKE. To " +
"respond to these errors (perhaps to update the UI or alert the user) set " +
"initializer.onCriticalErrorHandler to a non-null value or use the secondary " +
"constructor: Initializer(context, handler) { ... }. Note that the synchronizer " +
"and initializer BOTH have error handlers and since the initializer exists " +
"before the synchronizer, it needs its error handler set separately."
)
}
onCriticalErrorHandler?.invoke(error)
}
private suspend fun initRustBackend(
context: Context,
network: ZcashNetwork,
alias: String,
blockHeight: BlockHeight
): RustBackend {
val coordinator = DatabaseCoordinator.getInstance(context)
return RustBackend.init(
coordinator.cacheDbFile(network, alias).absolutePath,
coordinator.dataDbFile(network, alias).absolutePath,
File(context.getCacheDirSuspend(), "params").absolutePath,
network,
blockHeight
)
}
/**
* Delete the databases associated with this wallet. This removes all compact blocks and
* data derived from those blocks. For most wallets, this should not result in a loss of
* funds because the seed and spending keys are stored separately. This call just removes
* the associated data but not the seed or spending key, themselves, because those are
* managed separately by the wallet.
*
* @param appContext the application context.
* @param network the network associated with the data to be erased.
* @param alias the alias used to create the local data.
*
* @return true when one of the associated files was found. False most likely indicates
* that the wrong alias was provided.
*/
override suspend fun erase(
appContext: Context,
network: ZcashNetwork,
alias: String
): Boolean = DatabaseCoordinator.getInstance(appContext).deleteDatabases(network, alias)
}
}
/**
* Validate that the alias doesn't contain malicious characters by enforcing simple rules which
* permit the alias to be 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.
*
* @param alias the alias to validate.
*
* @throws IllegalArgumentException whenever the alias is not less than 100 characters or
* contains something other than alphanumeric characters. Underscores are allowed but aliases
* must start with a letter.
*/
internal fun validateAlias(alias: String) {
require(
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 " +
"characters and only contain letters, digits or underscores and start with a letter."
}
}

View File

@ -35,9 +35,11 @@ import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.internal.block.CompactBlockDbStore
import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader
import cash.z.ecc.android.sdk.internal.block.CompactBlockStore
import cash.z.ecc.android.sdk.internal.ext.getCacheDirSuspend
import cash.z.ecc.android.sdk.internal.ext.toHexReversed
import cash.z.ecc.android.sdk.internal.ext.tryNull
import cash.z.ecc.android.sdk.internal.isEmpty
import cash.z.ecc.android.sdk.internal.model.Checkpoint
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.transaction.OutboundTransactionManager
@ -48,17 +50,19 @@ import cash.z.ecc.android.sdk.internal.transaction.TransactionRepository
import cash.z.ecc.android.sdk.internal.transaction.WalletTransactionEncoder
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.internal.twigTask
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.AddressType
import cash.z.ecc.android.sdk.type.AddressType.Shielded
import cash.z.ecc.android.sdk.type.AddressType.Transparent
import cash.z.ecc.android.sdk.type.AddressType.Unified
import cash.z.ecc.android.sdk.type.ConsensusMatchType
import cash.z.ecc.android.sdk.type.UnifiedFullViewingKey
import cash.z.wallet.sdk.rpc.Service
import io.grpc.ManagedChannel
import kotlinx.coroutines.CoroutineExceptionHandler
@ -81,6 +85,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import java.io.File
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
@ -803,37 +808,64 @@ object DefaultSynchronizerFactory {
)
}
internal suspend fun defaultRustBackend(
context: Context,
network: ZcashNetwork,
alias: String,
blockHeight: BlockHeight
): RustBackend {
val coordinator = DatabaseCoordinator.getInstance(context)
return RustBackend.init(
coordinator.cacheDbFile(network, alias).absolutePath,
coordinator.dataDbFile(network, alias).absolutePath,
File(context.getCacheDirSuspend(), "params").absolutePath,
network,
blockHeight
)
}
// 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, seed: ByteArray?): TransactionRepository =
@Suppress("LongParameterList")
internal suspend fun defaultTransactionRepository(
context: Context,
rustBackend: RustBackend,
zcashNetwork: ZcashNetwork,
checkpoint: Checkpoint,
viewingKeys: List<UnifiedFullViewingKey>,
seed: ByteArray?
): TransactionRepository =
PagedTransactionRepository.new(
initializer.context,
initializer.network,
context,
zcashNetwork,
DEFAULT_PAGE_SIZE,
initializer.rustBackend,
rustBackend,
seed,
initializer.checkpoint,
initializer.viewingKeys,
initializer.overwriteVks
checkpoint,
viewingKeys,
false
)
fun defaultBlockStore(initializer: Initializer): CompactBlockStore =
internal fun defaultBlockStore(context: Context, rustBackend: RustBackend, zcashNetwork: ZcashNetwork):
CompactBlockStore =
CompactBlockDbStore.new(
initializer.context,
initializer.network,
initializer.rustBackend.cacheDbFile
context,
zcashNetwork,
rustBackend.cacheDbFile
)
fun defaultService(initializer: Initializer): LightWalletService =
LightWalletGrpcService.new(initializer.context, initializer.lightWalletEndpoint)
fun defaultService(context: Context, lightWalletEndpoint: LightWalletEndpoint): LightWalletService =
LightWalletGrpcService.new(context, lightWalletEndpoint)
fun defaultEncoder(
initializer: Initializer,
internal fun defaultEncoder(
rustBackend: RustBackend,
repository: TransactionRepository
): TransactionEncoder = WalletTransactionEncoder(initializer.rustBackend, repository)
): TransactionEncoder = WalletTransactionEncoder(rustBackend, repository)
fun defaultDownloader(
service: LightWalletService,
@ -841,31 +873,33 @@ object DefaultSynchronizerFactory {
): CompactBlockDownloader = CompactBlockDownloader(service, blockStore)
suspend fun defaultTxManager(
initializer: Initializer,
context: Context,
zcashNetwork: ZcashNetwork,
alias: String,
encoder: TransactionEncoder,
service: LightWalletService
): OutboundTransactionManager {
val databaseFile = DatabaseCoordinator.getInstance(initializer.context).pendingTransactionsDbFile(
initializer.network,
initializer.alias
val databaseFile = DatabaseCoordinator.getInstance(context).pendingTransactionsDbFile(
zcashNetwork,
alias
)
return PersistentTransactionManager(
initializer.context,
context,
encoder,
service,
databaseFile
)
}
fun defaultProcessor(
initializer: Initializer,
internal fun defaultProcessor(
rustBackend: RustBackend,
downloader: CompactBlockDownloader,
repository: TransactionRepository
): CompactBlockProcessor = CompactBlockProcessor(
downloader,
repository,
initializer.rustBackend,
initializer.rustBackend.birthdayHeight
rustBackend,
rustBackend.birthdayHeight
)
}

View File

@ -1,13 +1,19 @@
package cash.z.ecc.android.sdk
import android.content.Context
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.CheckpointTool
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.AddressType
import cash.z.ecc.android.sdk.type.ConsensusMatchType
import cash.z.wallet.sdk.rpc.Service
@ -463,29 +469,68 @@ interface Synchronizer {
/**
* Primary method that SDK clients will use to construct a synchronizer.
*
* If customized initialization is required (e.g. for dependency injection or testing), see
* [DefaultSynchronizerFactory].
*
* @param initializer the helper that is leveraged for creating all the components that the
* Synchronizer requires. It contains all information necessary to build a synchronizer and it is
* mainly responsible for initializing the databases associated with this synchronizer and loading
* the rust backend.
* @param seed the wallet's seed phrase. This only needs to be provided if this method returns an
* error indicating that the seed phrase is required for a database migration.
* @param seed the wallet's seed phrase. This is required the first time a new wallet is set up. For
* subsequent calls, seed is only needed if [InitializerException.SeedRequired] is thrown.
* @throws InitializerException.SeedRequired
*/
/*
* If customized initialization is required (e.g. for dependency injection or testing), see
* [DefaultSynchronizerFactory].
*/
@Suppress("LongParameterList")
suspend fun new(
initializer: Initializer,
seed: ByteArray,
context: Context,
zcashNetwork: ZcashNetwork,
alias: String = "zcash",
lightWalletEndpoint: LightWalletEndpoint,
seed: ByteArray?,
birthday: BlockHeight?
): Synchronizer {
val repository = DefaultSynchronizerFactory.defaultTransactionRepository(initializer, seed)
val blockStore = DefaultSynchronizerFactory.defaultBlockStore(initializer)
val service = DefaultSynchronizerFactory.defaultService(initializer)
val encoder = DefaultSynchronizerFactory.defaultEncoder(initializer, repository)
val applicationContext = context.applicationContext
validateAlias(alias)
val loadedCheckpoint = CheckpointTool.loadNearest(
applicationContext,
zcashNetwork,
birthday ?: zcashNetwork.saplingActivationHeight
)
val rustBackend = DefaultSynchronizerFactory.defaultRustBackend(
applicationContext,
zcashNetwork,
alias,
loadedCheckpoint.height
)
val viewingKeys = seed?.let {
DerivationTool.deriveUnifiedFullViewingKeys(
seed,
zcashNetwork,
1
).toList()
} ?: emptyList()
val repository = DefaultSynchronizerFactory.defaultTransactionRepository(
applicationContext,
rustBackend,
zcashNetwork,
loadedCheckpoint,
viewingKeys,
seed
)
val blockStore = DefaultSynchronizerFactory.defaultBlockStore(applicationContext, rustBackend, zcashNetwork)
val service = DefaultSynchronizerFactory.defaultService(applicationContext, lightWalletEndpoint)
val encoder = DefaultSynchronizerFactory.defaultEncoder(rustBackend, repository)
val downloader = DefaultSynchronizerFactory.defaultDownloader(service, blockStore)
val txManager =
DefaultSynchronizerFactory.defaultTxManager(initializer, encoder, service)
DefaultSynchronizerFactory.defaultTxManager(applicationContext, zcashNetwork, alias, encoder, service)
val processor =
DefaultSynchronizerFactory.defaultProcessor(initializer, downloader, repository)
DefaultSynchronizerFactory.defaultProcessor(rustBackend, downloader, repository)
return SdkSynchronizer(
repository,
@ -501,8 +546,57 @@ interface Synchronizer {
* This is a blocking call, so it should not be called from the main thread.
*/
@JvmStatic
fun newBlocking(initializer: Initializer, seed: ByteArray): Synchronizer = runBlocking {
new (initializer, seed)
@Suppress("LongParameterList")
fun newBlocking(
context: Context,
zcashNetwork: ZcashNetwork,
alias: String = "zcash",
lightWalletEndpoint: LightWalletEndpoint,
seed: ByteArray?,
birthday: BlockHeight?
): Synchronizer = runBlocking {
new(context, zcashNetwork, alias, lightWalletEndpoint, seed, birthday)
}
/**
* Delete the databases associated with this wallet. This removes all compact blocks and
* data derived from those blocks. Although most data can be regenerated by setting up a new
* Synchronizer instance with the seed, there are two special cases where data is not retained:
* 1. Outputs created with a `null` OVK
* 2. The UA to which a transaction was sent (recovery from seed will only reveal the receiver, not the full UA)
*
* @param appContext the application context.
* @param network the network associated with the data to be erased.
* @param alias the alias used to create the local data.
*
* @return true when one of the associated files was found. False most likely indicates
* that the wrong alias was provided.
*/
suspend fun erase(
appContext: Context,
network: ZcashNetwork,
alias: String
): Boolean = DatabaseCoordinator.getInstance(appContext).deleteDatabases(network, alias)
}
}
/**
* Validate that the alias doesn't contain malicious characters by enforcing simple rules which
* permit the alias to be 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.
*
* @param alias the alias to validate.
*
* @throws IllegalArgumentException whenever the alias is not less than 100 characters or
* contains something other than alphanumeric characters. Underscores are allowed but aliases
* must start with a letter.
*/
private fun validateAlias(alias: String) {
require(
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 " +
"characters and only contain letters, digits or underscores and start with a letter."
}
}

View File

@ -18,7 +18,6 @@ import cash.z.ecc.android.sdk.db.entity.Sent
import cash.z.ecc.android.sdk.db.entity.TransactionEntity
import cash.z.ecc.android.sdk.db.entity.Utxo
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.type.UnifiedAddressAccount
//
// Database

View File

@ -3,7 +3,6 @@ package cash.z.ecc.android.sdk.internal.transaction
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
import cash.z.ecc.android.sdk.db.entity.EncodedTransaction
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.UnifiedAddressAccount
import kotlinx.coroutines.flow.Flow
/**