parent
597cc43886
commit
3b826f8f6a
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,20 +48,15 @@ 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)
|
||||
synchronizer = Synchronizer.newBlocking(
|
||||
requireApplicationContext(),
|
||||
network,
|
||||
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
|
||||
seed = seed,
|
||||
birthday = network.saplingActivationHeight
|
||||
)
|
||||
}
|
||||
}.let { initializer ->
|
||||
synchronizer = Synchronizer.newBlocking(initializer)
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayAddress() {
|
||||
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
|
||||
|
|
|
@ -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,28 +43,15 @@ 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)
|
||||
synchronizer = Synchronizer.newBlocking(
|
||||
requireApplicationContext(),
|
||||
network,
|
||||
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
|
||||
seed = seed,
|
||||
birthday = null
|
||||
)
|
||||
}
|
||||
}.let { initializer ->
|
||||
synchronizer = Synchronizer.newBlocking(initializer, seed)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
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() {
|
||||
|
|
|
@ -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,21 +63,16 @@ 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)
|
||||
synchronizer = Synchronizer.newBlocking(
|
||||
requireApplicationContext(),
|
||||
network,
|
||||
alias = "Demo_Utxos",
|
||||
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
|
||||
seed = seed,
|
||||
birthday = null
|
||||
)
|
||||
}
|
||||
it.alias = "Demo_Utxos"
|
||||
}
|
||||
}
|
||||
synchronizer = runBlocking { Synchronizer.new(initializer, seed) }
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
|
@ -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)
|
||||
synchronizer = Synchronizer.newBlocking(
|
||||
requireApplicationContext(),
|
||||
network,
|
||||
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
|
||||
seed = seed,
|
||||
birthday = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}.let { initializer ->
|
||||
synchronizer = Synchronizer.newBlocking(initializer, seed)
|
||||
}
|
||||
spendingKey = runBlocking {
|
||||
DerivationTool.deriveSpendingKeys(seed, ZcashNetwork.fromResources(requireApplicationContext())).first()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 " +
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>()
|
||||
|
|
|
@ -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(
|
||||
synchronizer = Synchronizer.newBlocking(
|
||||
context,
|
||||
ZcashNetwork.Mainnet,
|
||||
lightWalletEndpoint = LightWalletEndpoint
|
||||
.defaultForNetwork(ZcashNetwork.Mainnet),
|
||||
seed = byteArrayOf(),
|
||||
birthday = BlockHeight.new(
|
||||
ZcashNetwork.Mainnet,
|
||||
birthdayHeight
|
||||
),
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val synchronizer = runBlocking {
|
||||
Synchronizer.new(
|
||||
initializer
|
||||
)
|
||||
}
|
||||
|
||||
synchronizer
|
||||
}
|
||||
|
||||
println("sync!")
|
||||
synchronizer.start()
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue