[#593] Refactor ZcashNetwork

Splits the ZcashNetwork and Lightwalletd server information, making it easier to configure different servers
This commit is contained in:
Carter Jernigan 2022-08-02 09:29:09 -04:00 committed by GitHub
parent 2362c60dd6
commit e01a906407
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 429 additions and 307 deletions

View File

@ -1,6 +1,10 @@
Change Log
==========
Upcoming
------------------------------------
- Split `ZcashNetwork` into `ZcashNetwork` and `LightWalletEndpoint` to decouple network and server configuration
Version 1.8.0-beta01
------------------------------------
- Added `BlockHeight` typesafe object to represent block heights

View File

@ -1,7 +1,16 @@
Troubleshooting Migrations
==========
Upcoming
--------------------------------------
`ZcashNetwork` is no longer an enum. The prior enum values are now declared as object properties `ZcashNetwork.Mainnet` and `ZcashNetwork.Testnet`. For the most part, this change should have minimal impact. ZcashNetwork was also moved from the package `cash.z.ecc.android.sdk.type` to `cash.z.ecc.android.sdk.model`, which will require a change to your import statements. The server fields have been removed from `ZcashNetwork`, allowing server and network configuration to be done independently.
`LightWalletEndpoint` is a new object to represent server information. Default values can be obtained from `LightWalletEndpoint.defaultForNetwork(ZcashNetwork)`
`Synchronizer` no longer allows changing the endpoint after construction. Instead, construct a new `Synchronizer` with the desired endpoint.
Migration to Version 1.8 from 1.7
--------------------------------------
Various APIs used `Int` to represent network block heights. Those APIs now use a typesafe `BlockHeight` type. BlockHeight is constructed with a factory method `BlockHeight.new(ZcashNetwork, Long)` which uses the network to validate the height is above the network's sapling activation height.
`WalletBirthday` has been renamed to `Checkpoint` and removed from the public API. Where clients previously passed in a `WalletBirthday` object, now a `BlockHeight` can be passed in instead.

View File

@ -5,7 +5,7 @@ import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
import cash.z.ecc.android.sdk.darkside.test.ScopedTest
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith

View File

@ -4,7 +4,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
import cash.z.ecc.android.sdk.darkside.test.ScopedTest
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test

View File

@ -5,7 +5,7 @@ import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
import cash.z.ecc.android.sdk.darkside.test.ScopedTest
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.BeforeClass

View File

@ -1,11 +1,12 @@
package cash.z.ecc.android.sdk.darkside.test
import android.content.Context
import cash.z.ecc.android.sdk.R
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.Darkside
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.wallet.sdk.rpc.Darkside
import cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL
import cash.z.wallet.sdk.rpc.DarksideStreamerGrpc
@ -23,17 +24,11 @@ class DarksideApi(
constructor(
appContext: Context,
host: String,
port: Int = ZcashNetwork.Mainnet.defaultPort,
usePlainText: Boolean = appContext.resources.getBoolean(
R.bool.lightwalletd_allow_very_insecure_connections
)
lightWalletEndpoint: LightWalletEndpoint
) : this(
LightWalletGrpcService.createDefaultChannel(
appContext,
host,
port,
usePlainText
lightWalletEndpoint
)
)

View File

@ -5,7 +5,9 @@ import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.Darkside
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.ZcashNetwork
import io.grpc.StatusRuntimeException
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filter
@ -23,10 +25,9 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
alias: String = "DarksideTestCoordinator",
seedPhrase: String = DEFAULT_SEED_PHRASE,
startHeight: BlockHeight = DEFAULT_START_HEIGHT,
host: String = COMPUTER_LOCALHOST,
network: ZcashNetwork = ZcashNetwork.Mainnet,
port: Int = network.defaultPort
) : this(TestWallet(seedPhrase, alias, network, host, startHeight = startHeight, port = port))
endpoint: LightWalletEndpoint = LightWalletEndpoint.Darkside
) : this(TestWallet(seedPhrase, alias, network, endpoint, startHeight = startHeight))
private val targetHeight = BlockHeight.new(wallet.network, 663250)
private val context = InstrumentationRegistry.getInstrumentation().context

View File

