Merge initializers.

The new API for initializing with a viewing key will support all existing uses of the other initializer. So they can be safely merged and the changes to support the new one are fairly straight forward.

Also, iterated on the design of the Initializer to simplify construction and make the API easier to use.
This commit is contained in:
Kevin Gorham 2020-09-21 22:43:50 -04:00
parent 306eac73b0
commit 58925b04b3
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
13 changed files with 191 additions and 149 deletions

View File

@ -110,4 +110,8 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBlockRangeBinding =
FragmentGetBlockRangeBinding.inflate(layoutInflater)
override fun onActionButtonClicked() {
super.onActionButtonClicked()
}
}

View File

@ -8,9 +8,9 @@ import androidx.paging.PagedList
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.VkInitializer
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
import cash.z.ecc.android.sdk.demoapp.App
@ -49,9 +49,9 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
App.instance.defaultConfig.let { config ->
initializer = VkInitializer(App.instance) {
import(seed, config.birthdayHeight)
server(config.host, config.port)
initializer = Initializer(App.instance) {
it.import(seed, config.birthdayHeight)
it.server(config.host, config.port)
}
address = DerivationTool.deriveShieldedAddress(seed)
}

View File

@ -46,9 +46,8 @@ import kotlinx.coroutines.withContext
*/
class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
private val config = App.instance.defaultConfig
private val initializer =
Initializer(App.instance, host = config.host, port = config.port, alias = "Demo_Utxos")
private val birthday = WalletBirthdayTool.loadNearest(App.instance, config.birthdayHeight)
private lateinit var seed: ByteArray
private lateinit var initializer: SdkSynchronizer.SdkInitializer
private lateinit var synchronizer: Synchronizer
private lateinit var adapter: UtxoAdapter<ConfirmedTransaction>
private val address: String = "t1RwbKka1CnktvAJ1cSqdn7c6PXWG4tZqgd"
@ -59,6 +58,26 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentListUtxosBinding =
FragmentListUtxosBinding.inflate(layoutInflater)
/**
* Initialize the required values that would normally live outside the demo but are repeated
* here for completeness so that each demo file can serve as a standalone example.
*/
private fun setup() {
// 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 = Initializer(App.instance) {
it.import(seed, config.birthdayHeight)
it.alias = "Demo_Utxos"
}
synchronizer = Synchronizer(initializer)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setup()
}
fun initUi() {
binding.inputAddress.setText(address)
binding.inputRangeStart.setText(ZcashSdk.SAPLING_ACTIVATION_HEIGHT.toString())
@ -85,7 +104,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
val txids = lightwalletService?.getTAddressTransactions(addressToUse, startToUse..endToUse)
var delta = now - allStart
updateStatus("found ${txids?.size} transactions in ${delta}ms.", false)
txids?.map {
it.data.apply {
try {
@ -93,7 +112,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
} catch (t: Throwable) {
twig("failed to decrypt and store transaction due to: $t")
}
}
}
}?.let { txData ->
val parseStart = now
val tList = LocalRpcTypes.TransactionDataList.newBuilder().addAllData(txData).build()
@ -147,18 +166,6 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
var finalCount: Int = 0
fun resetInBackground() {
try {
initializer.new(Mnemonics.MnemonicCode(sharedViewModel.seedPhrase.value).toSeed(), birthday)
} catch (e: Throwable) {
twig("warning to create a new initializer! Trying to open one instead due to: $e")
try {
initializer.open(birthday)
} catch (t: Throwable) {
twig("warning failed to open the initializer. Due to: $t")
}
}
try {
synchronizer = Synchronizer(initializer)
lifecycleScope.launch {
withContext(Dispatchers.IO) {
initialCount = (synchronizer as SdkSynchronizer).getTransactionCount()
@ -179,7 +186,6 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
fun onClear() {
synchronizer.stop()
initializer.clear()
}
private fun initTransactionUi() {

View File

@ -7,14 +7,13 @@ 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.VkInitializer
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.db.entity.*
import cash.z.ecc.android.sdk.demoapp.App
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentSendBinding
import cash.z.ecc.android.sdk.demoapp.util.SampleStorageBridge
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
import cash.z.ecc.android.sdk.ext.*
import cash.z.ecc.android.sdk.tool.DerivationTool
@ -51,9 +50,9 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
App.instance.defaultConfig.let { config ->
VkInitializer(App.instance) {
import(seed, config.birthdayHeight)
server(config.host, config.port)
Initializer(App.instance) {
it.import(seed, config.birthdayHeight)
it.server(config.host, config.port)
}.let { initializer ->
synchronizer = Synchronizer(initializer)
}

View File

@ -6,17 +6,17 @@ import cash.z.ecc.android.sdk.ext.Twig
import org.junit.Assert.assertEquals
import org.junit.Test
class VkInitializerTest {
class InitializerTest {
@Test
fun testInit() {
val height = 1_419_900
val initializer = VkInitializer(context) {
importedWalletBirthday(height)
setViewingKeys("zxviews1qvn6j50dqqqqpqxqkvqgx2sp63jccr4k5t8zefadpzsu0yy73vczfznwc794xz6lvy3yp5ucv43lww48zz95ey5vhrsq83dqh0ky9junq0cww2wjp9c3cd45n5l5x8l2g9atnx27e9jgyy8zasjy26gugjtefphan9al3tx208m8ekev5kkx3ug6pd0qk4gq4j4wfuxajn388pfpq54wklwktqkyjz9e6gam0n09xjc35ncd3yah5aa9ezj55lk4u7v7hn0v86vz7ygq4qj2v",
val initializer = Initializer(context) { config ->
config.importedWalletBirthday(height)
config.setViewingKeys("zxviews1qvn6j50dqqqqpqxqkvqgx2sp63jccr4k5t8zefadpzsu0yy73vczfznwc794xz6lvy3yp5ucv43lww48zz95ey5vhrsq83dqh0ky9junq0cww2wjp9c3cd45n5l5x8l2g9atnx27e9jgyy8zasjy26gugjtefphan9al3tx208m8ekev5kkx3ug6pd0qk4gq4j4wfuxajn388pfpq54wklwktqkyjz9e6gam0n09xjc35ncd3yah5aa9ezj55lk4u7v7hn0v86vz7ygq4qj2v",
"zxviews1qv886f6hqqqqpqy2ajg9sm22vs4gm4hhajthctfkfws34u45pjtut3qmz0eatpqzvllgsvlk3x0y35ktx5fnzqqzueyph20k3328kx46y3u5xs4750cwuwjuuccfp7la6rh8yt2vjz6tylsrwzy3khtjjzw7etkae6gw3vq608k7quka4nxkeqdxxsr9xxdagv2rhhwugs6w0cquu2ykgzgaln2vyv6ah3ram2h6lrpxuznyczt2xl3lyxcwlk4wfz5rh7wzfd7642c2ae5d7")
alias = "VkInitTest2"
config.alias = "VkInitTest2"
}
assertEquals(height, initializer.birthday.height)
}

View File

@ -1,8 +1,6 @@
package cash.z.ecc.android.sdk.ext
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.Initializer.DefaultBirthdayStore.Companion.ImportedWalletBirthdayStore
import cash.z.ecc.android.sdk.Initializer.DefaultBirthdayStore.Companion.NewWalletBirthdayStore
import cash.z.ecc.android.sdk.util.SimpleMnemonics
import kotlinx.coroutines.*
import org.junit.After
@ -11,34 +9,11 @@ import org.junit.Before
import org.junit.BeforeClass
import java.util.concurrent.TimeoutException
fun Initializer.importPhrase(
seedPhrase: String,
birthdayHeight: Int,
alias: String = ZcashSdk.DEFAULT_ALIAS,
clearCacheDb: Boolean = true,
clearDataDb: Boolean = true
) {
SimpleMnemonics().toSeed(seedPhrase.toCharArray()).let { seed ->
ImportedWalletBirthdayStore(context, birthdayHeight, alias).getBirthday().let {
import(seed, it, clearCacheDb, clearDataDb)
}
}
}
fun Initializer.new(alias: String = ZcashSdk.DEFAULT_ALIAS) {
SimpleMnemonics().let { mnemonics ->
mnemonics.nextMnemonic().let { phrase ->
twig("DELETE THIS LOG! ${String(phrase)}")
NewWalletBirthdayStore(context, alias).getBirthday().let {
new(mnemonics.toSeed(phrase), it, 1, true, true)
}
}
}
fun Initializer.Builder.seedPhrase(seedPhrase: String) {
setSeed(SimpleMnemonics().toSeed(seedPhrase.toCharArray()))
}
fun Initializer.deriveSpendingKey(seedPhrase: String) =
deriveSpendingKeys(SimpleMnemonics().toSeed(seedPhrase.toCharArray()))[0]
open class ScopedTest(val defaultTimeout: Long = 2000L) {
protected lateinit var testScope: CoroutineScope

View File

@ -2,7 +2,8 @@ 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.Initializer.WalletBirthday
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
@ -18,7 +19,7 @@ import java.io.IOException
class AddressGeneratorUtil {
private val context = InstrumentationRegistry.getInstrumentation().context
private val initializer = Initializer(context).open(WalletBirthday())
private val mnemonics = SimpleMnemonics()
@Test
@ -36,7 +37,7 @@ class AddressGeneratorUtil {
.map { seedPhrase ->
mnemonics.toSeed(seedPhrase.toCharArray())
}.map { seed ->
initializer.rustBackend.deriveAddress(seed)
DerivationTool.deriveShieldedAddress(seed)
}.collect { address ->
println("xrxrx2\t$address")
assertTrue(address.startsWith("zs1"))
@ -45,7 +46,7 @@ class AddressGeneratorUtil {
@Throws(IOException::class)
fun readLines() = flow<String> {
val seedFile = javaClass.getResourceAsStream("/utils/seeds.txt")
val seedFile = javaClass.getResourceAsStream("/utils/seeds.txt")!!
Okio.buffer(Okio.source(seedFile)).use { source ->
var line: String? = source.readUtf8Line()
while (line != null) {

View File

@ -10,6 +10,7 @@ import cash.z.ecc.android.sdk.ext.TroubleshootingTwig
import cash.z.ecc.android.sdk.ext.Twig
import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
@ -37,27 +38,26 @@ class BalancePrinterUtil {
private val mnemonics = SimpleMnemonics()
private val context = InstrumentationRegistry.getInstrumentation().context
private val alias = "BalanceUtil"
private val caceDbPath = Initializer.cacheDbPath(context, alias)
private val downloader = CompactBlockDownloader(
LightWalletGrpcService(context, host, port),
CompactBlockDbStore(context, caceDbPath)
)
// private val caceDbPath = Initializer.cacheDbPath(context, alias)
//
// private val downloader = CompactBlockDownloader(
// LightWalletGrpcService(context, host, port),
// CompactBlockDbStore(context, caceDbPath)
// )
// private val processor = CompactBlockProcessor(downloader)
// private val rustBackend = RustBackend.init(context, cacheDbName, dataDbName)
private val initializer = Initializer(context, host, port, alias)
private lateinit var birthday: Initializer.WalletBirthday
private lateinit var birthday: WalletBirthdayTool.WalletBirthday
private var synchronizer: Synchronizer? = null
@Before
fun setup() {
Twig.plant(TroubleshootingTwig())
cacheBlocks()
birthday = Initializer.DefaultBirthdayStore(context, birthdayHeight, alias).getBirthday()
birthday = WalletBirthdayTool.loadNearest(context, birthdayHeight)
}
private fun cacheBlocks() = runBlocking {
@ -82,7 +82,12 @@ class BalancePrinterUtil {
twig("checking balance for: $seedPhrase")
mnemonics.toSeed(seedPhrase.toCharArray())
}.collect { seed ->
initializer.import(seed, birthday, clearDataDb = true, clearCacheDb = false)
// TODO: clear the dataDb but leave the cacheDb
val initializer = Initializer(context) { config ->
config.import(seed, birthdayHeight)
config.server(host, port)
config.alias = alias
}
/*
what I need to do right now
- for each seed

View File

@ -6,6 +6,7 @@ import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess
import cash.z.ecc.android.sdk.ext.*
import cash.z.ecc.android.sdk.tool.DerivationTool
import io.grpc.StatusRuntimeException
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
@ -27,10 +28,10 @@ class DarksideTestCoordinator(val host: String = "127.0.0.1", val testName: Stri
// dependencies: public
val validator = DarksideTestValidator()
val chainMaker = DarksideChainMaker()
var initializer = Initializer(context, host, port, testName)
// var initializer = Initializer(context, Initializer.Builder(host, port, testName))
lateinit var synchronizer: SdkSynchronizer
val spendingKey: String get() = initializer.deriveSpendingKey(seedPhrase)
val spendingKey: String get() = DerivationTool.deriveSpendingKeys(SimpleMnemonics().toSeed(seedPhrase.toCharArray()))[0]
//
// High-level APIs
@ -66,11 +67,11 @@ class DarksideTestCoordinator(val host: String = "127.0.0.1", val testName: Stri
*/
fun initiate() {
twig("*************** INITIALIZING TEST COORDINATOR (ONLY ONCE) ***********************")
initializer.importPhrase(
seedPhrase,
birthdayHeight,
testName
)
val initializer = Initializer(context) { config ->
config.seedPhrase(seedPhrase)
config.birthdayHeight = birthdayHeight
config.alias = testName
}
synchronizer = Synchronizer(initializer) as SdkSynchronizer
val channel = (synchronizer as SdkSynchronizer).channel
darkside = DarksideApi(channel)
@ -307,4 +308,4 @@ class DarksideTestCoordinator(val host: String = "127.0.0.1", val testName: Stri
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-large-reorg.txt"
private const val DEFAULT_START_HEIGHT = 663150
}
}
}

View File

@ -6,6 +6,7 @@ import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.ext.TroubleshootingTwig
import cash.z.ecc.android.sdk.ext.Twig
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@ -37,9 +38,7 @@ class DataDbScannerUtil {
// private val rustBackend = RustBackend.init(context, cacheDbName, dataDbName)
private val initializer = Initializer(context, host, port, alias)
private lateinit var birthday: Initializer.WalletBirthday
private val birthdayHeight = 600_000
private lateinit var synchronizer: Synchronizer
@ -47,7 +46,6 @@ class DataDbScannerUtil {
fun setup() {
Twig.plant(TroubleshootingTwig())
// cacheBlocks()
birthday = Initializer.DefaultBirthdayStore(context, birthdayHeight, alias).getBirthday()
}
private fun cacheBlocks() = runBlocking {
@ -67,8 +65,7 @@ class DataDbScannerUtil {
@Test
fun scanExistingDb() {
initializer.open(birthday)
synchronizer = Synchronizer(initializer)
synchronizer = Synchronizer(Initializer(context) { it.birthdayHeight = birthdayHeight})
println("sync!")
synchronizer.start()

View File

@ -6,9 +6,10 @@ 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
import cash.z.ecc.android.sdk.ext.*
import cash.z.ecc.android.sdk.import
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
@ -38,7 +39,7 @@ class IntegrationTest {
@Test
fun testLoadBirthday() {
val (height, hash, time, tree) = Initializer.DefaultBirthdayStore.loadBirthdayFromAssets(context, ZcashSdk.SAPLING_ACTIVATION_HEIGHT + 1)
val (height, hash, time, tree) = WalletBirthdayTool.loadNearest(context, ZcashSdk.SAPLING_ACTIVATION_HEIGHT + 1)
assertEquals(ZcashSdk.SAPLING_ACTIVATION_HEIGHT, height)
}
@ -74,7 +75,7 @@ class IntegrationTest {
}
private suspend fun sendFunds(): Boolean {
val spendingKey = RustBackend().deriveSpendingKeys(seed)[0]
val spendingKey = DerivationTool.deriveSpendingKeys(seed)[0]
log("sending to address")
synchronizer.sendToAddress(
spendingKey,
@ -104,8 +105,10 @@ class IntegrationTest {
val toAddress = "zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0"
private val context = InstrumentationRegistry.getInstrumentation().context
private val initializer = Initializer(context, host, port).apply {
import(seed, birthdayHeight, overwrite = true)
private val initializer = Initializer(context) { config ->
config.setSeed(seed)
config.server(host, port)
config.birthdayHeight = birthdayHeight
}
private val synchronizer: Synchronizer = Synchronizer(initializer)

View File

@ -13,7 +13,7 @@ import java.io.File
/**
* Simplified Initializer focused on starting from a ViewingKey.
*/
class VkInitializer(appContext: Context, builder: Builder): SdkSynchronizer.SdkInitializer {
class Initializer private constructor(appContext: Context, builder: Builder): SdkSynchronizer.SdkInitializer {
override val context = appContext.applicationContext
override val rustBackend: RustBackend
override val alias: String
@ -23,7 +23,9 @@ class VkInitializer(appContext: Context, builder: Builder): SdkSynchronizer.Sdk
val birthday: WalletBirthdayTool.WalletBirthday
init {
birthday = builder.birthday
val loadedBirthday =
builder.birthday ?: WalletBirthdayTool.loadNearest(context, builder.birthdayHeight)
birthday = loadedBirthday
viewingKeys = builder.viewingKeys
alias = builder.alias
host = builder.host
@ -32,11 +34,11 @@ class VkInitializer(appContext: Context, builder: Builder): SdkSynchronizer.Sdk
initMissingDatabases(birthday, *viewingKeys.toTypedArray())
}
constructor(appContext: Context, block: Builder.() -> Unit) : this(appContext, Builder(appContext, block))
constructor(appContext: Context, block: (Builder) -> Unit) : this(appContext, Builder(block))
private fun initRustBackend(birthday: WalletBirthdayTool.WalletBirthday): RustBackend {
return RustBackend().init(
return RustBackend.init(
cacheDbPath(context, alias),
dataDbPath(context, alias),
"${context.cacheDir.absolutePath}/params",
@ -151,34 +153,47 @@ class VkInitializer(appContext: Context, builder: Builder): SdkSynchronizer.Sdk
}
class Builder(appContext: Context, block: Builder.() -> Unit) {
private val context = appContext.applicationContext
/* lateinit fields that can be set in multiple ways on this builder */
lateinit var birthday: WalletBirthdayTool.WalletBirthday
private set
val viewingKeys = mutableListOf<String>()
/* optional fields with default values */
var alias: String = ZcashSdk.DEFAULT_ALIAS
var host: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST
var port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT
var birthdayHeight: Int? = null
set(value) {
field = value
birthday = WalletBirthdayTool(context).loadNearest(value)
}
init {
block()
validateAlias(alias)
validateViewingKeys()
validateBirthday()
class Builder private constructor(
val viewingKeys: MutableList<String> = mutableListOf(),
var birthday: WalletBirthdayTool.WalletBirthday? = null,
var birthdayHeight: Int? = null,
var alias: String = ZcashSdk.DEFAULT_ALIAS,
var host: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST,
var port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT
) {
constructor(block: (Builder) -> Unit) : this(mutableListOf(), null, null) {
block(this)
validate()
}
constructor(
viewingKeys: MutableList<String> = mutableListOf(),
birthday: WalletBirthdayTool.WalletBirthday? = null,
/* optional fields with default values */
alias: String = ZcashSdk.DEFAULT_ALIAS,
host: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST,
port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT
) : this(viewingKeys, birthday, -1, alias, host, port) {
validate()
}
constructor(
viewingKeys: MutableList<String> = mutableListOf(),
birthdayHeight: Int = -1,
/* optional fields with default values */
alias: String = ZcashSdk.DEFAULT_ALIAS,
host: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST,
port: Int = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT
) : this(viewingKeys, null, birthdayHeight, alias, host, port) {
validate()
}
fun build(context: Context): Initializer {
if (birthday == null) {
birthday = WalletBirthdayTool.loadNearest(context, birthdayHeight)
}
return Initializer(context, this)
}
/**
* Add viewing keys to the set of accounts to monitor. Note: Using more than one viewing key
@ -258,16 +273,35 @@ class VkInitializer(appContext: Context, builder: Builder): SdkSynchronizer.Sdk
// Validation helpers
//
fun validate() {
validateAlias(alias)
validateViewingKeys()
validateBirthday()
}
private fun validateBirthday() {
require(::birthday.isInitialized) {
// one of the fields must be properly set
require((birthdayHeight ?: -1) >= ZcashSdk.SAPLING_ACTIVATION_HEIGHT
|| birthdayHeight != null) {
"Birthday is required but was not set on this initializer. Verify that a valid" +
" birthday was provided when creating the Initializer such as" +
" WalletBirthdayTool.loadNearest()"
}
require(birthday.height >= ZcashSdk.SAPLING_ACTIVATION_HEIGHT) {
"Invalid birthday height of ${birthday.height}. The birthday height must be at" +
" least the height of Sapling activation on ${ZcashSdk.NETWORK}" +
" (${ZcashSdk.SAPLING_ACTIVATION_HEIGHT})."
// but not both
require((birthdayHeight ?: -1) < ZcashSdk.SAPLING_ACTIVATION_HEIGHT
|| birthday == null) {
"Ambiguous birthday. Either the birthday Object or the birthdayHeight Int should" +
" be set but not both."
}
// the field that is set should contain a proper value
require(
(birthdayHeight ?: birthday?.height ?: -1) >= ZcashSdk.SAPLING_ACTIVATION_HEIGHT
) {
"Invalid birthday height of ${birthdayHeight ?: birthday?.height}. The birthday" +
" height must be at least the height of Sapling activation on" +
" ${ZcashSdk.NETWORK} (${ZcashSdk.SAPLING_ACTIVATION_HEIGHT})."
}
}
@ -281,5 +315,23 @@ class VkInitializer(appContext: Context, builder: Builder): SdkSynchronizer.Sdk
}
}
}
}
}
/**
* 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 1..99 && 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

@ -12,7 +12,7 @@ import java.io.File
* not be called directly by code outside of the SDK. Instead, one of the higher-level components
* should be used such as Wallet.kt or CompactBlockProcessor.kt.
*/
class RustBackend : RustBackendWelding {
class RustBackend private constructor() : RustBackendWelding {
init {
load()
@ -27,27 +27,6 @@ class RustBackend : RustBackendWelding {
get() = if (field != -1) field else throw BirthdayException.UninitializedBirthdayException
private set
/**
* Loads the library and initializes path variables. Although it is best to only call this
* function once, it is idempotent.
*/
fun init(
cacheDbPath: String,
dataDbPath: String,
paramsPath: String,
birthdayHeight: Int? = null
): RustBackend {
twig("Creating RustBackend") {
pathCacheDb = cacheDbPath
pathDataDb = dataDbPath
pathParamsDir = paramsPath
if (birthdayHeight != null) {
this.birthdayHeight = birthdayHeight
}
}
return this
}
fun clear(clearCacheDb: Boolean = true, clearDataDb: Boolean = true) {
if (clearCacheDb) {
twig("Deleting the cache database!")
@ -158,6 +137,26 @@ class RustBackend : RustBackendWelding {
companion object {
private var loaded = false
/**
* Loads the library and initializes path variables. Although it is best to only call this
* function once, it is idempotent.
*/
fun init(
cacheDbPath: String,
dataDbPath: String,
paramsPath: String,
birthdayHeight: Int? = null
): RustBackend {
return RustBackend().apply {
pathCacheDb = cacheDbPath
pathDataDb = dataDbPath
pathParamsDir = paramsPath
if (birthdayHeight != null) {
this.birthdayHeight = birthdayHeight
}
}
}
fun load() {
// It is safe to call these things twice but not efficient. So we add a loose check and
// ignore the fact that it's not thread-safe.