General cleanup before merging first pass at demo app.

This commit is contained in:
Kevin Gorham 2019-11-23 20:47:50 -05:00
parent 652e862d5c
commit 5380c0d365
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
16 changed files with 98 additions and 101 deletions

View File

@ -1,14 +1,18 @@
package cash.z.wallet.sdk.sample.demoapp
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.wallet.sdk.SdkSynchronizer
import cash.z.wallet.sdk.Initializer
import cash.z.wallet.sdk.Synchronizer
import cash.z.wallet.sdk.entity.isFailure
import cash.z.wallet.sdk.transaction.*
import cash.z.wallet.sdk.demoapp.util.SampleStorageBridge
import cash.z.wallet.sdk.ext.*
import cash.z.wallet.sdk.jni.RustBackend
import cash.z.wallet.sdk.service.LightWalletGrpcService
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.*
import org.junit.BeforeClass
import org.junit.Ignore
import org.junit.Test
@ -19,9 +23,6 @@ import org.junit.Test
*/
class SampleCodeTest {
private val seed = "Insert seed for testing".toByteArray()
private val lightwalletdHost: String = "34.68.177.238"
// ///////////////////////////////////////////////////
// Seed derivation
@Ignore @Test fun createBip39Seed_fromSeedPhrase() {
@ -48,21 +49,17 @@ class SampleCodeTest {
/////////////////////////////////////////////////////
// Derive Extended Spending Key
@Test fun deriveSpendingKey() {
// val wallet = Wallet()
// val privateKeys = wallet.initialize(context, seed)
// assertNotNull("Wallet already existed.", privateKeys)
//
// log("Spending Key: ${privateKeys?.get(0)}")
// log("Address: ${wallet.getAddress()}")
val spendingKeys = RustBackend().deriveSpendingKeys(seed)
assertEquals(1, spendingKeys.size)
log("Spending Key: ${spendingKeys?.get(0)}")
}
/////////////////////////////////////////////////////
// Derive address from Extended Spending Key
@Test fun getAddressFromSpendingKey() {
// TODO: turn spending key into viewing key via:
// let extfvks: Vec<_> = extsks.iter().map(ExtendedFullViewingKey::from).collect();
// val viewingKey = spendingKey //.asViewingKey()
// return getAddressFromViewingKey(viewingKey)
// Get Address
@Test fun getAddress() = runBlocking {
val address = synchronizer.getAddress()
assertFalse(address.isNullOrBlank())
log("Address: $address")
}
/////////////////////////////////////////////////////
@ -106,51 +103,54 @@ class SampleCodeTest {
// ///////////////////////////////////////////////////
// Create a signed transaction (with memo)
@Test fun createTransaction() = runBlocking {
// val wallet = Wallet()
// val repository = PagedTransactionRepository(context)
// val keyManager = SampleStorageBridge().securelyStoreSeed(seed)
// val encoder = WalletTransactionEncoder(wallet, repository, keyManager)
// val amount = 0.123.toZec().convertZecToZatoshi()
// val address = "ztestsapling1tklsjr0wyw0d58f3p7wufvrj2cyfv6q6caumyueadq8qvqt8lda6v6tpx474rfru9y6u75u7qnw"
// val memo = "Test Transaction"
// val encodedTx = encoder.create(amount, address, memo ?: "")
val rustBackend = RustBackend().init(context)
val repository = PagedTransactionRepository(context)
val encoder = WalletTransactionEncoder(rustBackend, repository)
val spendingKey = rustBackend.deriveSpendingKeys(seed)[0]
val amount = 0.123.convertZecToZatoshi()
val address = "ztestsapling1tklsjr0wyw0d58f3p7wufvrj2cyfv6q6caumyueadq8qvqt8lda6v6tpx474rfru9y6u75u7qnw"
val memo = "Test Transaction".toByteArray()
val encodedTx = encoder.createTransaction(spendingKey, amount, address, memo)
assertTrue(encodedTx.raw.isNotEmpty())
log("Transaction ID: ${encodedTx.txId.toHex()}")
}
// ///////////////////////////////////////////////////
// Create a signed transaction (with memo) and broadcast
@Test fun submitTransaction() = runBlocking {
// val amount = 0.123.toZec().convertZecToZatoshi()
// val address = "ztestsapling1tklsjr0wyw0d58f3p7wufvrj2cyfv6q6caumyueadq8qvqt8lda6v6tpx474rfru9y6u75u7qnw"
// val memo = "Test Transaction"
// val transaction = synchronizer.sendToAddress(amount, address, memo)
// log("transaction: $transaction")
val amount = 0.123.convertZecToZatoshi()
val address = "ztestsapling1tklsjr0wyw0d58f3p7wufvrj2cyfv6q6caumyueadq8qvqt8lda6v6tpx474rfru9y6u75u7qnw"
val memo = "Test Transaction"
val spendingKey = RustBackend().deriveSpendingKeys(seed)[0]
val transactionFlow = synchronizer.sendToAddress(spendingKey, amount, address, memo)
transactionFlow.collect {
log("pending transaction updated $it")
assertTrue("Failed to send funds. See log for details.", it?.isFailure() == false)
}
}
///////////////////////////////////////////////////////
// Utility Functions
//////////////////////////////////////////////////////
companion object {
private val seed = "Insert seed for testing".toByteArray()
private val lightwalletdHost: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private lateinit var synchronizer: SdkSynchronizer
private val synchronizer = Synchronizer(context, lightwalletdHost, seed)
@BeforeClass
@JvmStatic
fun init() {
Twig.plant(TroubleshootingTwig())
reset()
}
fun log(message: String?) = twig(message ?: "null")
private fun reset() {
context.getDatabasePath(ZcashSdk.DB_DATA_NAME).absoluteFile.delete()
context.getDatabasePath(ZcashSdk.DB_CACHE_NAME).absoluteFile.delete()
}
private fun ByteArray.toHex(): String {
val sb = StringBuilder(size * 2)
for (b in this)

View File

@ -57,14 +57,3 @@ class MainActivity : AppCompatActivity() {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}

View File

@ -19,7 +19,7 @@ class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
* Create and initialize the wallet. Initialization will return the private keys but for the
* purposes of this demo we don't need them.
*/
initializer.initializeAccounts(seed)
initializer.new(seed)
}
override fun onResetComplete() {

View File

@ -43,8 +43,4 @@ class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
override fun onClear() {
lightwalletService.shutdown()
}
}
//
//for block range, put them in a table and show a list of block heights and vtx count sorted by time
//
// and for this, allow input
}

View File

@ -40,6 +40,7 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
}
}
// TODO: iterate on this demo to show all the blocks in a recyclerview showing block heights and vtx count
private fun setBlockRange(blockRange: IntRange) {
val blocks =
lightwalletService.getBlockRange(blockRange)
@ -54,8 +55,4 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
private fun setError(message: String) {
binding.textInfo.text = "Error: $message"
}
}
//
//for block range, put them in a table and show a list of block heights and vtx count sorted by time
//
// and for this, allow input
}

View File

@ -34,8 +34,8 @@ class HomeFragment : Fragment() {
override fun onResume() {
super.onResume()
twig("Visiting the home screen clears the default databases, for sanity sake, because " +
"each demo is intended to be self-contained.")
twig("CLEARING DATA: Visiting the home screen clears the default databases, for sanity" +
" sake, because each demo is intended to be self-contained.")
App.instance.getDatabasePath("unusued.db").parentFile.listFiles().forEach { it.delete() }
}
}

View File

@ -66,12 +66,6 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
synchronizer.progress.collectWith(lifecycleScope, ::onProgress)
}
// private fun CoroutineScope.launchProgressMonitor(channel: ReceiveChannel<Int>) = launch {
// for (i in channel) {
// onProgress(i)
// }
// }
private fun onProgress(i: Int) {
val message = when (i) {
100 -> "Scanning blocks..."

View File

@ -6,7 +6,8 @@ data class DemoConfig(
val birthdayHeight: Int = 620_000,//523_240,
val network: ZcashNetwork = ZcashNetwork.TEST_NET,
val seed: ByteArray = "testreferencealice".toByteArray(),
val toAddress: String = "ztestsapling1fg82ar8y8whjfd52l0xcq0w3n7nn7cask2scp9rp27njeurr72ychvud57s9tu90fdqgwdt07lg"
val toAddress: String = "ztestsapling1fg82ar8y8whjfd52l0xcq0w3n7nn7cask2scp9rp27njeurr72ychvud57s9tu90fdqgwdt07lg",
val sendAmount: Double = 0.0024
)
enum class ZcashNetwork { MAIN_NET, TEST_NET }

View File

@ -1,17 +0,0 @@
package cash.z.wallet.sdk.sample.demoapp
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View File

@ -88,10 +88,11 @@ class Initializer(
fun new(
seed: ByteArray,
birthday: WalletBirthday = newWalletBirthday,
numberOfAccounts: Int = 1
numberOfAccounts: Int = 1,
overwrite: Boolean = false
): Array<String> {
initRustLibrary()
return initializeAccounts(seed, birthday, numberOfAccounts)
return initializeAccounts(seed, birthday, numberOfAccounts, overwrite)
}
/**
@ -103,10 +104,11 @@ class Initializer(
*/
fun import(
seed: ByteArray,
birthday: WalletBirthday = saplingBirthday
birthday: WalletBirthday = saplingBirthday,
overwrite: Boolean = false
): Array<String> {
initRustLibrary()
return initializeAccounts(seed, birthday)
return initializeAccounts(seed, birthday, overwrite = overwrite)
}
/**
@ -130,14 +132,16 @@ class Initializer(
* @return the spending keys for each account, ordered by index. These keys are only needed for
* spending funds.
*/
fun initializeAccounts(
private fun initializeAccounts(
seed: ByteArray,
birthday: WalletBirthday = newWalletBirthday,
numberOfAccounts: Int = 1
numberOfAccounts: Int = 1,
overwrite: Boolean = false
): Array<String> {
this.birthday = birthday
try {
if (overwrite) rustBackend.clear()
// only creates tables, if they don't exist
rustBackend.initDataDb()
twig("Initialized wallet for first run")
@ -155,7 +159,7 @@ class Initializer(
twig("seeded the database with sapling tree at height ${birthday.height}")
} catch (t: Throwable) {
if (t.message?.contains("is not empty") == true) {
throw InitializerException.AlreadyInitializedException(t)
throw InitializerException.AlreadyInitializedException(t, rustBackend.dbDataPath)
} else {
throw InitializerException.FalseStart(t)
}

View File

@ -10,6 +10,7 @@ import cash.z.wallet.sdk.block.CompactBlockProcessor.WalletBalance
import cash.z.wallet.sdk.block.CompactBlockStore
import cash.z.wallet.sdk.entity.*
import cash.z.wallet.sdk.exception.SynchronizerException
import cash.z.wallet.sdk.ext.ZcashSdk
import cash.z.wallet.sdk.ext.twig
import cash.z.wallet.sdk.ext.twigTask
import cash.z.wallet.sdk.jni.RustBackend
@ -300,6 +301,31 @@ class SdkSynchronizer internal constructor(
}.distinctUntilChanged()
}
/**
* Simplest constructor possible. Useful for demos, sample apps or PoC's. Anything more complex
* will probably want to handle initialization, directly.
*/
fun Synchronizer(
appContext: Context,
lightwalletdHost: String = ZcashSdk.DEFAULT_LIGHTWALLETD_HOST,
seed: ByteArray? = null,
birthday: Initializer.WalletBirthday? = null
): Synchronizer {
val initializer = Initializer(appContext)
if (initializer.hasData()) {
initializer.open()
} else {
seed ?: throw IllegalArgumentException(
"Failed to initialize. A seed is required when no wallet exists on the device."
)
if (birthday == null) {
initializer.new(seed, overwrite = true)
} else {
initializer.import(seed, birthday, overwrite = true)
}
}
return Synchronizer(appContext, lightwalletdHost, initializer.rustBackend)
}
/**
* Constructor function for building a Synchronizer in the most flexible way possible. This allows

View File

@ -73,8 +73,8 @@ sealed class BirthdayException(message: String, cause: Throwable? = null) : SdkE
sealed class InitializerException(message: String, cause: Throwable? = null) : SdkException(message, cause){
class FalseStart(cause: Throwable?) : InitializerException("Failed to initialize accounts due to: $cause", cause)
class AlreadyInitializedException(cause: Throwable) : InitializerException("Failed to initialize the blocks table" +
" because it already exists.", cause)
class AlreadyInitializedException(cause: Throwable, dbPath: String) : InitializerException("Failed to initialize the blocks table" +
" because it already exists in $dbPath", cause)
object DatabasePathException :
InitializerException("Critical failure to locate path for storing databases. Perhaps this" +
" device prevents apps from storing data? We cannot manage initialize the wallet" +

View File

@ -68,6 +68,11 @@ object ZcashSdk {
*/
const val LIGHTWALLETD_PORT = 9067
/**
* The default host to use for lightwalletd.
*/
const val DEFAULT_LIGHTWALLETD_HOST = "listwallted.z.cash"
const val DB_DATA_NAME = "Data.db"
const val DB_CACHE_NAME = "Cache.db"
const val DEFAULT_DB_NAME_PREFIX = "ZcashSdk_"

View File

@ -15,6 +15,10 @@ import java.io.File
*/
class RustBackend : RustBackendWelding {
init {
load()
}
internal lateinit var dbDataPath: String
internal lateinit var dbCachePath: String
internal lateinit var dbNamePrefix: String
@ -40,7 +44,6 @@ class RustBackend : RustBackendWelding {
): RustBackend {
this.dbNamePrefix = dbNamePrefix
twig("Creating RustBackend") {
load()
dbCachePath = File(dbPath, "${dbNamePrefix}${ZcashSdk.DB_CACHE_NAME}").absolutePath
dbDataPath = File(dbPath, "${dbNamePrefix}${ZcashSdk.DB_DATA_NAME}").absolutePath
paramDestinationDir = paramsPath

View File

@ -1,6 +1,5 @@
package cash.z.wallet.sdk.transaction
import cash.z.wallet.sdk.entity.ConfirmedTransaction
import cash.z.wallet.sdk.entity.PendingTransaction
import kotlinx.coroutines.flow.Flow

View File

@ -45,7 +45,7 @@ class WalletTransactionEncoder(
* @return the row id in the transactions table that contains the spend transaction
* or -1 if it failed
*/
suspend fun createSpend(
private suspend fun createSpend(
spendingKey: String,
value: Long,
toAddress: String,