Merge pull request #225 from zcash/hotfix/1.3.0-beta06

Hotfix/1.3.0 beta06
This commit is contained in:
Kevin Gorham 2021-04-29 15:59:26 -04:00 committed by GitHub
commit fb2e9cff51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 170 additions and 116 deletions

View File

@ -1,6 +1,14 @@
Change Log
==========
Version 1.3.0-beta06 *(2021-04-29)*
------------------------------------
- Fix: Repair publishing so that AARs work on Windows machines [issue #222].
- Fix: Incorrect BranchId on 32-bit devics [issue #224].
- Fix: Rescan should not go beyond the wallet checkpoint.
- New: Drop Android Jetifier since it is no longer used.
- Updated checkpoints, improved tests (added Test Suites) and better error messages.
Version 1.3.0-beta05 *(2021-04-23)*
------------------------------------
- Major: Consolidate product flavors into one library for the SDK instead of two.

View File

@ -47,6 +47,7 @@ apply plugin: 'com.vanniktech.maven.publish'
repositories {
google()
mavenCentral()
jcenter()
maven { url 'https://jitpack.io' }
}
@ -109,6 +110,8 @@ android {
jvmTarget = "1.8"
freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"
freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.FlowPreview"
// Tricky: fix: By default, the kotlin_module name will not include the version (in classes.jar/META-INF). Instead it has a colon, which breaks compilation on Windows. This is one way to set it explicitly to the proper value. See https://github.com/zcash/zcash-android-wallet-sdk/issues/222 for more info.
freeCompilerArgs += ["-module-name", "${config.publish.artifactId}-${config.publish.versionName}_${config.publish.target}"]
}
packagingOptions {

View File

@ -9,8 +9,8 @@ targetSdkVersion = 30
publish {
group = 'cash.z.ecc.android'
versionName = '1.3.0-beta05'
versionCode = 1_03_00_205 // 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-beta06'
versionCode = 1_03_00_206 // 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'
}

View File

@ -14,9 +14,11 @@ org.gradle.jvmargs=-Xmx1536m
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# support libraries have long been changed to AndroidX I think we can stop leaning on jetifier now!
#android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official

View File

@ -0,0 +1,33 @@
package cash.z.ecc.android.sdk
import cash.z.ecc.android.sdk.integration.SanityTest
import cash.z.ecc.android.sdk.integration.SmokeTest
import cash.z.ecc.android.sdk.integration.service.ChangeServiceTest
import cash.z.ecc.android.sdk.jni.BranchIdTest
import cash.z.ecc.android.sdk.jni.TransparentTest
import cash.z.ecc.android.sdk.transaction.PersistentTransactionManagerTest
import org.junit.runner.RunWith
import org.junit.runners.Suite
/**
* Suite of tests to run before submitting a pull request.
*
* For now, these are just the tests that are known to be recently updated and that pass. In the
* near future this suite will contain only fast running tests that can be used to quickly validate
* that a PR hasn't broken anything major.
*/
@RunWith(Suite::class)
@Suite.SuiteClasses(
// Fast tests that only run locally and don't require darksidewalletd or lightwalletd
BranchIdTest::class,
TransparentTest::class,
PersistentTransactionManagerTest::class,
// potentially exclude because these are long-running (and hit external srvcs)
SanityTest::class,
// potentially exclude because these hit external services
ChangeServiceTest::class,
SmokeTest::class,
)
class PullRequestSuite

View File

@ -126,7 +126,7 @@ class SanityTest(
TestWallet(TestWallet.Backups.SAMPLE_WALLET, ZcashNetwork.Mainnet),
"zxviews1q0hxkupsqqqqpqzsffgrk2smjuccedua7zswf5e3rgtv3ga9nhvhjug670egshd6me53r5n083s2m9mf4va4z7t39ltd3wr7hawnjcw09eu85q0ammsg0tsgx24p4ma0uvr4p8ltx5laum2slh2whc23ctwlnxme9w4dw92kalwk5u4wyem8dynknvvqvs68ktvm8qh7nx9zg22xfc77acv8hk3qqll9k3x4v2fa26puu2939ea7hy4hh60ywma69xtqhcy4037ne8g2sg8sq",
"031c6355641237643317e2d338f5e8734c57e8aa8ce960ee22283cf2d76bef73be",
1195000
1000000
)
)
}

View File

@ -47,7 +47,7 @@ class SmokeTest {
@Test
fun testSync() = runBlocking<Unit> {
wallet.sync(120_000L)
wallet.sync(300_000L)
}
companion object {

View File

@ -121,7 +121,7 @@ class ChangeServiceTest : ScopedTest() {
}
assertNotNull("Using an invalid host should generate an exception.", caughtException)
assertTrue(
"Exception was of the wrong type.",
"Exception was of the wrong type. Expected ${ChainInfoNotMatching::class.simpleName} but was ${caughtException!!::class.simpleName}",
caughtException is ChainInfoNotMatching
)
(caughtException as ChainInfoNotMatching).propertyNames.let { props ->

View File

@ -0,0 +1,63 @@
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.type.ZcashNetwork
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
/**
* This test is intended to run to make sure that branch ID logic works across all target devices.
*/
@MaintainedTest(TestPurpose.REGRESSION)
@RunWith(Parameterized::class)
class BranchIdTest(
private val networkName: String,
private val height: Int,
private val branchId: Long,
private val branchHex: String,
private val rustBackend: RustBackendWelding
) {
@Test
fun testBranchId_Hex() {
val branchId = rustBackend.getBranchIdForHeight(height)
val clientBranch = "%x".format(branchId)
assertEquals("Invalid branch Id Hex value for $networkName at height $height on ${rustBackend.network.networkName}", branchHex, clientBranch)
}
@Test
fun testBranchId_Numeric() {
val actual = rustBackend.getBranchIdForHeight(height)
assertEquals("Invalid branch ID for $networkName at height $height on ${rustBackend.network.networkName}", branchId, actual)
}
companion object {
@JvmStatic
@Parameterized.Parameters
fun wallets(): List<Array<Any>> {
// init values don't matter for this test because we're just checking branchIds, which
// is an abnormal use of the SDK because this really should run at the rust level
// However, due to quirks on certain devices, we created this test at the Android level,
// as a sanity check
val testnetBackend = RustBackend.init("", "", "", ZcashNetwork.Testnet)
val mainnetBackend = RustBackend.init("", "", "", ZcashNetwork.Mainnet)
return listOf(
// Mainnet Cases
arrayOf("Sapling", 419_200, 1991772603L, "76b809bb", mainnetBackend),
arrayOf("Blossom", 653_600, 733220448L, "2bb40e60", mainnetBackend),
arrayOf("Heartwood", 903_000, 4122551051L, "f5b9230b", mainnetBackend),
arrayOf("Canopy", 1_046_400, 3925833126L, "e9ff75a6", mainnetBackend),
// Testnet Cases
arrayOf("Sapling", 280_000, 1991772603L, "76b809bb", testnetBackend),
arrayOf("Blossom", 584_000, 733220448L, "2bb40e60", testnetBackend),
arrayOf("Heartwood", 903_800, 4122551051L, "f5b9230b", testnetBackend),
arrayOf("Canopy", 1_028_500, 3925833126L, "e9ff75a6", testnetBackend),
)
}
}
}

View File

@ -1,23 +1,8 @@
package cash.z.ecc.android.sdk.sample
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.Synchronizer.Status.SYNCED
import cash.z.ecc.android.sdk.ext.TroubleshootingTwig
import cash.z.ecc.android.sdk.ext.Twig
import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.ZcashNetwork.Testnet
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.newFixedThreadPoolContext
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.android.sdk.util.TestWallet
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Test
@ -27,7 +12,7 @@ import org.junit.Test
*/
class ShieldFundsSample {
val SEED_PHRASE = "wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame" // \"still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread\"//\"deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"//"deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"
val SEED_PHRASE = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" // \"//\"deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"//"deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"
/**
* This test will construct a t2z transaction. It is safe to run this repeatedly, because
@ -40,71 +25,12 @@ class ShieldFundsSample {
fun constructT2Z() = runBlocking {
Twig.sprout("ShieldFundsSample")
val wallet = SimpleWallet(SEED_PHRASE).sync()
wallet.shieldFunds()
val wallet = TestWallet(TestWallet.Backups.DEV_WALLET, ZcashNetwork.Mainnet)
Assert.assertEquals("foo", "${wallet.shieldedAddress} ${wallet.transparentAddress}")
// wallet.shieldFunds()
Twig.clip("ShieldFundsSample")
Assert.assertEquals(5, wallet.synchronizer.latestBalance.availableZatoshi)
}
// when startHeight is null, it will use the latest checkpoint
class SimpleWallet(seedPhrase: String, startHeight: Int? = null) {
// simple flag to turn off actually spending funds
val IS_DRY_RUN = true
val walletScope = CoroutineScope(
SupervisorJob() + newFixedThreadPoolContext(3, this.javaClass.simpleName)
)
private val context = InstrumentationRegistry.getInstrumentation().context
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
private val shieldedSpendingKey = DerivationTool.deriveSpendingKeys(seed, Testnet)[0]
private val transparentSecretKey = DerivationTool.deriveTransparentSecretKey(seed, Testnet)
private val shieldedAddress = DerivationTool.deriveShieldedAddress(seed, Testnet)
// t1b9Y6PESSGavavgge3ruTtX9X83817V29s
private val transparentAddress = DerivationTool.deriveTransparentAddress(seed, Testnet)
private val host = "lightwalletd.testnet.electriccoin.co"
private val config = Initializer.Config {
it.setSeed(seed, Testnet)
it.setBirthdayHeight(startHeight, false)
it.setNetwork(Testnet, host)
}
val synchronizer = Synchronizer(Initializer(context, config))
suspend fun sync(): SimpleWallet {
twig("Starting sync")
synchronizer.start(walletScope)
// block until synced
synchronizer.status.first { it == SYNCED }
twig("Synced!")
return this
}
suspend fun shieldFunds(): SimpleWallet {
twig("checking $transparentAddress for transactions!")
synchronizer.refreshUtxos(transparentAddress, 935000).let { count ->
twig("FOUND $count new UTXOs")
}
synchronizer.getTransparentBalance(transparentAddress).let { walletBalance ->
twig("FOUND utxo balance of total: ${walletBalance.totalZatoshi} available: ${walletBalance.availableZatoshi}")
if (walletBalance.availableZatoshi > 0L && !IS_DRY_RUN) {
synchronizer.shieldFunds(shieldedSpendingKey, transparentSecretKey)
.onCompletion { twig("done shielding funds") }
.catch { twig("Failed with $it") }
.collect()
}
}
return this
}
companion object {
init {
Twig.plant(TroubleshootingTwig())
}
}
}
}

View File

@ -44,7 +44,7 @@ class TestWallet(
) : this(
backup.seedPhrase,
network = network,
startHeight = backup.testnetBirthday,
startHeight = if (network == ZcashNetwork.Mainnet) backup.mainnetBirthday else backup.testnetBirthday,
alias = alias
)
@ -106,7 +106,7 @@ class TestWallet(
}
suspend fun rewindToHeight(height: Int): TestWallet {
synchronizer.rewindToHeight(height, false)
synchronizer.rewindToNearestHeight(height, false)
return this
}
@ -151,11 +151,13 @@ class TestWallet(
}
}
enum class Backups(val seedPhrase: String, val testnetBirthday: Int) {
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", 1_355_928),
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", 1_330_190),
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", 1_330_190),
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", 1_330_190),
enum class Backups(val seedPhrase: String, val testnetBirthday: Int, val mainnetBirthday: Int) {
// 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", 1_355_928, 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", 1_330_190, 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", 1_000_000, 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", 1_330_190, 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", 1_330_190, 1_000_000),
;
}
}

View File

@ -1,7 +0,0 @@
{
"network": "main",
"height": 1225000,
"hash": "0000000001977934324bddde1888c658b0faf997d91b36125d42b4a394ce94d5",
"time": 1619171235,
"tree": "010b90d9e211f1a12634595a2b7a3899abb0fb46aada6a88a1d5fe4732286cff1c00130000016671cff0e2881f9923d85952f6116d3855918016e8ef2951040a0ace61b8883e0000000000000134ca9a7c4309349dfe003f3b4b95898b4303631e9be3a25b4e917a4f3472b52f00000121c25bceccda091622bfac1b7973ffaa638abe1f334b3b56f48dc93dc549c9070001ece344ca21dbd3b681f167163d4792165efe8239390afc13378e50d044fee65a01089a1f9d50a037cc66aba4400b1703bcbb66f5f2993fd0dd3bb726e35940916700000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
}

View File

@ -0,0 +1,7 @@
{
"network": "main",
"height": 1230000,
"hash": "0000000001bcfbeb53fd5e9647168265bc5fc8e8622049b9cdd07098f0f51601",
"time": 1619548843,
"tree": "0164566f774ea1f6d7cf21a9fbf9cb3f2e7d1e400097417822a7c233422619c3710013000001bad8de55d9adf4d4ff08f4ad172a5ee80aa0049974c5227d2b07474078f73c2a01e8d5303b72f034efacb9b9aa0697aa5568bf6116f2fdc1e528c03b76ce569c340000000000015334d52667d65b8ff3dcfb3d6ed72c46ad19ea9112813aaf344a3e614eb9012600016cad8c39b711691f2bda74017894c657555a8bf7ef913931ac4f7ba0b48a30120121c25bceccda091622bfac1b7973ffaa638abe1f334b3b56f48dc93dc549c9070001ece344ca21dbd3b681f167163d4792165efe8239390afc13378e50d044fee65a01089a1f9d50a037cc66aba4400b1703bcbb66f5f2993fd0dd3bb726e35940916700000118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
}

View File

@ -17,6 +17,7 @@ import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException.EnhanceTr
import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException.EnhanceTransactionError.EnhanceTxDownloadError
import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException.MismatchedBranch
import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException.MismatchedNetwork
import cash.z.ecc.android.sdk.exception.InitializerException
import cash.z.ecc.android.sdk.exception.RustLayerException
import cash.z.ecc.android.sdk.ext.BatchMetrics
import cash.z.ecc.android.sdk.ext.Twig
@ -383,7 +384,7 @@ class CompactBlockProcessor(
val network = rustBackend.network.networkName
when {
!info.matchingNetwork(network) -> MismatchedNetwork(clientNetwork = network, serverNetwork = info.chainName)
!info.matchingConsensusBranchId(clientBranch) -> MismatchedBranch(clientBranch = clientBranch, serverBranch = info.consensusBranchId)
!info.matchingConsensusBranchId(clientBranch) -> MismatchedBranch(clientBranch = clientBranch, serverBranch = info.consensusBranchId, networkName = network)
else -> null
}
}
@ -593,8 +594,10 @@ class CompactBlockProcessor(
}
suspend fun getNearestRewindHeight(height: Int): Int {
return if (height < lowerBoundHeight) {
lowerBoundHeight
// TODO: add a concept of original checkpoint height to the processor. For now, derive it
val originalCheckpoint = lowerBoundHeight + MAX_REORG_SIZE + 2 // add one because we already have the checkpoint. Add one again because we delete ABOVE the block
return if (height < originalCheckpoint) {
originalCheckpoint
} else {
// tricky: subtract one because we delete ABOVE this block
rustBackend.getNearestRewindHeight(height) - 1
@ -745,11 +748,13 @@ class CompactBlockProcessor(
* @return the address of this wallet.
*/
suspend fun getShieldedAddress(accountId: Int = 0) = withContext(IO) {
repository.getAccount(accountId)!!.rawShieldedAddress
repository.getAccount(accountId)?.rawShieldedAddress
?: throw InitializerException.MissingAddressException("shielded")
}
suspend fun getTransparentAddress(accountId: Int = 0) = withContext(IO) {
repository.getAccount(accountId)!!.rawTransparentAddress
repository.getAccount(accountId)?.rawTransparentAddress
?: throw InitializerException.MissingAddressException("transparent")
}
/**

View File

@ -92,8 +92,8 @@ sealed class CompactBlockProcessorException(message: String, cause: Throwable? =
"Incompatible server: this client expects a server using $clientNetwork but it was $serverNetwork! Try updating the client or switching servers."
)
class MismatchedBranch(clientBranch: String?, serverBranch: String?) : CompactBlockProcessorException(
"Incompatible server: this client expects a server following consensus branch $clientBranch but it was $serverBranch! Try updating the client or switching servers."
class MismatchedBranch(clientBranch: String?, serverBranch: String?, networkName: String?) : CompactBlockProcessorException(
"Incompatible server: this client expects a server following consensus branch $clientBranch on $networkName but it was $serverBranch! Try updating the client or switching servers."
)
}
@ -147,6 +147,14 @@ sealed class InitializerException(message: String, cause: Throwable? = null) : S
" unified viewingKey from the seed or seedPhrase, if they exist, but it is probably" +
" better not to mask this error because the root issue should be addressed."
)
class MissingAddressException(description: String, cause: Throwable? = null) : InitializerException(
"Expected a $description address for this wallet but failed to find one. This usually" +
" means that wallet setup happened incorrectly. If this problem persists, a" +
" workaround might be to go to settings and WIPE the wallet and rescan. Doing so" +
" will restore any missing address information. Meanwhile, please report that" +
" this happened so that the root issue can be uncovered and corrected." +
if (cause != null) "\nCaused by: $cause" else ""
)
object DatabasePathException :
InitializerException(
"Critical failure to locate path for storing databases. Perhaps this device prevents" +

View File

@ -1158,13 +1158,13 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_branchIdFor
_: JClass<'_>,
height: jint,
network_id: jint,
) -> jint {
) -> jlong {
let res = panic::catch_unwind(|| {
let network = parse_network(network_id as u32)?;
let branch: BranchId = BranchId::for_height(&network, BlockHeight::from(height as u32));
let branch_id: u32 = u32::from(branch);
debug!("For height {} found consensus branch {:?}", height, branch);
Ok(branch_id as i32)
debug!("For height {} found consensus branch {:?} with id {}", height, branch, branch_id);
Ok(branch_id.into())
});
unwrap_exc_or(&env, res, -1)
}

View File

@ -106,7 +106,7 @@ version.com.google.protobuf..protobuf-gradle-plugin=0.8.16
version.com.nhaarman.mockitokotlin2..mockito-kotlin=2.2.0
version.com.vanniktech..gradle-maven-publish-plugin=0.14.2
version.com.vanniktech..gradle-maven-publish-plugin=0.15.0
version.io.grpc..grpc-android=1.37.0
@ -124,8 +124,12 @@ 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.kotlinx.coroutines=1.4.2
## # available=1.4.3-native-mt
## # available=1.4.3
## # available=1.5.0-RC
version.org.jetbrains.dokka..dokka-gradle-plugin=1.4.32
@ -138,10 +142,10 @@ version.org.junit.jupiter..junit-jupiter-engine=5.7.1
version.org.junit.jupiter..junit-jupiter-migrationsupport=5.7.1
## # available=5.8.0-M1
version.org.mockito..mockito-android=3.8.0
version.org.mockito..mockito-android=3.9.0
version.org.mockito..mockito-junit-jupiter=3.8.0
version.org.mockito..mockito-junit-jupiter=3.9.0
version.org.owasp..dependency-check-gradle=6.1.5
version.org.owasp..dependency-check-gradle=6.1.6
version.ru.gildor.coroutines..kotlin-coroutines-okhttp=1.0