Merge pull request #231 from zcash/release/1.3.0-beta14

Release/1.3.0 beta14
This commit is contained in:
Kevin Gorham 2021-06-19 00:30:50 -04:00 committed by GitHub
commit 81266f01e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 151 additions and 126 deletions

View File

@ -1,6 +1,12 @@
Change Log
==========
Version 1.3.0-beta14 *(2021-06-21)*
------------------------------------
- New: Add separate flows for sapling, orchard and tranparent balances.
- Fix: Continue troubleshooting and fixing server disconnects.
- Updated dependencies.
Version 1.3.0-beta12 *(2021-06-07)*
------------------------------------
- New: Expose network height as StateFlow.

View File

@ -9,8 +9,8 @@ targetSdkVersion = 30
publish {
group = 'cash.z.ecc.android'
versionName = '1.3.0-beta12'
versionCode = 1_03_00_212 // last digits are alpha(0XX) beta(2XX) rc(4XX) release(8XX). Ex: 1_08_04_401 is an release candidate build of version 1.8.4 and 1_08_04_800 would be the final release.
versionName = '1.3.0-beta14'
versionCode = 1_03_00_214 // last digits are alpha(0XX) beta(2XX) rc(4XX) release(8XX). Ex: 1_08_04_401 is an release candidate build of version 1.8.4 and 1_08_04_800 would be the final release.
artifactId = 'zcash-android-sdk'
target = 'release'
}

6
lint.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- Generated by `./gradlew refreshVersions` to avoid errors when using _ as a version. -->
<issue id="GradlePluginVersion" severity="ignore" />
<issue id="GradleDependency" severity="ignore" />
</lint>

View File

@ -1,10 +1,9 @@
import de.fayard.refreshVersions.RefreshVersionsSetup
buildscript {
repositories { gradlePluginPortal() }
dependencies.classpath("de.fayard.refreshVersions:refreshVersions:0.9.7")
}
plugins {
// See https://jmfayard.github.io/refreshVersions
id 'de.fayard.refreshVersions' version '0.10.1'
}
RefreshVersionsSetup.bootstrap(settings)
rootProject.name = 'zcash-android-sdk'

View File

@ -56,7 +56,7 @@ class TestnetIntegrationTest : ScopedTest() {
@Test
fun testBalance() = runBlocking {
var availableBalance: Long = 0L
synchronizer.balances.onFirst {
synchronizer.saplingBalances.onFirst {
availableBalance = it.availableZatoshi
}
@ -74,7 +74,7 @@ class TestnetIntegrationTest : ScopedTest() {
@Ignore
fun testSpend() = runBlocking {
var success = false
synchronizer.balances.filter { it.availableZatoshi > 0 }.onEach {
synchronizer.saplingBalances.filter { it.availableZatoshi > 0 }.onEach {
success = sendFunds()
}.first()
log("asserting $success")

View File

@ -31,6 +31,6 @@ class ShieldFundsSample {
// wallet.shieldFunds()
Twig.clip("ShieldFundsSample")
Assert.assertEquals(5, wallet.synchronizer.latestBalance.availableZatoshi)
Assert.assertEquals(5, wallet.synchronizer.saplingBalances.value.availableZatoshi)
}
}

View File

@ -206,7 +206,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
}
fun validateMinBalance(available: Long = -1, total: Long = -1) {
val balance = synchronizer.latestBalance
val balance = synchronizer.saplingBalances.value
if (available > 0) {
assertTrue("invalid available balance. Expected a minimum of $available but found ${balance.availableZatoshi}", available <= balance.availableZatoshi)
}

View File

@ -61,7 +61,7 @@ class TestWallet(
val synchronizer: SdkSynchronizer = Synchronizer(initializer) as SdkSynchronizer
val service = (synchronizer.processor.downloader.lightWalletService as LightWalletGrpcService)
val available get() = synchronizer.latestBalance.availableZatoshi
val available get() = synchronizer.saplingBalances.value.availableZatoshi
val shieldedAddress = DerivationTool.deriveShieldedAddress(seed, network = network)
val transparentAddress = DerivationTool.deriveTransparentAddress(seed, network = network)
val birthdayHeight get() = synchronizer.latestBirthdayHeight

View File

@ -66,8 +66,10 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
@ -99,7 +101,12 @@ class SdkSynchronizer internal constructor(
private val txManager: OutboundTransactionManager,
val processor: CompactBlockProcessor
) : Synchronizer {
private val _balances = ConflatedBroadcastChannel(WalletBalance())
// pools
private val _orchardBalances = MutableStateFlow(WalletBalance())
private val _saplingBalances = MutableStateFlow(WalletBalance())
private val _transparentBalances = MutableStateFlow(WalletBalance())
private val _status = ConflatedBroadcastChannel<Synchronizer.Status>(DISCONNECTED)
/**
@ -133,11 +140,18 @@ class SdkSynchronizer internal constructor(
override var isStarted = false
//
// Balances
//
override val orchardBalances = _orchardBalances.asStateFlow()
override val saplingBalances = _saplingBalances.asStateFlow()
override val transparentBalances = _transparentBalances.asStateFlow()
//
// Transactions
//
override val balances: Flow<WalletBalance> = _balances.asFlow()
override val clearedTransactions get() = storage.allTransactions
override val pendingTransactions = txManager.getAll()
override val sentTransactions get() = storage.sentTransactions
@ -223,12 +237,6 @@ class SdkSynchronizer internal constructor(
// Public API
//
/**
* Convenience function for the latest balance. Instead of using this, a wallet will more likely
* want to consume the flow of balances using [balances].
*/
override val latestBalance: WalletBalance get() = _balances.value
/**
* Convenience function for the latest height. Specifically, this value represents the last
* height that the synchronizer has observed from the lightwalletd server. Instead of using
@ -284,8 +292,6 @@ class SdkSynchronizer internal constructor(
processor.stop()
twig("Synchronizer::stop: coroutineScope.cancel()")
coroutineScope.cancel()
twig("Synchronizer::stop: _balances.cancel()")
_balances.cancel()
twig("Synchronizer::stop: _status.cancel()")
_status.cancel()
twig("Synchronizer::stop: COMPLETE")
@ -359,9 +365,21 @@ class SdkSynchronizer internal constructor(
* Calculate the latest balance, based on the blocks that have been scanned and transmit this
* information into the flow of [balances].
*/
suspend fun refreshBalance() {
twig("refreshing balance")
_balances.send(processor.getBalanceInfo())
suspend fun refreshAllBalances() {
refreshSaplingBalance()
refreshTransparentBalance()
// TODO: refresh orchard balance
twig("Warning: Orchard balance does not yet refresh. Only some of the plumbing is in place.")
}
suspend fun refreshSaplingBalance() {
twig("refreshing sapling balance")
_saplingBalances.value = processor.getBalanceInfo()
}
suspend fun refreshTransparentBalance() {
twig("refreshing transparent balance")
_transparentBalances.value = processor.getUtxoCacheBalance(getTransparentAddress())
}
suspend fun isValidAddress(address: String): Boolean {
@ -501,7 +519,7 @@ class SdkSynchronizer internal constructor(
refreshUtxos()
}
twigTask("Triggering balance refresh since $reason!") {
refreshBalance()
refreshAllBalances()
}
twigTask("Triggering pending transaction refresh since $reason!") {
refreshPendingTransactions()
@ -578,7 +596,7 @@ class SdkSynchronizer internal constructor(
twig("[cleanup] deleting expired transactions from storage")
hasCleaned = hasCleaned || (storage.deleteExpired(lastScannedHeight) > 0)
if (hasCleaned) refreshBalance()
if (hasCleaned) refreshAllBalances()
twig("[cleanup] done refreshing and cleaning up pending transactions")
}
@ -619,7 +637,7 @@ class SdkSynchronizer internal constructor(
// only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX.
if (encodedTx.isCancelled()) {
twig("[cleanup] this tx has been cancelled so we will cleanup instead of submitting")
if (cleanupCancelledTx(encodedTx)) refreshBalance()
if (cleanupCancelledTx(encodedTx)) refreshAllBalances()
encodedTx
} else {
txManager.submit(encodedTx)
@ -650,7 +668,7 @@ class SdkSynchronizer internal constructor(
// only submit if it wasn't cancelled. Otherwise cleanup, immediately for best UX.
if (encodedTx.isCancelled()) {
twig("[cleanup] this shielding tx has been cancelled so we will cleanup instead of submitting")
if (cleanupCancelledTx(encodedTx)) refreshBalance()
if (cleanupCancelledTx(encodedTx)) refreshAllBalances()
encodedTx
} else {
txManager.submit(encodedTx)

View File

@ -100,9 +100,19 @@ interface Synchronizer {
val networkHeight: StateFlow<Int>
/**
* A stream of balance values, separately reflecting both the available and total balance.
* A stream of balance values for the orchard pool. Includes the available and total balance.
*/
val balances: Flow<WalletBalance>
val orchardBalances: Flow<WalletBalance>
/**
* A stream of balance values for the sapling pool. Includes the available and total balance.
*/
val saplingBalances: Flow<WalletBalance>
/**
* A stream of balance values for the transparent pool. Includes the available and total balance.
*/
val transparentBalances: Flow<WalletBalance>
/* Transactions */
@ -136,11 +146,6 @@ interface Synchronizer {
*/
val latestHeight: Int
/**
* An in-memory reference to the most recently calculated balance.
*/
val latestBalance: WalletBalance
/**
* An in-memory reference to the best known birthday height, which can change if the first
* transaction has not yet occurred.

View File

@ -1,5 +1,6 @@
package cash.z.ecc.android.sdk.block // iimport cash.z.ecc.android.sdk.exception.LightWalletException
import cash.z.ecc.android.sdk.exception.LightWalletException
import cash.z.ecc.android.sdk.ext.retryUpTo
import cash.z.ecc.android.sdk.ext.tryWarn
import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.service.LightWalletService
@ -77,13 +78,17 @@ open class CompactBlockDownloader private constructor(val compactBlockStore: Com
}
suspend fun getServerInfo(): Service.LightdInfo = withContext<Service.LightdInfo>(IO) {
lateinit var result: Service.LightdInfo
try {
lightWalletService.getServerInfo()
result = lightWalletService.getServerInfo()
} catch (e: StatusRuntimeException) {
twig("WARNING: reconnecting to service in response to failure: $e")
lightWalletService.reconnect()
lightWalletService.getServerInfo()
retryUpTo(2) {
twig("WARNING: reconnecting to service in response to failure (retry #${it + 1}): $e")
lightWalletService.reconnect()
result = lightWalletService.getServerInfo()
}
}
result
}
suspend fun changeService(

View File

@ -11,6 +11,7 @@ import cash.z.wallet.sdk.rpc.CompactTxStreamerGrpc
import cash.z.wallet.sdk.rpc.Service
import com.google.protobuf.ByteString
import io.grpc.Channel
import io.grpc.ConnectivityState
import io.grpc.ManagedChannel
import io.grpc.android.AndroidChannelBuilder
import java.util.concurrent.TimeUnit
@ -66,20 +67,17 @@ class LightWalletGrpcService private constructor(
override fun getBlockRange(heightRange: IntRange): List<CompactFormats.CompactBlock> {
if (heightRange.isEmpty()) return listOf()
channel.resetConnectBackoff()
return channel.createStub(streamingRequestTimeoutSec)
return requireChannel().createStub(streamingRequestTimeoutSec)
.getBlockRange(heightRange.toBlockRange()).toList()
}
override fun getLatestBlockHeight(): Int {
channel.resetConnectBackoff()
return channel.createStub(singleRequestTimeoutSec)
return requireChannel().createStub(singleRequestTimeoutSec)
.getLatestBlock(Service.ChainSpec.newBuilder().build()).height.toInt()
}
override fun getServerInfo(): Service.LightdInfo {
channel.resetConnectBackoff()
return channel.createStub(singleRequestTimeoutSec)
return requireChannel().createStub(singleRequestTimeoutSec)
.getLightdInfo(Service.Empty.newBuilder().build())
}
@ -92,11 +90,10 @@ class LightWalletGrpcService private constructor(
)
.build()
}
channel.resetConnectBackoff()
val request =
Service.RawTransaction.newBuilder().setData(ByteString.copyFrom(spendTransaction))
.build()
return channel.createStub().sendTransaction(request)
return requireChannel().createStub().sendTransaction(request)
}
override fun shutdown() {
@ -107,8 +104,7 @@ class LightWalletGrpcService private constructor(
override fun fetchTransaction(txId: ByteArray): Service.RawTransaction? {
if (txId.isEmpty()) return null
channel.resetConnectBackoff()
return channel.createStub().getTransaction(
return requireChannel().createStub().getTransaction(
Service.TxFilter.newBuilder().setHash(ByteString.copyFrom(txId)).build()
)
}
@ -117,8 +113,7 @@ class LightWalletGrpcService private constructor(
tAddress: String,
startHeight: Int
): List<Service.GetAddressUtxosReply> {
channel.resetConnectBackoff()
val result = channel.createStub().getAddressUtxos(
val result = requireChannel().createStub().getAddressUtxos(
Service.GetAddressUtxosArg.newBuilder().setAddress(tAddress)
.setStartHeight(startHeight.toLong()).build()
)
@ -131,8 +126,7 @@ class LightWalletGrpcService private constructor(
): List<Service.RawTransaction> {
if (blockHeightRange.isEmpty() || tAddress.isBlank()) return listOf()
channel.resetConnectBackoff()
val result = channel.createStub().getTaddressTxids(
val result = requireChannel().createStub().getTaddressTxids(
Service.TransparentAddressBlockFilter.newBuilder().setAddress(tAddress)
.setRange(blockHeightRange.toBlockRange()).build()
)
@ -153,6 +147,19 @@ class LightWalletGrpcService private constructor(
)
}
// test code
var stateCount = 0
var state: ConnectivityState? = null
private fun requireChannel(): ManagedChannel {
state = channel.getState(false).let { new ->
if (state == new) stateCount++ else stateCount = 0
new
}
channel.resetConnectBackoff()
twig("getting channel isShutdown: ${channel.isShutdown} isTerminated: ${channel.isTerminated} getState: $state stateCount: $stateCount")
return channel
}
//
// Utilities
//

View File

@ -1,35 +1,13 @@
## suppress inspection "SpellCheckingInspection" for whole file
## suppress inspection "UnusedProperty" for whole file
##
## Dependencies and Plugin versions with their available updates
## Generated by $ ./gradlew refreshVersions
## Please, don't put extra comments in that file yet, keeping them is not supported yet.
#### Dependencies and Plugin versions with their available updates.
#### Generated by `./gradlew refreshVersions` version 0.10.1
####
#### Don't manually edit or split the comments that start with four hashtags (####),
#### they will be overwritten by refreshVersions.
####
#### suppress inspection "SpellCheckingInspection" for whole file
#### suppress inspection "UnusedProperty" for whole file
plugin.android=4.1.3
## # available=4.2.0-alpha01
## # available=4.2.0-alpha02
## # available=4.2.0-alpha03
## # available=4.2.0-alpha04
## # available=4.2.0-alpha05
## # available=4.2.0-alpha06
## # available=4.2.0-alpha07
## # available=4.2.0-alpha08
## # available=4.2.0-alpha09
## # available=4.2.0-alpha10
## # available=4.2.0-alpha11
## # available=4.2.0-alpha12
## # available=4.2.0-alpha13
## # available=4.2.0-alpha14
## # available=4.2.0-alpha15
## # available=4.2.0-alpha16
## # available=4.2.0-beta01
## # available=4.2.0-beta02
## # available=4.2.0-beta03
## # available=4.2.0-beta04
## # available=4.2.0-beta05
## # available=4.2.0-beta06
## # available=4.2.0-rc01
## # available=4.2.0
plugin.android=4.2.1
## # available=7.0.0-alpha01
## # available=7.0.0-alpha02
## # available=7.0.0-alpha03
@ -45,13 +23,22 @@ plugin.android=4.1.3
## # available=7.0.0-alpha13
## # available=7.0.0-alpha14
## # available=7.0.0-alpha15
## # available=7.0.0-beta01
## # available=7.0.0-beta02
## # available=7.0.0-beta03
## # available=7.0.0-beta04
## # available=7.1.0-alpha01
## # available=7.1.0-alpha02
version.androidx.appcompat=1.3.0-rc01
version.androidx.appcompat=1.3.0
## # available=1.4.0-alpha01
## # available=1.4.0-alpha02
version.androidx.arch.core=2.1.0
version.androidx.lifecycle=2.3.1
## # available=2.4.0-alpha01
## # available=2.4.0-alpha02
version.androidx.multidex=2.0.1
@ -74,34 +61,18 @@ version.androidx.paging=2.1.2
## # available=3.0.0-beta03
## # available=3.0.0-rc01
## # available=3.0.0
## # available=3.1.0-alpha01
version.androidx.room=2.3.0
## # available=2.4.0-alpha01
## # available=2.4.0-alpha02
## # available=2.4.0-alpha03
version.androidx.test=1.3.0
## # available=1.3.1-alpha01
## # available=1.3.1-alpha02
## # available=1.3.1-alpha03
## # available=1.4.0-alpha04
## # available=1.4.0-alpha05
## # available=1.4.0-alpha06
version.androidx.test=1.4.0-beta02
version.androidx.test.core=1.3.0
## # available=1.3.1-alpha01
## # available=1.3.1-alpha02
## # available=1.3.1-alpha03
## # available=1.4.0-alpha04
## # available=1.4.0-alpha05
## # available=1.4.0-alpha06
version.androidx.test.core=1.4.0-beta02
version.androidx.test.ext.junit=1.1.2
## # available=1.1.3-alpha01
## # available=1.1.3-alpha02
## # available=1.1.3-alpha03
## # available=1.1.3-alpha04
## # available=1.1.3-alpha05
## # available=1.1.3-alpha06
version.androidx.test.ext.junit=1.1.3-beta02
version.cash.z.ecc.android..kotlin-bip39=1.0.2
@ -115,43 +86,51 @@ version.com.nhaarman.mockitokotlin2..mockito-kotlin=2.2.0
version.com.vanniktech..gradle-maven-publish-plugin=0.15.1
version.io.grpc..grpc-android=1.37.0
version.io.grpc..grpc-android=1.38.1
version.io.grpc..grpc-okhttp=1.37.0
version.io.grpc..grpc-okhttp=1.38.1
version.io.grpc..grpc-protobuf-lite=1.37.0
version.io.grpc..grpc-protobuf-lite=1.38.1
version.io.grpc..grpc-stub=1.37.0
version.io.grpc..grpc-stub=1.38.1
version.io.grpc..grpc-testing=1.37.0
version.io.grpc..grpc-testing=1.38.1
version.javax.annotation..javax.annotation-api=1.3.2
version.kotlin=1.4.32
## # available=1.5.0-M1
## # available=1.5.0-M2
## # available=1.5.0-RC
## # available=1.5.0
version.junit=5.7.2
### available=5.8.0-M1
version.kotlinx.coroutines=1.4.3
## # available=1.5.0-RC-native-mt
## # available=1.5.0-RC
version.kotlin=1.5.10
## # available=1.5.20-M1
## # available=1.5.20-RC
version.kotlinx.coroutines=1.5.0
version.mockito=3.11.1
version.org.jetbrains.dokka..dokka-gradle-plugin=1.4.32
version.org.junit.jupiter..junit-jupiter-api=5.7.1
version.org.junit.jupiter..junit-jupiter-api=5.7.2
## # available=5.7.2
## # available=5.8.0-M1
version.org.junit.jupiter..junit-jupiter-engine=5.7.1
version.org.junit.jupiter..junit-jupiter-engine=5.7.2
## # available=5.7.2
## # available=5.8.0-M1
version.org.junit.jupiter..junit-jupiter-migrationsupport=5.7.1
version.org.junit.jupiter..junit-jupiter-migrationsupport=5.7.2
## # available=5.7.2
## # available=5.8.0-M1
version.org.mockito..mockito-android=3.10.0
version.org.mockito..mockito-android=3.11.1
## # available=3.11.0
## # available=3.11.1
version.org.mockito..mockito-junit-jupiter=3.10.0
version.org.mockito..mockito-junit-jupiter=3.11.1
## # available=3.11.0
## # available=3.11.1
version.org.owasp..dependency-check-gradle=6.1.6
version.org.owasp..dependency-check-gradle=6.2.2
version.ru.gildor.coroutines..kotlin-coroutines-okhttp=1.0