@ -11,10 +11,12 @@ import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.Darkside
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
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.ZcashNetwork
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
@ -36,9 +38,8 @@ class TestWallet(
val seedPhrase: String,
val alias: String = "TestWallet",
val network: ZcashNetwork = ZcashNetwork.Testnet,
val host: String = network.defaultHost,
startHeight: BlockHeight? = null,
val port: Int = network.defaultPort
val endpoint: LightWalletEndpoint = LightWalletEndpoint.Darkside,
startHeight: BlockHeight? = null
) {
constructor(
backup: Backups,
@ -66,7 +67,7 @@ class TestWallet(
runBlocking { DerivationTool.deriveTransparentSecretKey(seed, network = network) }
val initializer = runBlocking {
Initializer.new(context) { config ->
runBlocking { config.importWallet(seed, startHeight, network, host, alias = alias) }
runBlocking { config.importWallet(seed, startHeight, network, endpoint, alias = alias) }
}
}
val synchronizer: SdkSynchronizer = runBlocking { Synchronizer.new(initializer) } as SdkSynchronizer
@ -79,7 +80,6 @@ class TestWallet(
runBlocking { DerivationTool.deriveTransparentAddress(seed, network = network) }
val birthdayHeight get() = synchronizer.latestBirthdayHeight
val networkName get() = synchronizer.network.networkName
val connectionInfo get() = service.connectionInfo.toString()
suspend fun transparentBalance(): WalletBalance {
synchronizer.refreshUtxos(transparentAddress, synchronizer.latestBirthdayHeight)
@ -166,11 +166,46 @@ class TestWallet(
enum class Backups(val seedPhrase: String, val testnetBirthday: BlockHeight, val mainnetBirthday: BlockHeight) {
// TODO: get the proper birthday values for these wallets
DEFAULT("column rhythm acoustic gym cost fit keen maze fence seed mail medal shrimp tell relief clip cannon foster soldier shallow refuse lunar parrot banana", BlockHeight.new(ZcashNetwork.Testnet, 1_355_928), BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)),
SAMPLE_WALLET("input frown warm senior anxiety abuse yard prefer churn reject people glimpse govern glory crumble swallow verb laptop switch trophy inform friend permit purpose", BlockHeight.new(ZcashNetwork.Testnet, 1_330_190), BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)),
DEV_WALLET("still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread", BlockHeight.new(ZcashNetwork.Testnet, 1_000_000), BlockHeight.new(ZcashNetwork.Mainnet, 991645)),
ALICE("quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely life cliff spirit admit weekend message recipe trumpet impact kitten", BlockHeight.new(ZcashNetwork.Testnet, 1_330_190), BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)),
BOB("canvas wine sugar acquire garment spy tongue odor hole cage year habit bullet make label human unit option top calm neutral try vocal arena", BlockHeight.new(ZcashNetwork.Testnet, 1_330_190), BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)),
DEFAULT(
"column rhythm acoustic gym cost fit keen maze fence seed mail medal shrimp tell relief clip cannon foster soldier shallow refuse lunar parrot banana",
BlockHeight.new(
ZcashNetwork.Testnet,
1_355_928
),
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
),
SAMPLE_WALLET(
"input frown warm senior anxiety abuse yard prefer churn reject people glimpse govern glory crumble swallow verb laptop switch trophy inform friend permit purpose",
BlockHeight.new(
ZcashNetwork.Testnet,
1_330_190
),
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
),
DEV_WALLET(
"still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread",
BlockHeight.new(
ZcashNetwork.Testnet,
1_000_000
),
BlockHeight.new(ZcashNetwork.Mainnet, 991645)
),
ALICE(
"quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely life cliff spirit admit weekend message recipe trumpet impact kitten",
BlockHeight.new(
ZcashNetwork.Testnet,
1_330_190
),
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
),
BOB(
"canvas wine sugar acquire garment spy tongue odor hole cage year habit bullet make label human unit option top calm neutral try vocal arena",
BlockHeight.new(
ZcashNetwork.Testnet,
1_330_190
),
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
),
;
}
}

View File

@ -11,8 +11,10 @@ import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
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.Mainnet
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
@ -87,15 +89,18 @@ class SampleCodeTest {
// ///////////////////////////////////////////////////
// Query latest block height
@Test fun getLatestBlockHeightTest() {
val lightwalletService = LightWalletGrpcService(context, lightwalletdHost)
val lightwalletService = LightWalletGrpcService.new(context, lightwalletdHost)
log("Latest Block: ${lightwalletService.getLatestBlockHeight()}")
}
// ///////////////////////////////////////////////////
// Download compact block range
@Test fun getBlockRange() {
val blockRange = BlockHeight.new(ZcashNetwork.Mainnet, 500_000)..BlockHeight.new(ZcashNetwork.Mainnet, 500_009)
val lightwalletService = LightWalletGrpcService(context, lightwalletdHost)
val blockRange = BlockHeight.new(ZcashNetwork.Mainnet, 500_000)..BlockHeight.new(
ZcashNetwork.Mainnet,
500_009
)
val lightwalletService = LightWalletGrpcService.new(context, lightwalletdHost)
val blocks = lightwalletService.getBlockRange(blockRange)
assertEquals(blockRange.endInclusive.value - blockRange.start.value, blocks.count())
@ -151,7 +156,7 @@ class SampleCodeTest {
companion object {
private val seed = "Insert seed for testing".toByteArray()
private val lightwalletdHost: String = ZcashNetwork.Mainnet.defaultHost
private val lightwalletdHost = LightWalletEndpoint.Mainnet
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val synchronizer: Synchronizer = run {

View File

@ -20,7 +20,9 @@ import androidx.viewbinding.ViewBinding
import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.internal.service.LightWalletService
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.model.defaultForNetwork
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.navigation.NavigationView
@ -108,7 +110,11 @@ class MainActivity :
if (lightwalletService != null) {
lightwalletService?.shutdown()
}
lightwalletService = LightWalletGrpcService(applicationContext, ZcashNetwork.fromResources(applicationContext))
val network = ZcashNetwork.fromResources(applicationContext)
lightwalletService = LightWalletGrpcService.new(
applicationContext,
LightWalletEndpoint.defaultForNetwork(network)
)
}
private fun onFabClicked(view: View) {

View File

@ -9,9 +9,9 @@ import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetAddressBinding
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

View File

@ -14,9 +14,11 @@ import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.ext.collectWith
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
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 cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.runBlocking
@ -50,8 +52,12 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
// using the ViewingKey to initialize
runBlocking {
Initializer.new(requireApplicationContext(), null) {
it.setNetwork(ZcashNetwork.fromResources(requireApplicationContext()))
it.newWallet(viewingKey, network = ZcashNetwork.fromResources(requireApplicationContext()))
val network = ZcashNetwork.fromResources(requireApplicationContext())
it.newWallet(
viewingKey,
network = network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
)
}
}.let { initializer ->
synchronizer = Synchronizer.newBlocking(initializer)

View File

@ -14,7 +14,7 @@ import cash.z.ecc.android.sdk.demoapp.util.toRelativeTime
import cash.z.ecc.android.sdk.demoapp.util.withCommas
import cash.z.ecc.android.sdk.ext.toHex
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import kotlin.math.min
/**

View File

@ -13,7 +13,7 @@ import cash.z.ecc.android.sdk.demoapp.util.mainActivity
import cash.z.ecc.android.sdk.demoapp.util.toRelativeTime
import cash.z.ecc.android.sdk.demoapp.util.withCommas
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import kotlin.math.max
/**

View File

@ -9,8 +9,8 @@ import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetPrivateKeyBinding
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.launch
/**

View File

@ -17,8 +17,10 @@ import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.ext.collectWith
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.model.defaultForNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.runBlocking
/**
@ -51,11 +53,13 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
initializer = Initializer.newBlocking(
requireApplicationContext(),
Initializer.Config {
val network = ZcashNetwork.fromResources(requireApplicationContext())
runBlocking {
it.importWallet(
seed,
birthday = null,
network = ZcashNetwork.fromResources(requireApplicationContext())
network = network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
)
}
}

View File

@ -21,8 +21,10 @@ import cash.z.ecc.android.sdk.demoapp.util.mainActivity
import cash.z.ecc.android.sdk.ext.collectWith
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 cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -64,8 +66,15 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
// 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 = ZcashNetwork.fromResources(requireApplicationContext())) }
runBlocking {
it.newWallet(
seed,
network = network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
)
}
it.alias = "Demo_Utxos"
}
}
@ -96,12 +105,19 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
val network = ZcashNetwork.fromResources(requireApplicationContext())
binding.textStatus.requestFocus()
val addressToUse = binding.inputAddress.text.toString()
val startToUse = max(binding.inputRangeStart.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)
val startToUse = max(
binding.inputRangeStart.text.toString().toLongOrNull()
?: network.saplingActivationHeight.value,
network.saplingActivationHeight.value
)
val endToUse = binding.inputRangeEnd.text.toString().toLongOrNull()
?: getUxtoEndHeight(requireApplicationContext()).value
var allStart = now
twig("loading transactions in range $startToUse..$endToUse")
val txids = lightwalletService?.getTAddressTransactions(addressToUse, BlockHeight.new(network, startToUse)..BlockHeight.new(network, endToUse))
val txids = lightwalletService?.getTAddressTransactions(
addressToUse,
BlockHeight.new(network, startToUse)..BlockHeight.new(network, endToUse)
)
var delta = now - allStart
updateStatus("found ${txids?.size} transactions in ${delta}ms.", false)
@ -163,7 +179,12 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
resetInBackground()
val seed = Mnemonics.MnemonicCode(sharedViewModel.seedPhrase.value).toSeed()
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
binding.inputAddress.setText(DerivationTool.deriveTransparentAddress(seed, ZcashNetwork.fromResources(requireApplicationContext())))
binding.inputAddress.setText(
DerivationTool.deriveTransparentAddress(
seed,
ZcashNetwork.fromResources(requireApplicationContext())
)
)
}
}

View File

@ -29,9 +29,11 @@ import cash.z.ecc.android.sdk.ext.convertZecToZatoshi
import cash.z.ecc.android.sdk.ext.toZecString
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.twig
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 cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.runBlocking
/**
@ -66,8 +68,14 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
runBlocking {
Initializer.new(requireApplicationContext()) {
runBlocking { it.newWallet(seed, network = ZcashNetwork.fromResources(requireApplicationContext())) }
it.setNetwork(ZcashNetwork.fromResources(requireApplicationContext()))
val network = ZcashNetwork.fromResources(requireApplicationContext())
runBlocking {
it.newWallet(
seed,
network = network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
)
}
}
}.let { initializer ->
synchronizer = Synchronizer.newBlocking(initializer)

View File

@ -4,10 +4,16 @@ package cash.z.ecc.android.sdk.demoapp.util
import android.content.Context
import cash.z.ecc.android.sdk.demoapp.R
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import java.util.*
fun ZcashNetwork.Companion.fromResources(context: Context) = ZcashNetwork.valueOf(
context.getString(
R.string.network_name
)
)
fun ZcashNetwork.Companion.fromResources(context: Context): ZcashNetwork {
val networkNameFromResources = context.getString(R.string.network_name).lowercase(Locale.ROOT)
return if (networkNameFromResources == Testnet.networkName) {
ZcashNetwork.Testnet
} else if (networkNameFromResources.lowercase(Locale.ROOT) == Mainnet.networkName) {
ZcashNetwork.Mainnet
} else {
throw IllegalArgumentException("Unknown network name: $networkNameFromResources")
}
}

View File

@ -3,8 +3,8 @@ package cash.z.ecc.android.sdk
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.CheckpointTool
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.runBlocking
import org.json.JSONObject
import org.junit.Assert.assertEquals
@ -77,6 +77,7 @@ class AssetTest {
val expectedNetworkName = when (network) {
ZcashNetwork.Mainnet -> "main"
ZcashNetwork.Testnet -> "test"
else -> IllegalArgumentException("Unsupported network $network")
}
assertEquals("File: ${it.filename}", expectedNetworkName, jsonObject.getString("network"))

View File

@ -1,7 +1,7 @@
package cash.z.ecc.android.sdk.ext
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.util.SimpleMnemonics
import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient

View File

@ -4,11 +4,10 @@ import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose
import cash.z.ecc.android.sdk.ext.BlockExplorer
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
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.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
@ -31,13 +30,6 @@ class SanityTest(
val networkName = wallet.networkName
val name = "$networkName wallet"
@Test
fun testNotPlaintext() {
val message =
"is using plaintext. This will cause problems for the test. Ensure that the `lightwalletd_allow_very_insecure_connections` resource value is false"
assertFalse("$name $message", wallet.service.connectionInfo.usePlaintext)
}
@Test
fun testFilePaths() {
assertEquals(
@ -80,24 +72,14 @@ class SanityTest(
)
}
@Test
fun testServerConnection() {
assertEquals(
"$name has an invalid server connection",
"$networkName.lightwalletd.com:9067?usePlaintext=false",
wallet.connectionInfo
)
}
@Test
fun testLatestHeight() = runBlocking {
if (wallet.networkName == "mainnet") {
val expectedHeight = BlockExplorer.fetchLatestHeight()
// fetch height directly because the synchronizer hasn't started, yet
val downloaderHeight = wallet.service.getLatestBlockHeight()
val info = wallet.connectionInfo
assertTrue(
"$info\n ${wallet.networkName} Lightwalletd is too far behind. Downloader height $downloaderHeight is more than 10 blocks behind block explorer height $expectedHeight",
"${wallet.endpoint} ${wallet.networkName} Lightwalletd is too far behind. Downloader height $downloaderHeight is more than 10 blocks behind block explorer height $expectedHeight",
expectedHeight - 10 < downloaderHeight.value
)
}

View File

@ -4,7 +4,6 @@ import androidx.test.filters.LargeTest
import androidx.test.filters.MediumTest
import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.util.TestWallet
import kotlinx.coroutines.runBlocking
import org.junit.Assert
@ -19,16 +18,6 @@ import org.junit.Test
@MediumTest
class SmokeTest {
@Test
fun testNotPlaintext() {
val service =
wallet.synchronizer.processor.downloader.lightWalletService as LightWalletGrpcService
Assert.assertFalse(
"Wallet is using plaintext. This will cause problems for the test. Ensure that the `lightwalletd_allow_very_insecure_connections` resource value is false",
service.connectionInfo.usePlaintext
)
}
@Test
fun testFilePaths() {
Assert.assertEquals("Invalid DataDB file", "/data/user/0/cash.z.ecc.android.sdk.test/databases/TestWallet_testnet_Data.db", wallet.initializer.rustBackend.pathDataDb)

View File

@ -1,4 +1,4 @@
package cash.z.wallet.sdk.integration
package cash.z.ecc.android.sdk.integration
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
@ -13,11 +13,12 @@ import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
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.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.test.ScopedTest
import cash.z.ecc.android.sdk.tool.CheckpointTool
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
@ -39,9 +40,9 @@ class TestnetIntegrationTest : ScopedTest() {
@Test
@Ignore("This test is broken")
fun testLatestBlockTest() {
val service = LightWalletGrpcService(
val service = LightWalletGrpcService.new(
context,
host
lightWalletEndpoint
)
val height = service.getLatestBlockHeight()
assertTrue(height > saplingActivation)
@ -118,7 +119,7 @@ class TestnetIntegrationTest : ScopedTest() {
companion object {
init { Twig.plant(TroubleshootingTwig()) }
const val host = "lightwalletd.testnet.z.cash"
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"
@ -129,8 +130,8 @@ class TestnetIntegrationTest : ScopedTest() {
private val context = InstrumentationRegistry.getInstrumentation().context
private val initializer = runBlocking {
Initializer.new(context) { config ->
config.setNetwork(ZcashNetwork.Testnet, host)
runBlocking { config.importWallet(seed, BlockHeight.new(ZcashNetwork.Testnet, birthdayHeight), ZcashNetwork.Testnet) }
config.setNetwork(ZcashNetwork.Testnet, lightWalletEndpoint)
runBlocking { config.importWallet(seed, BlockHeight.new(ZcashNetwork.Testnet, birthdayHeight), ZcashNetwork.Testnet, lightWalletEndpoint) }
}
}
private lateinit var synchronizer: Synchronizer

View File

@ -12,8 +12,11 @@ import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.internal.service.LightWalletService
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.Mainnet
import cash.z.ecc.android.sdk.model.Testnet
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.test.ScopedTest
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
@ -35,13 +38,15 @@ import org.mockito.Spy
class ChangeServiceTest : ScopedTest() {
val network = ZcashNetwork.Mainnet
val lightWalletEndpoint = LightWalletEndpoint.Mainnet
private val eccEndpoint = LightWalletEndpoint("lightwalletd.electriccoin.co", 9087, true)
@Mock
lateinit var mockBlockStore: CompactBlockStore
var mockCloseable: AutoCloseable? = null
@Spy
val service = LightWalletGrpcService(context, network)
val service = LightWalletGrpcService.new(context, lightWalletEndpoint)
lateinit var downloader: CompactBlockDownloader
lateinit var otherService: LightWalletService
@ -50,7 +55,7 @@ class ChangeServiceTest : ScopedTest() {
fun setup() {
initMocks()
downloader = CompactBlockDownloader(service, mockBlockStore)
otherService = LightWalletGrpcService(context, "lightwalletd.electriccoin.co")
otherService = LightWalletGrpcService.new(context, eccEndpoint)
}
@After
@ -106,7 +111,7 @@ class ChangeServiceTest : ScopedTest() {
@Test
fun testSwitchToInvalidServer() = runBlocking {
var caughtException: Throwable? = null
downloader.changeService(LightWalletGrpcService(context, "invalid.lightwalletd")) {
downloader.changeService(LightWalletGrpcService.new(context, LightWalletEndpoint("invalid.lightwalletd", 9087, true))) {
caughtException = it
}
assertNotNull("Using an invalid host should generate an exception.", caughtException)
@ -119,7 +124,7 @@ class ChangeServiceTest : ScopedTest() {
@Test
fun testSwitchToTestnetFails() = runBlocking {
var caughtException: Throwable? = null
downloader.changeService(LightWalletGrpcService(context, ZcashNetwork.Testnet)) {
downloader.changeService(LightWalletGrpcService.new(context, LightWalletEndpoint.Testnet)) {
caughtException = it
}
assertNotNull("Using an invalid host should generate an exception.", caughtException)

View File

@ -3,7 +3,7 @@ package cash.z.ecc.android.sdk.jni
import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test

View File

@ -7,8 +7,8 @@ import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.BeforeClass

View File

@ -2,7 +2,7 @@ package cash.z.ecc.android.sdk.sample
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.util.TestWallet
import kotlinx.coroutines.runBlocking
import org.junit.Assert

View File

@ -5,8 +5,7 @@ import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.type.ZcashNetwork.Testnet
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.util.TestWallet
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
@ -90,7 +89,7 @@ class TransparentRestoreSample {
val wallet0 = TestWallet(
TestWallet.Backups.SAMPLE_WALLET.seedPhrase,
"tmpabc",
Testnet,
ZcashNetwork.Testnet,
startHeight = BlockHeight.new(
ZcashNetwork.Testnet,
1330190
@ -117,7 +116,15 @@ class TransparentRestoreSample {
*/
// @Test
fun hasFunds() = runBlocking<Unit> {
val walletSandbox = TestWallet(TestWallet.Backups.SAMPLE_WALLET.seedPhrase, "WalletC", Testnet, startHeight = BlockHeight.new(ZcashNetwork.Testnet, 1330190))
val walletSandbox = TestWallet(
TestWallet.Backups.SAMPLE_WALLET.seedPhrase,
"WalletC",
ZcashNetwork.Testnet,
startHeight = BlockHeight.new(
ZcashNetwork.Testnet,
1330190
)
)
// val job = walletA.walletScope.launch {
// twig("Syncing WalletA")
// walletA.sync()

View File

@ -4,8 +4,8 @@ import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.CheckpointTool.IS_FALLBACK_ON_FAILURE
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test

View File

@ -1,10 +1,9 @@
package cash.z.ecc.android.sdk.util
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking

View File

@ -9,8 +9,10 @@ import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
import cash.z.ecc.android.sdk.internal.model.Checkpoint
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 cash.z.ecc.android.sdk.tool.CheckpointTool
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
@ -82,8 +84,8 @@ class BalancePrinterUtil {
}.collect { seed ->
// TODO: clear the dataDb but leave the cacheDb
val initializer = Initializer.new(context) { config ->
runBlocking { config.importWallet(seed, birthdayHeight, network) }
config.setNetwork(network)
val endpoint = LightWalletEndpoint.defaultForNetwork(network)
runBlocking { config.importWallet(seed, birthdayHeight, network, endpoint) }
config.alias = alias
}
/*

View File

@ -7,7 +7,7 @@ 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.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

View File

@ -11,10 +11,12 @@ import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
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.Testnet
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.ZcashNetwork
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
@ -36,9 +38,8 @@ class TestWallet(
val seedPhrase: String,
val alias: String = "TestWallet",
val network: ZcashNetwork = ZcashNetwork.Testnet,
val host: String = network.defaultHost,
startHeight: BlockHeight? = null,
val port: Int = network.defaultPort
val endpoint: LightWalletEndpoint = LightWalletEndpoint.Testnet,
startHeight: BlockHeight? = null
) {
constructor(
backup: Backups,
@ -66,7 +67,7 @@ class TestWallet(
runBlocking { DerivationTool.deriveTransparentSecretKey(seed, network = network) }
val initializer = runBlocking {
Initializer.new(context) { config ->
runBlocking { config.importWallet(seed, startHeight, network, host, alias = alias) }
runBlocking { config.importWallet(seed, startHeight, network, endpoint, alias = alias) }
}
}
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(initializer) as SdkSynchronizer
@ -79,7 +80,6 @@ class TestWallet(
runBlocking { DerivationTool.deriveTransparentAddress(seed, network = network) }
val birthdayHeight get() = synchronizer.latestBirthdayHeight
val networkName get() = synchronizer.network.networkName
val connectionInfo get() = service.connectionInfo.toString()
suspend fun transparentBalance(): WalletBalance {
synchronizer.refreshUtxos(transparentAddress, synchronizer.latestBirthdayHeight)
@ -166,11 +166,46 @@ class TestWallet(
enum class Backups(val seedPhrase: String, val testnetBirthday: BlockHeight, val mainnetBirthday: BlockHeight) {
// TODO: get the proper birthday values for these wallets
DEFAULT("column rhythm acoustic gym cost fit keen maze fence seed mail medal shrimp tell relief clip cannon foster soldier shallow refuse lunar parrot banana", BlockHeight.new(ZcashNetwork.Testnet, 1_355_928), BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)),
SAMPLE_WALLET("input frown warm senior anxiety abuse yard prefer churn reject people glimpse govern glory crumble swallow verb laptop switch trophy inform friend permit purpose", BlockHeight.new(ZcashNetwork.Testnet, 1_330_190), BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)),
DEV_WALLET("still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread", BlockHeight.new(ZcashNetwork.Testnet, 1_000_000), BlockHeight.new(ZcashNetwork.Mainnet, 991645)),
ALICE("quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely life cliff spirit admit weekend message recipe trumpet impact kitten", BlockHeight.new(ZcashNetwork.Testnet, 1_330_190), BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)),
BOB("canvas wine sugar acquire garment spy tongue odor hole cage year habit bullet make label human unit option top calm neutral try vocal arena", BlockHeight.new(ZcashNetwork.Testnet, 1_330_190), BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)),
DEFAULT(
"column rhythm acoustic gym cost fit keen maze fence seed mail medal shrimp tell relief clip cannon foster soldier shallow refuse lunar parrot banana",
BlockHeight.new(
ZcashNetwork.Testnet,
1_355_928
),
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
),
SAMPLE_WALLET(
"input frown warm senior anxiety abuse yard prefer churn reject people glimpse govern glory crumble swallow verb laptop switch trophy inform friend permit purpose",
BlockHeight.new(
ZcashNetwork.Testnet,
1_330_190
),
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
),
DEV_WALLET(
"still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread",
BlockHeight.new(
ZcashNetwork.Testnet,
1_000_000
),
BlockHeight.new(ZcashNetwork.Mainnet, 991645)
),
ALICE(
"quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely life cliff spirit admit weekend message recipe trumpet impact kitten",
BlockHeight.new(
ZcashNetwork.Testnet,
1_330_190
),
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
),
BOB(
"canvas wine sugar acquire garment spy tongue odor hole cage year habit bullet make label human unit option top calm neutral try vocal arena",
BlockHeight.new(
ZcashNetwork.Testnet,
1_330_190
),
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
),
;
}
}

View File

@ -6,7 +6,9 @@ import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
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 org.junit.Ignore
import org.junit.Test
@ -14,7 +16,7 @@ class TransactionCounterUtil {
private val network = ZcashNetwork.Mainnet
private val context = InstrumentationRegistry.getInstrumentation().context
private val service = LightWalletGrpcService(context, network)
private val service = LightWalletGrpcService.new(context, LightWalletEndpoint.Mainnet)
init {
Twig.plant(TroubleshootingTwig())

View File

@ -8,7 +8,7 @@ import cash.z.ecc.android.sdk.internal.KEY_VERSION
import cash.z.ecc.android.sdk.internal.VERSION_1
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import org.json.JSONObject
object CheckpointFixture {

View File

@ -10,10 +10,11 @@ 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.UnifiedViewingKey
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.File
@ -21,13 +22,13 @@ import java.io.File
/**
* Simplified Initializer focused on starting from a ViewingKey.
*/
@Suppress("LongParameterList")
class Initializer private constructor(
val context: Context,
internal val rustBackend: RustBackend,
val network: ZcashNetwork,
val alias: String,
val host: String,
val port: Int,
val lightWalletEndpoint: LightWalletEndpoint,
val viewingKeys: List<UnifiedViewingKey>,
val overwriteVks: Boolean,
internal val checkpoint: Checkpoint
@ -45,10 +46,7 @@ class Initializer private constructor(
lateinit var network: ZcashNetwork
private set
lateinit var host: String
private set
var port: Int = ZcashNetwork.Mainnet.defaultPort
lateinit var lightWalletEndpoint: LightWalletEndpoint
private set
/**
@ -160,12 +158,10 @@ class Initializer private constructor(
*/
fun setNetwork(
network: ZcashNetwork,
host: String = network.defaultHost,
port: Int = network.defaultPort
lightWalletEndpoint: LightWalletEndpoint
): Config = apply {
this.network = network
this.host = host
this.port = port
this.lightWalletEndpoint = lightWalletEndpoint
}
/**
@ -175,16 +171,14 @@ class Initializer private constructor(
seed: ByteArray,
birthday: BlockHeight?,
network: ZcashNetwork,
host: String = network.defaultHost,
port: Int = network.defaultPort,
lightWalletEndpoint: LightWalletEndpoint,
alias: String = ZcashSdk.DEFAULT_ALIAS
): Config =
importWallet(
DerivationTool.deriveUnifiedViewingKeys(seed, network = network)[0],
birthday,
network,
host,
port,
lightWalletEndpoint,
alias
)
@ -195,12 +189,11 @@ class Initializer private constructor(
viewingKey: UnifiedViewingKey,
birthday: BlockHeight?,
network: ZcashNetwork,
host: String = network.defaultHost,
port: Int = network.defaultPort,
lightWalletEndpoint: LightWalletEndpoint,
alias: String = ZcashSdk.DEFAULT_ALIAS
): Config = apply {
setViewingKeys(viewingKey)
setNetwork(network, host, port)
setNetwork(network, lightWalletEndpoint)
importedWalletBirthday(birthday)
this.alias = alias
}
@ -211,14 +204,12 @@ class Initializer private constructor(
suspend fun newWallet(
seed: ByteArray,
network: ZcashNetwork,
host: String = network.defaultHost,
port: Int = network.defaultPort,
lightWalletEndpoint: LightWalletEndpoint,
alias: String = ZcashSdk.DEFAULT_ALIAS
): Config = newWallet(
DerivationTool.deriveUnifiedViewingKeys(seed, network)[0],
network,
host,
port,
lightWalletEndpoint,
alias
)
@ -228,12 +219,11 @@ class Initializer private constructor(
fun newWallet(
viewingKey: UnifiedViewingKey,
network: ZcashNetwork,
host: String = network.defaultHost,
port: Int = network.defaultPort,
lightWalletEndpoint: LightWalletEndpoint,
alias: String = ZcashSdk.DEFAULT_ALIAS
): Config = apply {
setViewingKeys(viewingKey)
setNetwork(network, host, port)
setNetwork(network, lightWalletEndpoint)
newWalletBirthday()
this.alias = alias
}
@ -352,8 +342,7 @@ class Initializer private constructor(
rustBackend,
config.network,
config.alias,
config.host,
config.port,
config.lightWalletEndpoint,
config.viewingKeys,
config.overwriteVks,
loadedCheckpoint

View File

@ -50,12 +50,12 @@ import cash.z.ecc.android.sdk.internal.twigTask
import cash.z.ecc.android.sdk.model.BlockHeight
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.ConsensusMatchType
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.wallet.sdk.rpc.Service
import io.grpc.ManagedChannel
import kotlinx.coroutines.CoroutineExceptionHandler
@ -308,20 +308,6 @@ class SdkSynchronizer internal constructor(
*/
override suspend fun getServerInfo(): Service.LightdInfo = processor.downloader.getServerInfo()
/**
* Changes the server that is being used to download compact blocks. This will throw an
* exception if it detects that the server change is invalid e.g. switching to testnet from
* mainnet.
*/
override suspend fun changeServer(host: String, port: Int, errorHandler: (Throwable) -> Unit) {
val info =
(processor.downloader.lightWalletService as LightWalletGrpcService).connectionInfo
processor.downloader.changeService(
LightWalletGrpcService(info.appContext, host, port),
errorHandler
)
}
override suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight =
processor.getNearestRewindHeight(height)
@ -800,7 +786,7 @@ object DefaultSynchronizerFactory {
CompactBlockDbStore.new(initializer.context, initializer.network, initializer.rustBackend.pathCacheDb)
fun defaultService(initializer: Initializer): LightWalletService =
LightWalletGrpcService(initializer.context, initializer.host, initializer.port)
LightWalletGrpcService.new(initializer.context, initializer.lightWalletEndpoint)
fun defaultEncoder(
initializer: Initializer,

View File

@ -7,9 +7,9 @@ import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.model.BlockHeight
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.type.AddressType
import cash.z.ecc.android.sdk.type.ConsensusMatchType
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.wallet.sdk.rpc.Service
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@ -284,18 +284,6 @@ interface Synchronizer {
*/
suspend fun getServerInfo(): Service.LightdInfo
/**
* Gracefully change the server that the Synchronizer is currently using. In some cases, this
* will require waiting until current network activity is complete. Ideally, this would protect
* against accidentally switching between testnet and mainnet, by comparing the service info of
* the existing server with that of the new one.
*/
suspend fun changeServer(
host: String,
port: Int = network.defaultPort,
errorHandler: (Throwable) -> Unit = { throw it }
)
/**
* Download all UTXOs for the given address and store any new ones in the database.
*

View File

@ -2,7 +2,7 @@ package cash.z.ecc.android.sdk.exception
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.wallet.sdk.rpc.Service
import io.grpc.Status
import io.grpc.Status.Code.UNAVAILABLE

View File

@ -2,7 +2,7 @@ package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import org.json.JSONObject
// Version is not returned from the server, so version 1 is implied. A version is declared here

View File

@ -8,7 +8,7 @@ import cash.z.ecc.android.sdk.internal.SdkDispatchers
import cash.z.ecc.android.sdk.internal.SdkExecutors
import cash.z.ecc.android.sdk.internal.db.CompactBlockDb
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.wallet.sdk.rpc.CompactFormats
import kotlinx.coroutines.withContext

View File

@ -1,12 +1,10 @@
package cash.z.ecc.android.sdk.internal.service
import android.content.Context
import cash.z.ecc.android.sdk.R
import cash.z.ecc.android.sdk.annotation.OpenForTesting
import cash.z.ecc.android.sdk.exception.LightWalletException
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.wallet.sdk.rpc.CompactFormats
import cash.z.wallet.sdk.rpc.CompactTxStreamerGrpc
import cash.z.wallet.sdk.rpc.Service
@ -31,39 +29,14 @@ import kotlin.time.Duration.Companion.seconds
*/
@OpenForTesting
class LightWalletGrpcService private constructor(
context: Context,
private val lightWalletEndpoint: LightWalletEndpoint,
var channel: ManagedChannel,
private val singleRequestTimeout: Duration = 10.seconds,
private val streamingRequestTimeout: Duration = 90.seconds
) : LightWalletService {
lateinit var connectionInfo: ConnectionInfo
constructor(
appContext: Context,
network: ZcashNetwork,
usePlaintext: Boolean =
appContext.resources.getBoolean(R.bool.lightwalletd_allow_very_insecure_connections)
) : this(appContext, network.defaultHost, network.defaultPort, usePlaintext)
/**
* Construct an instance that corresponds to the given host and port.
*
* @param appContext the application context used to check whether TLS is required by this build
* flavor.
* @param host the host of the server to use.
* @param port the port of the server to use.
* @param usePlaintext whether to use TLS or plaintext for requests. Plaintext is dangerous so
* it requires jumping through a few more hoops.
*/
constructor(
appContext: Context,
host: String,
port: Int = ZcashNetwork.Mainnet.defaultPort,
usePlaintext: Boolean =
appContext.resources.getBoolean(R.bool.lightwalletd_allow_very_insecure_connections)
) : this(createDefaultChannel(appContext, host, port, usePlaintext)) {
connectionInfo = ConnectionInfo(appContext.applicationContext, host, port, usePlaintext)
}
private val applicationContext = context.applicationContext
/* LightWalletService implementation */
@ -141,22 +114,14 @@ class LightWalletGrpcService private constructor(
}
override fun reconnect() {
twig(
"closing existing channel and then reconnecting to ${connectionInfo.host}:" +
"${connectionInfo.port}?usePlaintext=${connectionInfo.usePlaintext}"
)
twig("closing existing channel and then reconnecting")
channel.shutdown()
channel = createDefaultChannel(
connectionInfo.appContext,
connectionInfo.host,
connectionInfo.port,
connectionInfo.usePlaintext
)
channel = createDefaultChannel(applicationContext, lightWalletEndpoint)
}
// test code
var stateCount = 0
var state: ConnectivityState? = null
internal var stateCount = 0
internal var state: ConnectivityState? = null
private fun requireChannel(): ManagedChannel {
state = channel.getState(false).let { new ->
if (state == new) stateCount++ else stateCount = 0
@ -172,37 +137,13 @@ class LightWalletGrpcService private constructor(
return channel
}
//
// Utilities
//
private fun Channel.createStub(timeoutSec: Duration = 60.seconds) = CompactTxStreamerGrpc
.newBlockingStub(this)
.withDeadlineAfter(timeoutSec.inWholeSeconds, TimeUnit.SECONDS)
/**
* This function effectively parses streaming responses. Each call to next(), on the iterators
* returned from grpc, triggers a network call.
*/
private fun <T> Iterator<T>.toList(): List<T> =
mutableListOf<T>().apply {
while (hasNext()) {
this@apply += next()
}
}
inner class ConnectionInfo(
val appContext: Context,
val host: String,
val port: Int,
val usePlaintext: Boolean
) {
override fun toString(): String {
return "$host:$port?usePlaintext=$usePlaintext"
}
}
companion object {
fun new(context: Context, lightWalletEndpoint: LightWalletEndpoint): LightWalletGrpcService {
val channel = createDefaultChannel(context, lightWalletEndpoint)
return LightWalletGrpcService(context, lightWalletEndpoint, channel)
}
/**
* Convenience function for creating the default channel to be used for all connections. It
* is important that this channel can handle transitioning from WiFi to Cellular connections
@ -210,24 +151,23 @@ class LightWalletGrpcService private constructor(
*/
fun createDefaultChannel(
appContext: Context,
host: String,
port: Int,
usePlaintext: Boolean
lightWalletEndpoint: LightWalletEndpoint
): ManagedChannel {
twig("Creating channel that will connect to $host:$port?usePlaintext=$usePlaintext")
twig(
"Creating channel that will connect to " +
"${lightWalletEndpoint.host}:${lightWalletEndpoint.port}" +
"/?usePlaintext=${!lightWalletEndpoint.isSecure}"
)
return AndroidChannelBuilder
.forAddress(host, port)
.forAddress(lightWalletEndpoint.host, lightWalletEndpoint.port)
.context(appContext)
.enableFullStreamDecompression()
.apply {
if (usePlaintext) {
if (!appContext.resources.getBoolean(
R.bool.lightwalletd_allow_very_insecure_connections
)
) throw LightWalletException.InsecureConnection
usePlaintext()
} else {
if (lightWalletEndpoint.isSecure) {
useTransportSecurity()
} else {
twig("WARNING: Using insecure channel")
usePlaintext()
}
}
.build()
@ -235,6 +175,10 @@ class LightWalletGrpcService private constructor(
}
}
private fun Channel.createStub(timeoutSec: Duration = 60.seconds) = CompactTxStreamerGrpc
.newBlockingStub(this)
.withDeadlineAfter(timeoutSec.inWholeSeconds, TimeUnit.SECONDS)
private fun BlockHeight.toBlockHeight(): Service.BlockID =
Service.BlockID.newBuilder().setHeight(value).build()
@ -243,3 +187,14 @@ private fun ClosedRange<BlockHeight>.toBlockRange(): Service.BlockRange =
.setStart(start.toBlockHeight())
.setEnd(endInclusive.toBlockHeight())
.build()
/**
* This function effectively parses streaming responses. Each call to next(), on the iterators
* returned from grpc, triggers a network call.
*/
private fun <T> Iterator<T>.toList(): List<T> =
mutableListOf<T>().apply {
while (hasNext()) {
this@apply += next()
}
}

View File

@ -16,8 +16,8 @@ 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.ZcashNetwork
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext

View File

@ -9,9 +9,9 @@ import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.BlockHeight
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.UnifiedViewingKey
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.withContext
import java.io.File

View File

@ -4,8 +4,8 @@ import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.model.BlockHeight
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.type.UnifiedViewingKey
import cash.z.ecc.android.sdk.type.ZcashNetwork
/**
* Contract defining the exposed capabilities of the Rust backend.

View File

@ -2,7 +2,6 @@ package cash.z.ecc.android.sdk.model
import android.content.Context
import cash.z.ecc.android.sdk.tool.CheckpointTool
import cash.z.ecc.android.sdk.type.ZcashNetwork
/**
* Represents a block height, which is a UInt32. SDK clients use this class to represent the "birthday" of a wallet.

View File

@ -0,0 +1,5 @@
package cash.z.ecc.android.sdk.model
data class LightWalletEndpoint(val host: String, val port: Int, val isSecure: Boolean) {
companion object
}

View File

@ -0,0 +1,44 @@
@file:Suppress("ktlint:filename")
package cash.z.ecc.android.sdk.model
/*
* This is a set of extension functions currently, because we expect them to change in the future.
*/
fun LightWalletEndpoint.Companion.defaultForNetwork(zcashNetwork: ZcashNetwork): LightWalletEndpoint {
return when (zcashNetwork.id) {
ZcashNetwork.Mainnet.id -> LightWalletEndpoint.Mainnet
ZcashNetwork.Testnet.id -> LightWalletEndpoint.Testnet
else -> error("Unknown network id: ${zcashNetwork.id}")
}
}
/**
* This is a special localhost value on the Android emulator, which allows it to contact
* the localhost of the computer running the emulator.
*/
private const val COMPUTER_LOCALHOST = "10.0.2.2"
private const val DEFAULT_PORT = 9067
val LightWalletEndpoint.Companion.Mainnet
get() = LightWalletEndpoint(
"mainnet.lightwalletd.com",
DEFAULT_PORT,
isSecure = true
)
val LightWalletEndpoint.Companion.Testnet
get() = LightWalletEndpoint(
"testnet.lightwalletd.com",
DEFAULT_PORT,
isSecure = true
)
val LightWalletEndpoint.Companion.Darkside
get() = LightWalletEndpoint(
COMPUTER_LOCALHOST,
DEFAULT_PORT,
isSecure = false
)

View File

@ -0,0 +1,43 @@
package cash.z.ecc.android.sdk.model
/**
* The Zcash network. Should be one of [ZcashNetwork.Testnet] or [ZcashNetwork.Mainnet].
*
* The constructor for the network is public to allow for certain test cases to use a custom "darkside" network.
*/
data class ZcashNetwork(
val id: Int,
val networkName: String,
val saplingActivationHeight: BlockHeight,
val orchardActivationHeight: BlockHeight
) {
@Suppress("MagicNumber")
companion object {
const val ID_TESTNET = 0
const val ID_MAINNET = 1
// You may notice there are extra checkpoints bundled in the SDK that match the
// sapling/orchard activation heights.
val Testnet = ZcashNetwork(
ID_TESTNET,
"testnet",
saplingActivationHeight = BlockHeight(280_000),
orchardActivationHeight = BlockHeight(1_842_420)
)
val Mainnet = ZcashNetwork(
ID_MAINNET,
"mainnet",
saplingActivationHeight = BlockHeight(419_200),
orchardActivationHeight = BlockHeight(1_687_104)
)
fun from(id: Int) = when (id) {
0 -> Testnet
1 -> Mainnet
else -> throw IllegalArgumentException("Unknown network id: $id")
}
}
}

View File

@ -7,7 +7,7 @@ import cash.z.ecc.android.sdk.internal.from
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.model.ZcashNetwork
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.BufferedReader

View File

@ -2,8 +2,8 @@ package cash.z.ecc.android.sdk.tool
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.jni.RustBackendWelding
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
import cash.z.ecc.android.sdk.type.ZcashNetwork
class DerivationTool {

View File

@ -1,18 +0,0 @@
package cash.z.ecc.android.sdk.type
import cash.z.ecc.android.sdk.model.BlockHeight
enum class ZcashNetwork(
val id: Int,
val networkName: String,
val saplingActivationHeight: BlockHeight,
val defaultHost: String,
val defaultPort: Int
) {
Testnet(0, "testnet", BlockHeight(280_000), "testnet.lightwalletd.com", 9067),
Mainnet(1, "mainnet", BlockHeight(419_200), "mainnet.lightwalletd.com", 9067);
companion object {
fun from(id: Int) = values().first { it.id == id }
}
}

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="lightwalletd_allow_very_insecure_connections">false</bool>
</resources>

View File

@ -1,3 +0,0 @@
<resources>
<string name="sdk_test_message">Library linking is working!</string>
</resources>

View File

@ -1,6 +1,5 @@
package cash.z.ecc.android.sdk.model
import cash.z.ecc.android.sdk.type.ZcashNetwork
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

View File

@ -0,0 +1,16 @@
package cash.z.ecc.android.sdk.model
import org.junit.Test
import kotlin.test.assertTrue
class LightWalletEndpointTest {
@Test
fun requireSecureMainnet() {
assertTrue(LightWalletEndpoint.Mainnet.isSecure)
}
@Test
fun requireSecureTestnet() {
assertTrue(LightWalletEndpoint.Testnet.isSecure)
}
}