diff --git a/CHANGELOG.md b/CHANGELOG.md
index 454c0b50..10aea142 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -71,6 +71,17 @@ Change Log
- `DerivationTool.deriveUnifiedViewingKeys`
- `DerivationTool.validateUnifiedViewingKey`
+Version 1.9.0-beta05
+------------------------------------
+- The minimum version of Android supported is now API 21
+- Fixed R8/ProGuard consumer rule, which eliminates a runtime crash for minified apps
+
+Version 1.9.0-beta04
+------------------------------------
+- The SDK now stores sapling param files in `no_backup/co.electricoin.zcash` folder instead of the `cache/params`
+ folder. Besides that, `SaplingParamTool` also does validation of downloaded sapling param file hash and size.
+**No action required from client app**.
+
Version 1.9.0-beta03
------------------------------------
- No changes; this release is a test of a new deployment process
diff --git a/build-conventions/gradle.lockfile b/build-conventions/gradle.lockfile
index 11b99c8a..19d220fc 100644
--- a/build-conventions/gradle.lockfile
+++ b/build-conventions/gradle.lockfile
@@ -1,69 +1,62 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
-androidx.databinding:databinding-common:7.2.2=runtimeClasspath
-androidx.databinding:databinding-compiler-common:7.2.2=runtimeClasspath
-com.android.databinding:baseLibrary:7.2.2=runtimeClasspath
-com.android.tools.analytics-library:crash:30.2.2=runtimeClasspath
-com.android.tools.analytics-library:protos:30.2.2=runtimeClasspath
-com.android.tools.analytics-library:shared:30.2.2=runtimeClasspath
-com.android.tools.analytics-library:tracker:30.2.2=runtimeClasspath
-com.android.tools.build.jetifier:jetifier-core:1.0.0-beta09=runtimeClasspath
-com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta09=runtimeClasspath
-com.android.tools.build:aapt2-proto:7.2.2-7984345=runtimeClasspath
-com.android.tools.build:aaptcompiler:7.2.2=runtimeClasspath
-com.android.tools.build:apksig:7.2.2=compileClasspath,runtimeClasspath
-com.android.tools.build:apkzlib:7.2.2=compileClasspath,runtimeClasspath
-com.android.tools.build:builder-model:7.2.2=compileClasspath,runtimeClasspath
-com.android.tools.build:builder-test-api:7.2.2=runtimeClasspath
-com.android.tools.build:builder:7.2.2=compileClasspath,runtimeClasspath
-com.android.tools.build:bundletool:1.8.2=runtimeClasspath
-com.android.tools.build:gradle-api:7.2.2=compileClasspath,runtimeClasspath
-com.android.tools.build:gradle:7.2.2=compileClasspath,runtimeClasspath
-com.android.tools.build:manifest-merger:30.2.2=compileClasspath,runtimeClasspath
+androidx.databinding:databinding-common:7.3.0=runtimeClasspath
+androidx.databinding:databinding-compiler-common:7.3.0=runtimeClasspath
+com.android.databinding:baseLibrary:7.3.0=runtimeClasspath
+com.android.tools.analytics-library:crash:30.3.0=runtimeClasspath
+com.android.tools.analytics-library:protos:30.3.0=runtimeClasspath
+com.android.tools.analytics-library:shared:30.3.0=runtimeClasspath
+com.android.tools.analytics-library:tracker:30.3.0=runtimeClasspath
+com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=runtimeClasspath
+com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=runtimeClasspath
+com.android.tools.build:aapt2-proto:7.3.0-8691043=runtimeClasspath
+com.android.tools.build:aaptcompiler:7.3.0=runtimeClasspath
+com.android.tools.build:apksig:7.3.0=compileClasspath,runtimeClasspath
+com.android.tools.build:apkzlib:7.3.0=compileClasspath,runtimeClasspath
+com.android.tools.build:builder-model:7.3.0=compileClasspath,runtimeClasspath
+com.android.tools.build:builder-test-api:7.3.0=runtimeClasspath
+com.android.tools.build:builder:7.3.0=compileClasspath,runtimeClasspath
+com.android.tools.build:bundletool:1.9.0=runtimeClasspath
+com.android.tools.build:gradle-api:7.3.0=compileClasspath,runtimeClasspath
+com.android.tools.build:gradle:7.3.0=compileClasspath,runtimeClasspath
+com.android.tools.build:manifest-merger:30.3.0=compileClasspath,runtimeClasspath
com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=runtimeClasspath
-com.android.tools.ddms:ddmlib:30.2.2=runtimeClasspath
-com.android.tools.layoutlib:layoutlib-api:30.2.2=runtimeClasspath
-com.android.tools.lint:lint-model:30.2.2=runtimeClasspath
-com.android.tools.lint:lint-typedef-remover:30.2.2=runtimeClasspath
-com.android.tools.utp:android-device-provider-ddmlib-proto:30.2.2=runtimeClasspath
-com.android.tools.utp:android-device-provider-gradle-proto:30.2.2=runtimeClasspath
-com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.2.2=runtimeClasspath
-com.android.tools.utp:android-test-plugin-host-coverage-proto:30.2.2=runtimeClasspath
-com.android.tools.utp:android-test-plugin-host-retention-proto:30.2.2=runtimeClasspath
-com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.2.2=runtimeClasspath
-com.android.tools:annotations:30.2.2=runtimeClasspath
-com.android.tools:common:30.2.2=runtimeClasspath
-com.android.tools:dvlib:30.2.2=runtimeClasspath
-com.android.tools:repository:30.2.2=runtimeClasspath
-com.android.tools:sdk-common:30.2.2=runtimeClasspath
-com.android.tools:sdklib:30.2.2=runtimeClasspath
-com.android:signflinger:7.2.2=runtimeClasspath
-com.android:zipflinger:7.2.2=compileClasspath,runtimeClasspath
-com.fasterxml.jackson.core:jackson-annotations:2.11.1=runtimeClasspath
-com.fasterxml.jackson.core:jackson-core:2.11.1=runtimeClasspath
-com.fasterxml.jackson.core:jackson-databind:2.11.1=runtimeClasspath
-com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.11.1=runtimeClasspath
-com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.11.1=runtimeClasspath
-com.fasterxml.jackson.module:jackson-module-kotlin:2.11.1=runtimeClasspath
-com.fasterxml.woodstox:woodstox-core:6.2.1=runtimeClasspath
+com.android.tools.ddms:ddmlib:30.3.0=runtimeClasspath
+com.android.tools.layoutlib:layoutlib-api:30.3.0=runtimeClasspath
+com.android.tools.lint:lint-model:30.3.0=runtimeClasspath
+com.android.tools.lint:lint-typedef-remover:30.3.0=runtimeClasspath
+com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=runtimeClasspath
+com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=runtimeClasspath
+com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=runtimeClasspath
+com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=runtimeClasspath
+com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=runtimeClasspath
+com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=runtimeClasspath
+com.android.tools:annotations:30.3.0=runtimeClasspath
+com.android.tools:common:30.3.0=runtimeClasspath
+com.android.tools:dvlib:30.3.0=runtimeClasspath
+com.android.tools:repository:30.3.0=runtimeClasspath
+com.android.tools:sdk-common:30.3.0=runtimeClasspath
+com.android.tools:sdklib:30.3.0=runtimeClasspath
+com.android:signflinger:7.3.0=runtimeClasspath
+com.android:zipflinger:7.3.0=compileClasspath,runtimeClasspath
com.github.gundy:semver4j:0.16.4=runtimeClasspath
com.google.android:annotations:4.1.1.4=runtimeClasspath
-com.google.api.grpc:proto-google-common-protos:1.12.0=runtimeClasspath
+com.google.api.grpc:proto-google-common-protos:2.0.1=runtimeClasspath
com.google.auto.value:auto-value-annotations:1.6.2=runtimeClasspath
com.google.code.findbugs:jsr305:3.0.2=runtimeClasspath
com.google.code.gson:gson:2.8.9=runtimeClasspath
com.google.crypto.tink:tink:1.3.0-rc2=runtimeClasspath
com.google.dagger:dagger:2.28.3=runtimeClasspath
-com.google.errorprone:error_prone_annotations:2.3.4=runtimeClasspath
+com.google.errorprone:error_prone_annotations:2.4.0=runtimeClasspath
com.google.flatbuffers:flatbuffers-java:1.12.0=runtimeClasspath
com.google.guava:failureaccess:1.0.1=runtimeClasspath
com.google.guava:guava:30.1-jre=runtimeClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=runtimeClasspath
com.google.j2objc:j2objc-annotations:1.3=runtimeClasspath
com.google.jimfs:jimfs:1.1=runtimeClasspath
-com.google.protobuf:protobuf-java-util:3.10.0=runtimeClasspath
-com.google.protobuf:protobuf-java:3.10.0=runtimeClasspath
+com.google.protobuf:protobuf-java-util:3.17.2=runtimeClasspath
+com.google.protobuf:protobuf-java:3.17.2=runtimeClasspath
com.google.testing.platform:core-proto:0.0.8-alpha07=runtimeClasspath
com.googlecode.json-simple:json-simple:1.1=runtimeClasspath
com.googlecode.juniversalchardet:juniversalchardet:1.0.3=runtimeClasspath
@@ -77,47 +70,45 @@ commons-codec:commons-codec:1.11=runtimeClasspath
commons-io:commons-io:2.4=runtimeClasspath
commons-logging:commons-logging:1.2=runtimeClasspath
de.undercouch:gradle-download-task:4.1.1=runtimeClasspath
-io.grpc:grpc-api:1.21.1=runtimeClasspath
-io.grpc:grpc-context:1.21.1=runtimeClasspath
-io.grpc:grpc-core:1.21.1=runtimeClasspath
-io.grpc:grpc-netty:1.21.1=runtimeClasspath
-io.grpc:grpc-protobuf-lite:1.21.1=runtimeClasspath
-io.grpc:grpc-protobuf:1.21.1=runtimeClasspath
-io.grpc:grpc-stub:1.21.1=runtimeClasspath
-io.netty:netty-buffer:4.1.34.Final=runtimeClasspath
-io.netty:netty-codec-http2:4.1.34.Final=runtimeClasspath
-io.netty:netty-codec-http:4.1.34.Final=runtimeClasspath
-io.netty:netty-codec-socks:4.1.34.Final=runtimeClasspath
-io.netty:netty-codec:4.1.34.Final=runtimeClasspath
-io.netty:netty-common:4.1.34.Final=runtimeClasspath
-io.netty:netty-handler-proxy:4.1.34.Final=runtimeClasspath
-io.netty:netty-handler:4.1.34.Final=runtimeClasspath
-io.netty:netty-resolver:4.1.34.Final=runtimeClasspath
-io.netty:netty-transport:4.1.34.Final=runtimeClasspath
-io.opencensus:opencensus-api:0.21.0=runtimeClasspath
-io.opencensus:opencensus-contrib-grpc-metrics:0.21.0=runtimeClasspath
+io.grpc:grpc-api:1.39.0=runtimeClasspath
+io.grpc:grpc-context:1.39.0=runtimeClasspath
+io.grpc:grpc-core:1.39.0=runtimeClasspath
+io.grpc:grpc-netty:1.39.0=runtimeClasspath
+io.grpc:grpc-protobuf-lite:1.39.0=runtimeClasspath
+io.grpc:grpc-protobuf:1.39.0=runtimeClasspath
+io.grpc:grpc-stub:1.39.0=runtimeClasspath
+io.netty:netty-buffer:4.1.52.Final=runtimeClasspath
+io.netty:netty-codec-http2:4.1.52.Final=runtimeClasspath
+io.netty:netty-codec-http:4.1.52.Final=runtimeClasspath
+io.netty:netty-codec-socks:4.1.52.Final=runtimeClasspath
+io.netty:netty-codec:4.1.52.Final=runtimeClasspath
+io.netty:netty-common:4.1.52.Final=runtimeClasspath
+io.netty:netty-handler-proxy:4.1.52.Final=runtimeClasspath
+io.netty:netty-handler:4.1.52.Final=runtimeClasspath
+io.netty:netty-resolver:4.1.52.Final=runtimeClasspath
+io.netty:netty-transport:4.1.52.Final=runtimeClasspath
+io.perfmark:perfmark-api:0.23.0=runtimeClasspath
it.unimi.dsi:fastutil:8.4.0=runtimeClasspath
jakarta.activation:jakarta.activation-api:1.2.1=runtimeClasspath
jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=runtimeClasspath
+javax.annotation:javax.annotation-api:1.3.2=runtimeClasspath
javax.inject:javax.inject:1=runtimeClasspath
net.java.dev.jna:jna-platform:5.6.0=runtimeClasspath
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,runtimeClasspath
net.sf.jopt-simple:jopt-simple:4.9=runtimeClasspath
net.sf.kxml:kxml2:2.3.0=runtimeClasspath
org.apache.commons:commons-compress:1.20=runtimeClasspath
-org.apache.httpcomponents:httpclient:4.5.9=runtimeClasspath
-org.apache.httpcomponents:httpcore:4.4.11=runtimeClasspath
+org.apache.httpcomponents:httpclient:4.5.13=runtimeClasspath
+org.apache.httpcomponents:httpcore:4.4.13=runtimeClasspath
org.apache.httpcomponents:httpmime:4.5.6=runtimeClasspath
org.bitbucket.b_c:jose4j:0.7.0=runtimeClasspath
-org.bouncycastle:bcpkix-jdk15on:1.56=runtimeClasspath
-org.bouncycastle:bcprov-jdk15on:1.56=runtimeClasspath
+org.bouncycastle:bcpkix-jdk15on:1.67=runtimeClasspath
+org.bouncycastle:bcprov-jdk15on:1.67=runtimeClasspath
org.checkerframework:checker-qual:3.5.0=runtimeClasspath
-org.codehaus.mojo:animal-sniffer-annotations:1.17=runtimeClasspath
-org.codehaus.woodstox:stax2-api:4.2.1=runtimeClasspath
+org.codehaus.mojo:animal-sniffer-annotations:1.19=runtimeClasspath
org.glassfish.jaxb:jaxb-runtime:2.3.2=runtimeClasspath
org.glassfish.jaxb:txw2:2.3.2=runtimeClasspath
org.jdom:jdom2:2.0.6=runtimeClasspath
-org.jetbrains.dokka:dokka-core:1.4.32=runtimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,runtimeClasspath
org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=runtimeClasspath
org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=runtimeClasspath
@@ -165,12 +156,8 @@ org.jetbrains.kotlin:kotlin-util-io:1.6.21=kotlinCompilerPluginClasspathMain
org.jetbrains.kotlin:kotlin-util-io:1.7.10=compileClasspath,runtimeClasspath
org.jetbrains.kotlin:kotlin-util-klib:1.7.10=runtimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=runtimeClasspath
-org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1=runtimeClasspath
org.jetbrains:annotations:13.0=compileClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,runtimeClasspath
-org.jetbrains:markdown-jvm:0.2.1=runtimeClasspath
-org.jetbrains:markdown:0.2.1=runtimeClasspath
org.json:json:20180813=runtimeClasspath
-org.jsoup:jsoup:1.13.1=runtimeClasspath
org.jvnet.staxex:stax-ex:1.8.1=runtimeClasspath
org.ow2.asm:asm-analysis:9.1=runtimeClasspath
org.ow2.asm:asm-commons:9.1=runtimeClasspath
diff --git a/build-conventions/src/main/kotlin/zcash-sdk.android-conventions.gradle.kts b/build-conventions/src/main/kotlin/zcash-sdk.android-conventions.gradle.kts
index 10770b31..84d9c4fa 100644
--- a/build-conventions/src/main/kotlin/zcash-sdk.android-conventions.gradle.kts
+++ b/build-conventions/src/main/kotlin/zcash-sdk.android-conventions.gradle.kts
@@ -1,4 +1,5 @@
import com.android.build.api.dsl.CommonExtension
+import com.android.build.api.dsl.ManagedVirtualDevice
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
pluginManager.withPlugin("com.android.application") {
@@ -51,6 +52,7 @@ pluginManager.withPlugin("com.android.library") {
}
}
+@Suppress("LongMethod")
fun com.android.build.gradle.BaseExtension.configureBaseExtension() {
compileSdkVersion(project.property("ANDROID_COMPILE_SDK_VERSION").toString().toInt())
ndkVersion = project.property("ANDROID_NDK_VERSION").toString()
@@ -85,6 +87,29 @@ fun com.android.build.gradle.BaseExtension.configureBaseExtension() {
if (project.property("IS_USE_TEST_ORCHESTRATOR").toString().toBoolean()) {
execution = "ANDROIDX_TEST_ORCHESTRATOR"
}
+
+ @Suppress("UnstableApiUsage")
+ managedDevices {
+ @Suppress("MagicNumber", "PropertyName", "VariableNaming")
+ val MANAGED_DEVICES_MIN_SDK = 27
+
+ val testDeviceMinSdkVersion = project.properties["ANDROID_MIN_SDK_VERSION"]
+ .toString().toInt().coerceAtLeast(MANAGED_DEVICES_MIN_SDK)
+ val testDeviceMaxSdkVersion = project.properties["ANDROID_TARGET_SDK_VERSION"].toString().toInt()
+
+ devices {
+ create("pixel2Min") {
+ device = "Pixel 2"
+ apiLevel = testDeviceMinSdkVersion
+ systemImageSource = "aosp"
+ }
+ create("pixel2Target") {
+ device = "Pixel 2"
+ apiLevel = testDeviceMaxSdkVersion
+ systemImageSource = "aosp"
+ }
+ }
+ }
}
if (this is CommonExtension<*, *, *, *>) {
diff --git a/build.gradle.kts b/build.gradle.kts
index 6f51cbe9..d35f6463 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -60,10 +60,10 @@ fladle {
// Firebase Test Lab has min and max values that might differ from our project's
// These are determined by `gcloud firebase test android models list`
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
- val FIREBASE_TEST_LAB_MIN_API = 23
+ val FIREBASE_TEST_LAB_MIN_API = 19
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
- val FIREBASE_TEST_LAB_MAX_API = 30
+ val FIREBASE_TEST_LAB_MAX_API = 33
val minSdkVersion = run {
val buildMinSdk = project.properties["ANDROID_MIN_SDK_VERSION"].toString().toInt()
@@ -84,8 +84,8 @@ fladle {
}
devices.addAll(
- mapOf("model" to "Pixel2", "version" to minSdkVersion),
- mapOf("model" to "Pixel2", "version" to targetSdkVersion)
+ mapOf("model" to "Nexus5", "version" to minSdkVersion),
+ mapOf("model" to "Pixel2.arm", "version" to targetSdkVersion)
)
@Suppress("MagicNumber")
diff --git a/darkside-test-lib/build.gradle.kts b/darkside-test-lib/build.gradle.kts
index bbff1b1a..f91994c7 100644
--- a/darkside-test-lib/build.gradle.kts
+++ b/darkside-test-lib/build.gradle.kts
@@ -6,6 +6,8 @@ plugins {
}
android {
+ namespace = "cash.z.ecc.android.sdk.darkside"
+
defaultConfig {
//targetSdk = 30 //Integer.parseInt(project.property("targetSdkVersion"))
multiDexEnabled = true
diff --git a/darkside-test-lib/src/androidTest/AndroidManifest.xml b/darkside-test-lib/src/androidTest/AndroidManifest.xml
index c6357e0e..a2a699c4 100644
--- a/darkside-test-lib/src/androidTest/AndroidManifest.xml
+++ b/darkside-test-lib/src/androidTest/AndroidManifest.xml
@@ -1,7 +1,6 @@
+ xmlns:android="http://schemas.android.com/apk/res/android">
diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt
index 7b4bde93..9f1e9c0c 100644
--- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt
+++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt
@@ -211,7 +211,7 @@ class TestWallet(
1_330_190
),
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
- ),
+ )
;
}
}
diff --git a/darkside-test-lib/src/main/AndroidManifest.xml b/darkside-test-lib/src/main/AndroidManifest.xml
index b68a4b42..92573553 100644
--- a/darkside-test-lib/src/main/AndroidManifest.xml
+++ b/darkside-test-lib/src/main/AndroidManifest.xml
@@ -1,7 +1,6 @@
+ xmlns:android="http://schemas.android.com/apk/res/android">
diff --git a/demo-app/build.gradle.kts b/demo-app/build.gradle.kts
index 0a3e795c..72279b09 100644
--- a/demo-app/build.gradle.kts
+++ b/demo-app/build.gradle.kts
@@ -8,9 +8,11 @@ plugins {
}
android {
+ namespace = "cash.z.ecc.android.sdk.demoapp"
+
defaultConfig {
applicationId = "cash.z.ecc.android.sdk.demoapp"
- minSdk = 19
+ minSdk = 21
versionCode = 1
versionName = "1.0"
multiDexEnabled = true
@@ -20,6 +22,30 @@ android {
viewBinding = true
}
+ val releaseKeystorePath = project.property("ZCASH_RELEASE_KEYSTORE_PATH").toString()
+ val releaseKeystorePassword = project.property("ZCASH_RELEASE_KEYSTORE_PASSWORD").toString()
+ val releaseKeyAlias = project.property("ZCASH_RELEASE_KEY_ALIAS").toString()
+ val releaseKeyAliasPassword =
+ project.property("ZCASH_RELEASE_KEY_ALIAS_PASSWORD").toString()
+ val isReleaseSigningConfigured = listOf(
+ releaseKeystorePath,
+ releaseKeystorePassword,
+ releaseKeyAlias,
+ releaseKeyAliasPassword
+ ).all { it.isNotBlank() }
+
+ signingConfigs {
+ if (isReleaseSigningConfigured) {
+ // If this block doesn't execute, the output will be unsigned
+ create("release").apply {
+ storeFile = File(releaseKeystorePath)
+ storePassword = releaseKeystorePassword
+ keyAlias = releaseKeyAlias
+ keyPassword = releaseKeyAliasPassword
+ }
+ }
+ }
+
flavorDimensions.add("network")
productFlavors {
@@ -46,6 +72,9 @@ android {
File("proguard-project.txt")
)
)
+ if (isReleaseSigningConfigured) {
+ signingConfig = signingConfigs.getByName("release")
+ }
}
}
@@ -75,13 +104,13 @@ dependencies {
}
fladle {
-// Firebase Test Lab has min and max values that might differ from our project's
-// These are determined by `gcloud firebase test android models list`
+ // Firebase Test Lab has min and max values that might differ from our project's
+ // These are determined by `gcloud firebase test android models list`
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
- val FIREBASE_TEST_LAB_MIN_API = 23
+ val FIREBASE_TEST_LAB_MIN_API = 19
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
- val FIREBASE_TEST_LAB_MAX_API = 30
+ val FIREBASE_TEST_LAB_MAX_API = 33
val minSdkVersion = run {
val buildMinSdk =
@@ -116,8 +145,8 @@ fladle {
testTimeout.set("5m")
devices.addAll(
- mapOf("model" to "Pixel2", "version" to minSdkVersion),
- mapOf("model" to "Pixel2", "version" to targetSdkVersion)
+ mapOf("model" to "Nexus5", "version" to minSdkVersion),
+ mapOf("model" to "Pixel2.arm", "version" to targetSdkVersion)
)
flankVersion.set(libs.versions.flank.get())
diff --git a/demo-app/src/main/AndroidManifest.xml b/demo-app/src/main/AndroidManifest.xml
index d2f37c8c..1c99debc 100644
--- a/demo-app/src/main/AndroidManifest.xml
+++ b/demo-app/src/main/AndroidManifest.xml
@@ -1,7 +1,6 @@
+ xmlns:android="http://schemas.android.com/apk/res/android">
() {
val lastSpace = lastIndexOf(' ')
return if (firstSpace != -1 && lastSpace >= firstSpace) {
"${take(firstSpace)}...${takeLast(length - 1 - lastSpace)}"
- } else this
+ } else {
+ this
+ }
}
}
}
diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/TransactionViewHolder.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/TransactionViewHolder.kt
index 7a7067cd..3223c487 100644
--- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/TransactionViewHolder.kt
+++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/TransactionViewHolder.kt
@@ -27,8 +27,11 @@ class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
val isInbound = transaction?.toAddress.isNullOrEmpty()
amountText.text = transaction?.valueInZatoshi.convertZatoshiToZecString()
timeText.text =
- if (transaction == null || transaction.blockTimeInSeconds == 0L) "Pending"
- else formatter.format(transaction.blockTimeInSeconds * 1000L)
+ if (transaction == null || transaction.blockTimeInSeconds == 0L) {
+ "Pending"
+ } else {
+ formatter.format(transaction.blockTimeInSeconds * 1000L)
+ }
infoText.text = getMemoString(transaction)
icon.rotation = if (isInbound) 0f else 180f
diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/UtxoViewHolder.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/UtxoViewHolder.kt
index 498acb0d..04b0672d 100644
--- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/UtxoViewHolder.kt
+++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/UtxoViewHolder.kt
@@ -23,8 +23,11 @@ class UtxoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindTo(transaction: ConfirmedTransaction?) {
amountText.text = transaction?.valueInZatoshi.convertZatoshiToZecString()
timeText.text =
- if (transaction == null || transaction.blockTimeInSeconds == 0L) "Pending"
- else formatter.format(transaction.blockTimeInSeconds * 1000L)
+ if (transaction == null || transaction.blockTimeInSeconds == 0L) {
+ "Pending"
+ } else {
+ formatter.format(transaction.blockTimeInSeconds * 1000L)
+ }
infoText.text = getMemoString(transaction)
}
diff --git a/demo-app/src/main/res/layout/fragment_gallery.xml b/demo-app/src/main/res/layout/fragment_gallery.xml
deleted file mode 100644
index 35aa5845..00000000
--- a/demo-app/src/main/res/layout/fragment_gallery.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
diff --git a/demo-app/src/main/res/layout/fragment_get_block_range.xml b/demo-app/src/main/res/layout/fragment_get_block_range.xml
index 4b22f6b8..bf40c2a5 100644
--- a/demo-app/src/main/res/layout/fragment_get_block_range.xml
+++ b/demo-app/src/main/res/layout/fragment_get_block_range.xml
@@ -33,7 +33,7 @@
android:id="@+id/text_layout_end_height"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="16dp"
+ android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/button_apply"
app:layout_constraintHorizontal_chainStyle="packed"
@@ -57,7 +57,7 @@
android:id="@+id/button_apply"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="16dp"
+ android:layout_marginStart="16dp"
android:text="apply"
app:layout_constraintBottom_toBottomOf="@id/text_layout_end_height"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/demo-app/src/main/res/layout/fragment_home.xml b/demo-app/src/main/res/layout/fragment_home.xml
index 31ed37e7..334671d4 100644
--- a/demo-app/src/main/res/layout/fragment_home.xml
+++ b/demo-app/src/main/res/layout/fragment_home.xml
@@ -11,8 +11,8 @@
android:id="@+id/text_seed_phrase"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:drawableEnd="@drawable/ic_baseline_edit_24"
- android:drawableTint="@color/colorPrimary"
+ app:drawableEndCompat="@drawable/ic_baseline_edit_24"
+ app:drawableTint="@color/colorPrimary"
android:drawablePadding="12dp"
android:padding="24dp"
android:text="Seed phrase set to: apple...fish"
diff --git a/demo-app/src/main/res/layout/fragment_home_second.xml b/demo-app/src/main/res/layout/fragment_home_second.xml
deleted file mode 100644
index b58945eb..00000000
--- a/demo-app/src/main/res/layout/fragment_home_second.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
diff --git a/demo-app/src/main/res/layout/fragment_list_transactions.xml b/demo-app/src/main/res/layout/fragment_list_transactions.xml
index b40524bc..50a3c625 100644
--- a/demo-app/src/main/res/layout/fragment_list_transactions.xml
+++ b/demo-app/src/main/res/layout/fragment_list_transactions.xml
@@ -29,7 +29,7 @@
android:id="@+id/text_layout_end_height"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="16dp"
+ android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/text_layout_start_height"
diff --git a/demo-app/src/main/res/layout/fragment_slideshow.xml b/demo-app/src/main/res/layout/fragment_slideshow.xml
deleted file mode 100644
index 375b34aa..00000000
--- a/demo-app/src/main/res/layout/fragment_slideshow.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
diff --git a/docs/Architecture.md b/docs/Architecture.md
index 50fc76e1..879b5d47 100644
--- a/docs/Architecture.md
+++ b/docs/Architecture.md
@@ -24,12 +24,16 @@ To improve the speed of syncing with the Zcash network, the SDK contains a serie
To update the checkpoints, see [Checkmate](https://github.com/zcash-hackworks/checkmate).
-We generally recommend adding new checkpoints every few weeks. By convention, checkpoints are added in block increments of 10,000 which provides a reasonable tradeoff in terms of number of checkpoints versus performance.
+We generally recommend adding new checkpoints every few weeks. By convention, checkpoints are added in block
+increments of 2,500 for mainnet and 10,000 for testnet. These increments provide a reasonable tradeoff in terms of number of checkpoints versus performance.
-There are two special checkpoints, one for sapling activation and another for orchard activation. These are mentioned because they don't follow the "round 10,000" rule.
+There are two special checkpoints, one for sapling activation and another for orchard activation. These are
+mentioned because they don't follow the "round 2,500 or 10,000" rule.
* Sapling activation
* Mainnet: 419200
* Testnet: 280000
* Orchard activation
* Mainnet: 1687104
- * Testnet: 1842420
\ No newline at end of file
+ * Testnet: 1842420
+
+Note: If you're updating the checkpoints with the Checkmate tool, it'll generate several checkpoint files from input parameters for you. Mostly you can filter out the first and the last one, as the first is probably already imported in the project and the last one does not follow the round rule.
diff --git a/docs/Setup.md b/docs/Setup.md
index 666e6c02..f531451e 100644
--- a/docs/Setup.md
+++ b/docs/Setup.md
@@ -22,6 +22,13 @@ Start by making sure the command line with Gradle works first, because **all the
```bash
rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android
```
+1. Install python 2.7
+ 1. macOS with Homebrew
+ 1. `brew install pyenv`
+ 1. `pyenv install 2.7.18`
+ 1. To enable pyenv in your bash shell run: `eval "$(pyenv init -)"`
+ 1. Get the path to python 2: `which python2`
+ 1. Add `rust.pythonCommand=PYTHON2 PATH` in `${sdkRootDir}/local.properties`
1. Install Android Studio and the Android SDK
1. Download [Android Studio](https://developer.android.com/studio/). We typically use the stable version of Android Studio, unless specifically noted due to short-term known issues.
1. During the Android Studio setup wizard, choose the "Standard" setup option
@@ -39,7 +46,7 @@ Start by making sure the command line with Gradle works first, because **all the
export PATH=${PATH}:${ANDROID_HOME}/platform-tools
```
1. Install the Android NDK
- 1. Go to the Android SDK Manager inside Android Studio
+ 1. Go to the Android SDK Manager inside Android Studio. Select the "SDK Tools" tab.
1. Click the checkbox for "Show Package Details"
1. Install the exact NDK version listed in [gradle.properties](../gradle.properties) under `ANDROID_NDK_VERSION`
1. Configure a device for development and testing
@@ -79,11 +86,11 @@ Start by making sure the command line with Gradle works first, because **all the
1. Delete the invisible `.idea` in the root directory of the project. This directory is partially ignored by Git, so deleting it will remove the files that are untracked
1. Restore the missing files in `.idea` folder from Git
1. Relaunch Android Studio
-2. Clean the individual Gradle project by running `./gradlew clean` which will purge local build outputs.
-3. Run Gradle with the argument `--rerun-tasks` which will effectively disable the build cache by re-running tasks and repopulating the cache. E.g. `./gradlew assemble --rerun-tasks`
-4. Reboot your computer, which will ensure that Gradle and Kotlin daemons are completely killed and relaunched
-5. Delete the global Gradle cache under `~/.gradle/caches`
-6. If adding a new dependency or updating a dependency, a warning that a dependency cannot be found may indicate the Maven repository restrictions need adjusting
+1. Clean the individual Gradle project by running `./gradlew clean` which will purge local build outputs.
+1. Run Gradle with the argument `--rerun-tasks` which will effectively disable the build cache by re-running tasks and repopulating the cache. E.g. `./gradlew assemble --rerun-tasks`
+1. Reboot your computer, which will ensure that Gradle and Kotlin daemons are completely killed and relaunched
+1. Delete the global Gradle cache under `~/.gradle/caches`
+1. If adding a new dependency or updating a dependency, a warning that a dependency cannot be found may indicate the Maven repository restrictions need adjusting
## Gradle Tasks
A variety of Gradle tasks are set up within the project, and these tasks are also accessible in Android Studio as run configurations.
@@ -97,6 +104,8 @@ A variety of Gradle tasks are set up within the project, and these tasks are als
* `lint` - Performs static analysis with Android lint
* `dependencyUpdates` - Checks for available dependency updates. It will only suggest final releases, unless a particular dependency is already using a non-final release (e.g. alpha, beta, RC).
+Gradle Managed Devices are also configured with our build scripts. We have found best results running tests one module at a time, rather than trying to run them all at once. For example: `./gradlew :sdk-lib:pixel2TargetDebugAndroidTest` will run the SDK tests on a Pixel 2 sized device using our target API version.
+
## Gradle Properties
A variety of Gradle properties can be used to configure the build. Most of these properties are optional and help with advanced configuration. If you're just doing local development or making a small pull request contribution, you likely do not need to worry about these.
@@ -110,7 +119,7 @@ For Continuous Integration, see [CI.md](CI.md). The rest of this section is reg
1. Configure or request access to a Firebase Test Lab project
1. If you are an Electric Coin Co team member: Make an IT request to add your Google account to the existing Firebase Test Lab project
- 2. If you are an open source contributor: set up your own Firebase project for the purpose of running Firebase Test Lab
+ 1. If you are an open source contributor: set up your own Firebase project for the purpose of running Firebase Test Lab
1. Set the Firebase Google Cloud project name as a global Gradle property `ZCASH_FIREBASE_TEST_LAB_PROJECT` under `~/.gradle/gradle.properties`
1. Run the Gradle task `flankAuth` to generate a Firebase authentication token on your machine
diff --git a/docs/tests/Download sapling files.md b/docs/tests/Download sapling files.md
new file mode 100644
index 00000000..beeb638a
--- /dev/null
+++ b/docs/tests/Download sapling files.md
@@ -0,0 +1,32 @@
+# About
+This manual test case provides information on how to manually test an implemented action of downloading both of our
+sapling params files (`sapling-spend.params`, `sapling-output.params`) to the preferred location
+`/no_backup/co.electricoin.zcash/`. The benefit of this approach is that the content of `no_backup` folder is not part
+of automatic user data backup to user's cloud storage. Our sapling files are quite big (up to 50MB).
+
+# Prerequisite
+- Installed [Android Studio](https://developer.android.com/studio)
+- Ideally two emulators with min and max supported API level
+- A working git client
+- Cloned [Zcash Android SDK repository](https://github.com/zcash/zcash-android-wallet-sdk)
+- A wallet seed phrase with available funds
+
+# Download files steps
+1. Remove a previous version of the demo-app from the emulator, if there is any
+2. Install the latest version of the demo-app from the latest commit on the **Main** branch
+3. Run the demo-app on selected emulator
+4. Once it's opened on the Home screen, change the wallet seed phrase to your preferred one to have some funds
+available, which can be spent for the purpose of this test
+5. Go to the Send screen and wait for Downloading and Syncing processes to finish
+6. Then type the ZEC amount you want to send and the Address to which you want the Zec amount sent
+7. Wait for send confirmation
+8. Sapling params files should be now downloaded in the preferred location. Open Device File Explorer from Android
+Studio bottom-left corner, select the same emulator device from the top. Go to
+`/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/no_backup/co.electricoin.zcash`, which should be created
+automatically
+9. Now verify there both of our sapling params files (`sapling-spend.params`, `sapling-output.params`) placed in the
+`no_backup/co.electricoin.zcash` folder
+
+# Check result
+Ideally run this test for both emulators (min and max supported API level) to ensure the correct functionality on both
+Android version. There is a difference in implementation for these Android versions, but the result should be the same.
\ No newline at end of file
diff --git a/docs/tests/Move database files to no_backup.md b/docs/tests/Move database files to no_backup.md
index 7e5de1b9..46144312 100644
--- a/docs/tests/Move database files to no_backup.md
+++ b/docs/tests/Move database files to no_backup.md
@@ -1,37 +1,55 @@
# About
-This manual test case provides information on how to manually test an implemented action of moving all of our databases files from default `/databases/` to preferred `/no_backup/co.electricoin.zcash` directory. The benefit of this approach is that the content `no_backup` folder is not part of automatic user data backup to user's cloud storage. Our databases can contain potentially big and sensitive data.
+This manual test case provides information on how to manually test an implemented action of moving all of our databases
+files from default `/databases/` to preferred `/no_backup/co.electricoin.zcash` directory. The benefit of this approach
+is that the content `no_backup` folder is not part of automatic user data backup to user's cloud storage. Our databases
+can contain potentially big and sensitive data.
-The move feature takes all related files (database file itself as well as `journal` and `wal` rollback files) and moves them only once on app start (before first database access) when a client app uses an updated version of this SDK.
+The move feature takes all related files (database file itself as well as `journal` and `wal` rollback files) and moves
+them only once on app start (before first database access) when a client app uses an updated version of this SDK.
# Prerequisite
-- Installed Android Studio
+- Installed [Android Studio](https://developer.android.com/studio)
- Ideally two emulators with min and max supported API level
- A working git client
-- Cloned Zcash Android SDK repository
+- Cloned [Zcash Android SDK repository](https://github.com/zcash/zcash-android-wallet-sdk)
# Prepare steps
1. Install a previous version of the SDK and its demo-app to create database files in the original `database` folder
-1. Switch back to commit **Bump version to 1.8.0-beta01 [3fda6da]** from Jul 11 2022 on the **Main** branch in your git client, or with this git command `git checkout 3fda6da1cae5b83174e5b1e020c91dfe95d93458`
-2. Update dependencies lock (if needed) and sync Gradle files
-3. Run the demo-app on selected emulator
-4. Once it's opened go through the app to let the SDK create all the database files. Visit these screens step by step from the side menu:
+2. Switch back to commit **Bump version to 1.8.0-beta01 [3fda6da]** from Jul 11 2022 on the **Main** branch in your
+ git client, or with this git command `git checkout 3fda6da1cae5b83174e5b1e020c91dfe95d93458`
+3. Update dependencies lock (if needed) and sync Gradle files
+4. Run the demo-app on selected emulator
+5. Once it's opened go through the app to let the SDK create all the database files. Visit these screens step by step
+ from the side menu:
1. Get Balance
2. List Transactions
3. List UTXOs
-2. Open Device File Explorer from Android Studio bottom-left corner, select the same emulator device from the top drop-down menu
-3. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/databases`
-4. Verify there are `data.db`, `cache.db` and `utxos.db` files (their names can vary, depends on the current build variant). There can be several rollback files created.
+6. Open Device File Explorer from Android Studio bottom-left corner, select the same emulator device from the top
+drop-down menu
+7. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/databases`
+8. Verify there are `data.db`, `cache.db` and `utxos.db` files (their names can vary, depends on the current build
+ variant). There can be several rollback files created.
# Move steps
-1. Install the newer version of the SDK and its demo-app to the same device to check the database files move operation result
+1. Install the newer version of the SDK and its demo-app to the same device to check the database files move operation
+result
1. Switch to the latest commit on the **Main** branch in your git client
2. Update dependencies lock (if needed) and sync Gradle files
3. Run the demo-app on the same emulator device as previously
-2. Once the app is opened, go to the Device File Explorer from Android Studio bottom-left corner again
-3. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/databases` again, now there shouldn't be any files placed in the `database` folder
-4. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/no_backup/co.electricoin.zcash`, which should be created automatically
-5. Now verify there are the same files placed in the `no_backup/co.electricoin.zcash` folder as in `databases` were
-6. To be sure everything is alright, just visit several screens from the side-menu and see no unexpected behavior
+2. Once the app is opened go through the same steps as previously to let the SDK apply the move mechanisms to all our
+database files. Visit these screens step by step from the side menu:
+ 1. Get Balance
+ 2. List Transactions
+ 3. List UTXOs
+3. Go to the Device File Explorer from Android Studio bottom-left corner again
+4. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/databases` again, now there shouldn't be any files placed
+in the `database` folder
+5. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/no_backup/co.electricoin.zcash`, which should be created
+automatically
+6. Now verify there are the same files placed in the `no_backup/co.electricoin.zcash` folder as in `databases` were
+7. To be sure everything is alright, just visit several screens from the side-menu and see no unexpected behavior
# Check result
-Ideally run this test (Prepare and Move steps) for both emulators (Android SDK 21 and 31) to ensure the correct functionality on both Android version. There is a difference in implementation for these Android versions, but the result should be the same.
\ No newline at end of file
+Ideally run this test (Prepare and Move steps) for both emulators (min and max supported API level) to ensure the
+correct functionality on both Android version. There is a difference in implementation for these Android versions, but
+the result should be the same.
\ No newline at end of file
diff --git a/docs/tests/Move sapling files to no_backup.md b/docs/tests/Move sapling files to no_backup.md
new file mode 100644
index 00000000..4cdd894b
--- /dev/null
+++ b/docs/tests/Move sapling files to no_backup.md
@@ -0,0 +1,47 @@
+# About
+This manual test case provides information on how to manually test an implemented action of moving both of our
+sapling params files (`sapling-spend.params`, `sapling-output.params`) from legacy location `/cache/params/` to
+the preferred location `/no_backup/co.electricoin.zcash/`. The benefit of this approach is that the content of
+`no_backup` folder is not part of automatic user data backup to user's cloud storage. Our sapling files are quite big
+(up to 50MB).
+
+# Prerequisite
+- Installed [Android Studio](https://developer.android.com/studio)
+- Ideally two emulators with min and max supported API level
+- A working git client
+- Cloned [Zcash Android SDK repository](https://github.com/zcash/zcash-android-wallet-sdk)
+- A wallet seed phrase with available funds
+
+# Prepare steps
+1. Install a previous version of the SDK and its demo-app to create sapling files in the original `cache/params` folder
+2. Switch back to commit **Check sapling files size [12c23dd0]** from Aug 26 2022 on the **Main** branch in your
+git client, or with this git command `git checkout 12c23dd054c687431aaf51bfc5f67d5dbc08625b`
+3. Update dependencies lock (if needed) and sync Gradle files
+4. Run the demo-app on selected emulator
+5. Once it's opened on the Home screen, change the wallet seed phrase to your preferred one to have some funds
+available, which can be spent for the purpose of this test
+6. Go to the Send screen and wait for Downloading and Syncing processes to finish
+7. Then type the ZEC amount you want to send and the Address to which you want the Zec amount sent
+8. Wait for send confirmation
+9. Sapling params files should be now moved to the original location. Open Device File
+ Explorer from Android Studio bottom-left corner, select the same emulator device from the top
+drop-down menu. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/cache/params`
+10. Verify there are `sapling-spend.params` and `sapling-output.params`
+
+# Move steps
+1. Install the newer version of the SDK and its demo-app to the same device to check the database files move operation
+result
+ 1. Switch to the latest commit on the **Main** branch in your git client
+ 2. Update dependencies lock (if needed) and sync Gradle files
+ 3. Run the demo-app on the same emulator device as previously
+2. Once the app is opened, go to the Device File Explorer from Android Studio bottom-left corner again
+3. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/cache/params` again, now there shouldn't be our sapling
+ params files placed in the folder and the folder `/params/` should be missing
+4. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/no_backup/co.electricoin.zcash`, which should be created
+automatically
+5. Now verify there are the same files placed in the `no_backup/co.electricoin.zcash` folder as in `cache/params` were
+
+# Check result
+Ideally run this test (Prepare and Move steps) for both emulators (min and max supported API level) to ensure the
+correct functionality on both Android version. There is a difference in implementation for these Android versions,
+but the result should be the same.
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 1dac21e3..eb42e4b8 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -22,7 +22,7 @@ ZCASH_ASCII_GPG_KEY=
# Configures whether release is an unstable snapshot, therefore published to the snapshot repository.
IS_SNAPSHOT=true
-LIBRARY_VERSION=1.9.0-beta03
+LIBRARY_VERSION=1.9.0-beta04
# Kotlin compiler warnings can be considered errors, failing the build.
ZCASH_IS_TREAT_WARNINGS_AS_ERRORS=true
@@ -52,25 +52,30 @@ IS_MINIFY_APP_ENABLED=true
# are overridden via ~/.gradle/gradle.properties to allow secure injection.
# Debug keystore is useful if using Google Maps or Firebase, which require API keys to be linked
# to a signing key. Without a debug keystore, the default Android debug keystore will be used.
+# Without a release signing configuration, the release output will not be signed.
ZCASH_DEBUG_KEYSTORE_PATH=
+ZCASH_RELEASE_KEYSTORE_PATH=
+ZCASH_RELEASE_KEYSTORE_PASSWORD=
+ZCASH_RELEASE_KEY_ALIAS=
+ZCASH_RELEASE_KEY_ALIAS_PASSWORD=
# Versions
-ANDROID_MIN_SDK_VERSION=19
-ANDROID_TARGET_SDK_VERSION=31
+ANDROID_MIN_SDK_VERSION=21
+ANDROID_TARGET_SDK_VERSION=33
ANDROID_COMPILE_SDK_VERSION=33
# TODO[#317]: https://github.com/zcash/zcash-android-wallet-sdk/issues/317
# When changing this, be sure to update .github/actions/setup/action.yml
ANDROID_NDK_VERSION=22.1.7171670
-ANDROID_GRADLE_PLUGIN_VERSION=7.2.2
+ANDROID_GRADLE_PLUGIN_VERSION=7.3.0
DETEKT_VERSION=1.21.0
DOKKA_VERSION=1.7.10
EMULATOR_WTF_GRADLE_PLUGIN_VERSION=0.0.10
FLANK_VERSION=22.03.0
FULLADLE_VERSION=0.17.4
GRADLE_VERSIONS_PLUGIN_VERSION=0.42.0
-KTLINT_VERSION=0.46.1
+KTLINT_VERSION=0.47.1
KSP_VERSION=1.7.10-1.0.6
PROTOBUF_GRADLE_PLUGIN_VERSION=0.8.19
RUST_GRADLE_PLUGIN_VERSION=0.9.3
@@ -102,10 +107,8 @@ KOTLINX_COROUTINES_VERSION=1.6.4
KOTLIN_VERSION=1.7.10
MOCKITO_KOTLIN_VERSION=2.2.0
MOCKITO_VERSION=4.6.1
-OKHTTP_VERSION=4.10.0
-OKIO_VERSION=3.2.0
PROTOC_VERSION=3.21.4
ZCASH_WALLET_PLUGINS_VERSION=1.0.1
# This shouldn't be changed, as Android doesn't support targets beyond Java 8
-ANDROID_JVM_TARGET=1.8
\ No newline at end of file
+ANDROID_JVM_TARGET=1.8
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 5116c5b1..5e75d05c 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,3 +1,4 @@
+#Mon Sep 19 11:01:26 CEST 2022
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
diff --git a/sdk-lib/Cargo.lock b/sdk-lib/Cargo.lock
index 145ef2c0..5d2e79b8 100644
--- a/sdk-lib/Cargo.lock
+++ b/sdk-lib/Cargo.lock
@@ -67,14 +67,14 @@ checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e"
[[package]]
name = "android_logger"
-version = "0.11.1"
+version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e9dd62f37dea550caf48c77591dc50bd1a378ce08855be1a0c42a97b7550fb"
+checksum = "2ec2333c185d826313162cee39d3fcc6a84ba08114a839bebf53b961e7e75773"
dependencies = [
"android_log-sys",
"env_logger",
+ "lazy_static",
"log",
- "once_cell",
]
[[package]]
@@ -550,9 +550,9 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "env_logger"
-version = "0.9.1"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272"
+checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"log",
"regex",
diff --git a/sdk-lib/Cargo.toml b/sdk-lib/Cargo.toml
index 1b1b7d0e..658436d9 100644
--- a/sdk-lib/Cargo.toml
+++ b/sdk-lib/Cargo.toml
@@ -10,7 +10,7 @@ publish = false
edition = "2018"
[dependencies]
-android_logger = "0.11"
+android_logger = "0.9"
failure = "0.1"
hdwallet = "0.3.1"
hdwallet-bitcoin = "0.3"
diff --git a/sdk-lib/build.gradle.kts b/sdk-lib/build.gradle.kts
index 197716b1..7dc060ff 100644
--- a/sdk-lib/build.gradle.kts
+++ b/sdk-lib/build.gradle.kts
@@ -119,6 +119,8 @@ signing {
}
android {
+ namespace = "cash.z.ecc.android.sdk"
+
useLibrary("android.test.runner")
defaultConfig {
@@ -278,11 +280,6 @@ dependencies {
// (com.google.guava:listenablefuture:1.0) per this recommendation from Chris Povirk, given guava's decision to
// split ListenableFuture away from Guava: https://groups.google.com/d/msg/guava-discuss/GghaKwusjcY/bCIAKfzOEwAJ
implementation(libs.guava)
- // OKIO is a transitive dependency used when writing param files to disk. Like GSON, this can be
- // replaced if needed. For compatibility, we match the library version used in grpc-okhttp:
- // https://github.com/grpc/grpc-java/blob/v1.37.x/build.gradle#L159
- implementation(libs.okio)
- implementation(libs.okhttp)
// Tests
testImplementation(libs.kotlin.reflect)
@@ -303,9 +300,6 @@ dependencies {
androidTestImplementation(libs.coroutines.okhttp)
androidTestImplementation(libs.kotlin.test)
androidTestImplementation(libs.kotlinx.coroutines.test)
- // used by 'ru.gildor.corutines.okhttp.await' (to make simple suspended requests) and breaks on versions higher
- // than 3.8.0
- androidTestImplementation(libs.okhttp)
// sample mnemonic plugin
androidTestImplementation(libs.zcashwalletplgn)
diff --git a/sdk-lib/proguard-consumer.txt b/sdk-lib/proguard-consumer.txt
index 65a66697..1d186adb 100644
--- a/sdk-lib/proguard-consumer.txt
+++ b/sdk-lib/proguard-consumer.txt
@@ -9,6 +9,8 @@
io.grpc.okhttp.OkHttpChannelBuilder sslSocketFactory(javax.net.ssl.SSLSocketFactory);
io.grpc.okhttp.OkHttpChannelBuilder transportExecutor(java.util.concurrent.Executor);
}
+# gRPC related - https://github.com/grpc/grpc-java/issues/6612
+-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
# Prevent OKHttp from causing warnings for consumers of the SDK
-dontwarn org.bouncycastle.jsse.BCSSLParameters
diff --git a/sdk-lib/src/androidTest/AndroidManifest.xml b/sdk-lib/src/androidTest/AndroidManifest.xml
index 6053bf43..a2a699c4 100644
--- a/sdk-lib/src/androidTest/AndroidManifest.xml
+++ b/sdk-lib/src/androidTest/AndroidManifest.xml
@@ -1,7 +1,6 @@
+ xmlns:android="http://schemas.android.com/apk/res/android">
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/annotation/MaintainedTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/annotation/MaintainedTest.kt
index 00832118..c461f44f 100644
--- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/annotation/MaintainedTest.kt
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/annotation/MaintainedTest.kt
@@ -18,7 +18,7 @@ enum class TestPurpose {
/**
* These tests require a running instance of [darksidewalletd](https://github.com/zcash/lightwalletd/blob/master/docs/darksidewalletd.md).
*/
- DARKSIDE,
+ DARKSIDE
}
/**
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/db/DatabaseCoordinatorTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/db/DatabaseCoordinatorTest.kt
index a1d7a446..2319be03 100644
--- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/db/DatabaseCoordinatorTest.kt
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/db/DatabaseCoordinatorTest.kt
@@ -3,6 +3,8 @@ package cash.z.ecc.android.sdk.db
import androidx.test.filters.FlakyTest
import androidx.test.filters.MediumTest
import androidx.test.filters.SmallTest
+import cash.z.ecc.android.sdk.internal.ext.createNewFileSuspend
+import cash.z.ecc.android.sdk.internal.ext.existsSuspend
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.test.getAppContext
import cash.z.ecc.fixture.DatabaseNameFixture
@@ -165,39 +167,39 @@ class DatabaseCoordinatorTest {
DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_CACHE_NAME)
)
- assertTrue(originalDbFile.exists())
- assertTrue(originalDbJournalFile.exists())
- assertTrue(originalDbWalFile.exists())
+ assertTrue(originalDbFile.existsSuspend())
+ assertTrue(originalDbJournalFile.existsSuspend())
+ assertTrue(originalDbWalFile.existsSuspend())
- assertFalse(expectedDbFile.exists())
- assertFalse(expectedDbJournalFile.exists())
- assertFalse(expectedDbWalFile.exists())
+ assertFalse(expectedDbFile.existsSuspend())
+ assertFalse(expectedDbJournalFile.existsSuspend())
+ assertFalse(expectedDbWalFile.existsSuspend())
dbCoordinator.cacheDbFile(
DatabaseNameFixture.TEST_DB_NETWORK,
DatabaseNameFixture.TEST_DB_ALIAS
).also { resultFile ->
- assertTrue(resultFile.exists())
+ assertTrue(resultFile.existsSuspend())
assertEquals(expectedDbFile.absolutePath, resultFile.absolutePath)
- assertTrue(expectedDbFile.exists())
- assertTrue(expectedDbJournalFile.exists())
- assertTrue(expectedDbWalFile.exists())
+ assertTrue(expectedDbFile.existsSuspend())
+ assertTrue(expectedDbJournalFile.existsSuspend())
+ assertTrue(expectedDbWalFile.existsSuspend())
- assertFalse(originalDbFile.exists())
- assertFalse(originalDbJournalFile.exists())
- assertFalse(originalDbWalFile.exists())
+ assertFalse(originalDbFile.existsSuspend())
+ assertFalse(originalDbJournalFile.existsSuspend())
+ assertFalse(originalDbWalFile.existsSuspend())
}
}
- private fun getEmptyFile(parent: File, fileName: String): File {
+ private suspend fun getEmptyFile(parent: File, fileName: String): File {
return File(parent, fileName).apply {
assertTrue(parentFile != null)
parentFile!!.mkdirs()
- assertTrue(parentFile!!.exists())
+ assertTrue(parentFile!!.existsSuspend())
- createNewFile()
- assertTrue(exists())
+ createNewFileSuspend()
+ assertTrue(existsSuspend())
}
}
@@ -226,14 +228,14 @@ class DatabaseCoordinatorTest {
fileName = DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_CACHE_NAME)
)
- assertTrue(dbFile.exists())
- assertTrue(dbJournalFile.exists())
- assertTrue(dbWalFile.exists())
+ assertTrue(dbFile.existsSuspend())
+ assertTrue(dbJournalFile.existsSuspend())
+ assertTrue(dbWalFile.existsSuspend())
dbCoordinator.deleteDatabases(DatabaseNameFixture.TEST_DB_NETWORK, DatabaseNameFixture.TEST_DB_ALIAS).also {
- assertFalse(dbFile.exists())
- assertFalse(dbJournalFile.exists())
- assertFalse(dbWalFile.exists())
+ assertFalse(dbFile.existsSuspend())
+ assertFalse(dbJournalFile.existsSuspend())
+ assertFalse(dbWalFile.existsSuspend())
}
}
}
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/FileExtTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/FileExtTest.kt
new file mode 100644
index 00000000..96ba5087
--- /dev/null
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/FileExtTest.kt
@@ -0,0 +1,55 @@
+package cash.z.ecc.android.sdk.ext
+
+import androidx.test.filters.SmallTest
+import cash.z.ecc.android.sdk.internal.ext.createNewFileSuspend
+import cash.z.ecc.android.sdk.internal.ext.existsSuspend
+import cash.z.ecc.android.sdk.internal.ext.getSha1Hash
+import cash.z.ecc.android.sdk.test.getAppContext
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import java.io.File
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+class FileExtTest {
+
+ private val testFile = File(getAppContext().filesDir, "test_file")
+
+ @Before
+ @After
+ fun remove_test_files() {
+ testFile.delete()
+ }
+
+ @Test
+ @SmallTest
+ fun check_empty_file_sha1_result() = runTest {
+ testFile.apply {
+ createNewFileSuspend()
+ assertTrue(existsSuspend())
+ assertEquals(
+ expected = "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+ actual = getSha1Hash(),
+ message = "SHA1 hashes are not the same."
+ )
+ }
+ }
+
+ @Test
+ @SmallTest
+ fun check_not_empty_file_sha1_result() = runTest {
+ testFile.apply {
+ createNewFileSuspend()
+ assertTrue(existsSuspend())
+ writeText("Hey! It compiles! Ship it!")
+ assertTrue(length() > 0)
+ assertEquals(
+ expected = "28756ec5d3a73f1e8993bdd46de74b79453ff21c",
+ actual = getSha1Hash(),
+ message = "SHA1 hashes are not the same."
+ )
+ }
+ }
+}
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/TestExtensions.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/TestExtensions.kt
index 4709743b..2cca93ab 100644
--- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/TestExtensions.kt
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/TestExtensions.kt
@@ -1,19 +1,19 @@
package cash.z.ecc.android.sdk.ext
-import okhttp3.OkHttpClient
-import okhttp3.Request
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
import org.json.JSONObject
-import ru.gildor.coroutines.okhttp.await
+import java.net.HttpURLConnection
+import java.net.URL
import kotlin.test.assertNotNull
object BlockExplorer {
suspend fun fetchLatestHeight(): Long {
- val client = OkHttpClient()
- val request = Request.Builder()
- .url("https://api.blockchair.com/zcash/blocks?limit=1")
- .build()
- val result = client.newCall(request).await()
- val body = result.body?.string()
+ val url = URL("https://api.blockchair.com/zcash/blocks?limit=1")
+ val connection = withContext(Dispatchers.IO) {
+ url.openConnection()
+ } as HttpURLConnection
+ val body = connection.inputStream.bufferedReader().readText()
assertNotNull(body, "Body can not be null.")
return JSONObject(body).getJSONArray("data").getJSONObject(0).getLong("id")
}
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/SanityTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/SanityTest.kt
index bf4f2864..3969a1c5 100644
--- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/SanityTest.kt
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/SanityTest.kt
@@ -59,9 +59,9 @@ class SanityTest(
)
)
assertTrue(
- "$name has invalid CacheDB params dir",
- rustBackend.pathParamsDir.endsWith(
- "cache/params"
+ "$name has invalid params dir",
+ rustBackend.saplingParamDir.path.endsWith(
+ "no_backup/co.electricoin.zcash"
)
)
}
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/SmokeTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/SmokeTest.kt
index 33465023..edf9e1d5 100644
--- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/SmokeTest.kt
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/SmokeTest.kt
@@ -46,7 +46,9 @@ class SmokeTest {
)
assertTrue(
"Invalid CacheDB params dir",
- rustBackend.pathParamsDir.endsWith("cache/params")
+ rustBackend.saplingParamDir.endsWith(
+ "no_backup/co.electricoin.zcash"
+ )
)
}
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolBasicTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolBasicTest.kt
new file mode 100644
index 00000000..fd564dc3
--- /dev/null
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolBasicTest.kt
@@ -0,0 +1,166 @@
+package cash.z.ecc.android.sdk.internal
+
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SmallTest
+import cash.z.ecc.android.sdk.exception.TransactionEncoderException
+import cash.z.ecc.android.sdk.internal.ext.getSha1Hash
+import cash.z.ecc.android.sdk.internal.ext.listFilesSuspend
+import cash.z.ecc.android.sdk.test.getAppContext
+import cash.z.ecc.fixture.SaplingParamToolFixture
+import cash.z.ecc.fixture.SaplingParamsFixture
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import java.io.File
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+class SaplingParamToolBasicTest {
+
+ @Before
+ fun setup() {
+ // clear the param files
+ runBlocking {
+ SaplingParamsFixture.clearAllFilesFromDirectory(SaplingParamsFixture.DESTINATION_DIRECTORY)
+ SaplingParamsFixture.clearAllFilesFromDirectory(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY)
+ }
+ }
+
+ @Test
+ @SmallTest
+ fun init_sapling_param_tool_test() = runTest {
+ val spendSaplingParams = SaplingParamsFixture.new()
+ val outputSaplingParams = SaplingParamsFixture.new(
+ SaplingParamsFixture.DESTINATION_DIRECTORY,
+ SaplingParamsFixture.OUTPUT_FILE_NAME,
+ SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE,
+ SaplingParamsFixture.OUTPUT_FILE_HASH
+ )
+
+ val saplingParamTool = SaplingParamTool(
+ SaplingParamToolProperties(
+ emptyList(),
+ SaplingParamsFixture
+ .DESTINATION_DIRECTORY,
+ SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY
+ )
+ )
+
+ // we inject params files to let the ensureParams() finish successfully without executing its extended operation
+ // like fetchParams, etc.
+ SaplingParamsFixture.createFile(File(spendSaplingParams.destinationDirectory, spendSaplingParams.fileName))
+ SaplingParamsFixture.createFile(File(outputSaplingParams.destinationDirectory, outputSaplingParams.fileName))
+
+ saplingParamTool.ensureParams(spendSaplingParams.destinationDirectory)
+ }
+
+ @Test
+ @SmallTest
+ fun init_and_get_params_destination_dir_test() = runTest {
+ val destDir = SaplingParamTool.new(getAppContext()).properties.paramsDirectory
+
+ assertNotNull(destDir)
+ assertEquals(
+ SaplingParamsFixture.DESTINATION_DIRECTORY.absolutePath,
+ destDir.absolutePath,
+ "Failed to validate init operation's destination directory."
+ )
+ }
+
+ @Test
+ @MediumTest
+ fun move_files_from_legacy_destination_test() = runTest {
+ SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY.mkdirs()
+ val spendFile = File(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, SaplingParamsFixture.SPEND_FILE_NAME)
+ val outputFile = File(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, SaplingParamsFixture.OUTPUT_FILE_NAME)
+
+ // now we inject params files to the legacy location to be "moved" to the preferred location
+ SaplingParamsFixture.createFile(spendFile)
+ SaplingParamsFixture.createFile(outputFile)
+
+ assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, spendFile))
+ assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, outputFile))
+ assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, spendFile))
+ assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, outputFile))
+
+ // we need to use modified array of sapling parameters to pass through the SHA1 hashes validation
+ val destDir = SaplingParamTool.initAndGetParamsDestinationDir(
+ SaplingParamToolFixture.new(
+ saplingParamsFiles = listOf(
+ SaplingParameters(
+ SaplingParamToolFixture.PARAMS_DIRECTORY,
+ SaplingParamTool.SPEND_PARAM_FILE_NAME,
+ SaplingParamTool.SPEND_PARAM_FILE_MAX_BYTES_SIZE,
+ spendFile.getSha1Hash()
+ ),
+ SaplingParameters(
+ SaplingParamToolFixture.PARAMS_DIRECTORY,
+ SaplingParamTool.OUTPUT_PARAM_FILE_NAME,
+ SaplingParamTool.OUTPUT_PARAM_FILE_MAX_BYTES_SIZE,
+ outputFile.getSha1Hash()
+ )
+ )
+ )
+ )
+
+ assertEquals(
+ SaplingParamsFixture.DESTINATION_DIRECTORY.absolutePath,
+ destDir.absolutePath
+ )
+
+ assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, spendFile))
+ assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, outputFile))
+ assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, spendFile))
+ assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, outputFile))
+ }
+
+ private suspend fun isFileInPlace(directory: File, file: File): Boolean {
+ return directory.listFilesSuspend()?.any { it.name == file.name } ?: false
+ }
+
+ @Test
+ @MediumTest
+ fun ensure_params_exception_thrown_test() = runTest {
+ val saplingParamTool = SaplingParamTool(
+ SaplingParamToolFixture.new(
+ saplingParamsFiles = listOf(
+ SaplingParameters(
+ SaplingParamToolFixture.PARAMS_DIRECTORY,
+ "test_file_1",
+ SaplingParamTool.SPEND_PARAM_FILE_MAX_BYTES_SIZE,
+ SaplingParamTool.SPEND_PARAM_FILE_SHA1_HASH
+ ),
+ SaplingParameters(
+ SaplingParamToolFixture.PARAMS_DIRECTORY,
+ "test_file_0",
+ SaplingParamTool.OUTPUT_PARAM_FILE_MAX_BYTES_SIZE,
+ SaplingParamTool.OUTPUT_PARAM_FILE_SHA1_HASH
+ )
+ )
+ )
+ )
+
+ // now we inject params files to the preferred location to pass through the check missing files phase
+ SaplingParamsFixture.createFile(
+ File(
+ saplingParamTool.properties.saplingParams[0].destinationDirectory,
+ saplingParamTool.properties.saplingParams[0].fileName
+ )
+ )
+ SaplingParamsFixture.createFile(
+ File(
+ saplingParamTool.properties.saplingParams[1].destinationDirectory,
+ saplingParamTool.properties.saplingParams[1].fileName
+ )
+ )
+
+ // the ensure params block should fail in validation phase, because we use a different params file names
+ assertFailsWith {
+ saplingParamTool.ensureParams(SaplingParamToolFixture.PARAMS_DIRECTORY)
+ }
+ }
+}
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolIntegrationTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolIntegrationTest.kt
new file mode 100644
index 00000000..0f7d5da3
--- /dev/null
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolIntegrationTest.kt
@@ -0,0 +1,261 @@
+package cash.z.ecc.android.sdk.internal
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import cash.z.ecc.android.sdk.exception.TransactionEncoderException
+import cash.z.ecc.android.sdk.internal.ext.listFilesSuspend
+import cash.z.ecc.android.sdk.test.getAppContext
+import cash.z.ecc.fixture.SaplingParamsFixture
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+import kotlin.test.assertContains
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+// TODO [#650]: https://github.com/zcash/zcash-android-wallet-sdk/issues/650
+// TODO [#650]: Move integration tests to separate module
+@Ignore(
+ "These tests need to be refactored to a separate test module. They cause SSLHandshakeException: Chain " +
+ "validation failed on CI."
+)
+@RunWith(AndroidJUnit4::class)
+class SaplingParamToolIntegrationTest {
+
+ private val spendSaplingParams = SaplingParamsFixture.new()
+
+ private val outputSaplingParams = SaplingParamsFixture.new(
+ SaplingParamsFixture.DESTINATION_DIRECTORY,
+ SaplingParamsFixture.OUTPUT_FILE_NAME,
+ SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE,
+ SaplingParamsFixture.OUTPUT_FILE_HASH
+ )
+
+ @Before
+ fun setup() {
+ // clear and prepare the param files
+ runBlocking {
+ SaplingParamsFixture.clearAllFilesFromDirectory(SaplingParamsFixture.DESTINATION_DIRECTORY)
+ SaplingParamsFixture.clearAllFilesFromDirectory(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY)
+ }
+ }
+
+ @Test
+ @LargeTest
+ fun test_files_exists() = runBlocking {
+ val saplingParamTool = SaplingParamTool.new(getAppContext())
+
+ saplingParamTool.fetchParams(spendSaplingParams)
+ saplingParamTool.fetchParams(outputSaplingParams)
+
+ val result = saplingParamTool.validate(
+ SaplingParamsFixture.DESTINATION_DIRECTORY
+ )
+
+ assertTrue(result)
+ }
+
+ @Test
+ @LargeTest
+ fun output_file_exists() = runBlocking {
+ val saplingParamTool = SaplingParamTool.new(getAppContext())
+
+ saplingParamTool.fetchParams(spendSaplingParams)
+ File(spendSaplingParams.destinationDirectory, spendSaplingParams.fileName).delete()
+
+ val result = saplingParamTool.validate(spendSaplingParams.destinationDirectory)
+
+ assertFalse(result, "Validation should fail as the spend param file is missing.")
+ }
+
+ @Test
+ @LargeTest
+ fun spend_file_exists() = runBlocking {
+ val saplingParamTool = SaplingParamTool.new(getAppContext())
+
+ saplingParamTool.fetchParams(outputSaplingParams)
+ File(outputSaplingParams.destinationDirectory, outputSaplingParams.fileName).delete()
+
+ val result = saplingParamTool.validate(outputSaplingParams.destinationDirectory)
+
+ assertFalse(result, "Validation should fail as the output param file is missing.")
+ }
+
+ @Test
+ @LargeTest
+ fun check_all_files_fetched() = runBlocking {
+ val expectedSpendFile = File(
+ SaplingParamsFixture.DESTINATION_DIRECTORY,
+ SaplingParamsFixture.SPEND_FILE_NAME
+ )
+ val expectedOutputFile = File(
+ SaplingParamsFixture.DESTINATION_DIRECTORY,
+ SaplingParamsFixture.OUTPUT_FILE_NAME
+ )
+
+ val saplingParamTool = SaplingParamTool.new(getAppContext())
+
+ saplingParamTool.ensureParams(SaplingParamsFixture.DESTINATION_DIRECTORY)
+
+ val actualFiles = SaplingParamsFixture.DESTINATION_DIRECTORY.listFilesSuspend()
+ assertNotNull(actualFiles)
+
+ assertContains(actualFiles, expectedSpendFile)
+
+ assertContains(actualFiles, expectedOutputFile)
+ }
+
+ @Test
+ @LargeTest
+ fun check_correct_spend_param_file_size() = runBlocking {
+ val saplingParamTool = SaplingParamTool.new(getAppContext())
+
+ saplingParamTool.fetchParams(spendSaplingParams)
+
+ val expectedSpendFile = File(
+ SaplingParamsFixture.DESTINATION_DIRECTORY,
+ SaplingParamsFixture.SPEND_FILE_NAME
+ )
+
+ assertTrue(expectedSpendFile.length() < SaplingParamsFixture.SPEND_FILE_MAX_SIZE)
+ assertFalse(expectedSpendFile.length() < SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE)
+ }
+
+ @Test
+ @LargeTest
+ fun check_correct_output_param_file_size() = runBlocking {
+ val saplingParamTool = SaplingParamTool.new(getAppContext())
+
+ saplingParamTool.fetchParams(outputSaplingParams)
+
+ val expectedOutputFile = File(
+ SaplingParamsFixture.DESTINATION_DIRECTORY,
+ SaplingParamsFixture.OUTPUT_FILE_NAME
+ )
+
+ assertTrue(expectedOutputFile.length() < SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE)
+ assertFalse(expectedOutputFile.length() > SaplingParamsFixture.SPEND_FILE_MAX_SIZE)
+ }
+
+ @Test
+ @LargeTest
+ fun fetch_params_uninitialized_test() = runTest {
+ val saplingParamTool = SaplingParamTool.new(getAppContext())
+
+ SaplingParamsFixture.DESTINATION_DIRECTORY.delete()
+
+ assertFailsWith {
+ saplingParamTool.fetchParams(spendSaplingParams)
+ }
+
+ assertFalse(saplingParamTool.validate(SaplingParamsFixture.DESTINATION_DIRECTORY))
+ }
+
+ @Test
+ @LargeTest
+ fun fetch_params_incorrect_hash_test() = runTest {
+ val saplingParamTool = SaplingParamTool.new(getAppContext())
+
+ assertFailsWith {
+ saplingParamTool.fetchParams(
+ SaplingParamsFixture.new(
+ fileName = SaplingParamsFixture.OUTPUT_FILE_NAME,
+ fileMaxSize = SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE,
+ fileHash = "test_hash_which_causes_failure_of_validation"
+ )
+ )
+ }
+
+ assertFalse(saplingParamTool.validate(SaplingParamsFixture.DESTINATION_DIRECTORY))
+ }
+
+ @Test
+ @LargeTest
+ fun fetch_params_incorrect_max_file_size_test() = runTest {
+ val saplingParamTool = SaplingParamTool.new(getAppContext())
+
+ assertFailsWith {
+ saplingParamTool.fetchParams(
+ SaplingParamsFixture.new(
+ fileName = SaplingParamsFixture.OUTPUT_FILE_NAME,
+ fileHash = SaplingParamsFixture.OUTPUT_FILE_HASH,
+ fileMaxSize = 0
+ )
+ )
+ }
+
+ assertFalse(saplingParamTool.validate(SaplingParamsFixture.DESTINATION_DIRECTORY))
+ }
+
+ @Test
+ @LargeTest
+ fun fetch_param_manual_recover_test_from_fetch_params_exception() = runTest {
+ val saplingParamTool = SaplingParamTool.new(getAppContext())
+
+ SaplingParamsFixture.DESTINATION_DIRECTORY.delete() // will cause the FetchParamsException
+
+ val exception = assertFailsWith {
+ saplingParamTool.fetchParams(outputSaplingParams)
+ }
+
+ assertEquals(outputSaplingParams.fileName, exception.parameters.fileName)
+
+ val expectedOutputFile = File(
+ SaplingParamsFixture.DESTINATION_DIRECTORY,
+ SaplingParamsFixture.OUTPUT_FILE_NAME
+ )
+
+ assertFalse(expectedOutputFile.exists())
+
+ // to set up the missing deleted folder
+ SaplingParamTool.initAndGetParamsDestinationDir(saplingParamTool.properties)
+
+ // re-try with parameters returned by the exception
+ saplingParamTool.fetchParams(exception.parameters)
+
+ assertTrue(expectedOutputFile.exists())
+ }
+
+ @Test
+ @LargeTest
+ fun fetch_param_manual_recover_test_from_validate_params_exception() = runTest {
+ val saplingParamTool = SaplingParamTool.new(getAppContext())
+
+ val expectedOutputFile = File(
+ SaplingParamsFixture.DESTINATION_DIRECTORY,
+ SaplingParamsFixture.OUTPUT_FILE_NAME
+ )
+
+ val outputSaplingParams = SaplingParamsFixture.new(
+ SaplingParamsFixture.DESTINATION_DIRECTORY,
+ SaplingParamsFixture.OUTPUT_FILE_NAME,
+ SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE,
+ SaplingParamsFixture.SPEND_FILE_HASH // will cause the ValidateParamsException
+ )
+
+ val exception = assertFailsWith {
+ saplingParamTool.fetchParams(outputSaplingParams)
+ }
+
+ assertFalse(expectedOutputFile.exists())
+
+ val fixedOutputSaplingParams = SaplingParamsFixture.new(
+ destinationDirectoryPath = exception.parameters.destinationDirectory,
+ fileName = exception.parameters.fileName,
+ fileMaxSize = exception.parameters.fileMaxSizeBytes,
+ fileHash = SaplingParamsFixture.OUTPUT_FILE_HASH // fixed file hash
+ )
+
+ // re-try with fixed parameters
+ saplingParamTool.fetchParams(fixedOutputSaplingParams)
+
+ assertTrue(expectedOutputFile.exists())
+ }
+}
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolTest.kt
deleted file mode 100644
index d3d479ef..00000000
--- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/internal/SaplingParamToolTest.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package cash.z.ecc.android.sdk.internal
-
-import android.content.Context
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import cash.z.ecc.android.sdk.ext.ZcashSdk
-import kotlinx.coroutines.runBlocking
-import org.junit.Assert
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import java.io.File
-
-@RunWith(AndroidJUnit4::class)
-@Ignore(
- "These tests need to be refactored to a separate test module. They cause SSLHandshakeException: Chain " +
- "validation failed on CI"
-)
-class SaplingParamToolTest {
-
- val context: Context = InstrumentationRegistry.getInstrumentation().context
-
- val cacheDir = "${context.cacheDir.absolutePath}/params"
-
- @Before
- fun setup() {
- // clear the param files
- runBlocking { SaplingParamTool.clear(cacheDir) }
- }
-
- @Test
- @Ignore("This test is broken")
- fun testFilesExists() = runBlocking {
- // Given
- SaplingParamTool.fetchParams(cacheDir)
-
- // When
- val result = SaplingParamTool.validate(cacheDir)
-
- // Then
- Assert.assertFalse(result)
- }
-
- @Test
- fun output_file_exists() = runBlocking {
- // Given
- SaplingParamTool.fetchParams(cacheDir)
- File(cacheDir, ZcashSdk.OUTPUT_PARAM_FILE_NAME).delete()
-
- // When
- val result = SaplingParamTool.validate(cacheDir)
-
- // Then
- Assert.assertFalse("Validation should fail when the spend params are missing", result)
- }
-
- @Test
- fun param_file_exists() = runBlocking {
- // Given
- SaplingParamTool.fetchParams(cacheDir)
- File(cacheDir, ZcashSdk.SPEND_PARAM_FILE_NAME).delete()
-
- // When
- val result = SaplingParamTool.validate(cacheDir)
-
- // Then
- Assert.assertFalse("Validation should fail when the spend params are missing", result)
- }
-
- @Test
- fun testInsufficientDeviceStorage() = runBlocking {
- // Given
- SaplingParamTool.fetchParams(cacheDir)
-
- Assert.assertFalse("insufficient storage", false)
- }
-
- @Test
- fun testSufficientDeviceStorageForOnlyOneFile() = runBlocking {
- SaplingParamTool.fetchParams(cacheDir)
-
- Assert.assertFalse("insufficient storage", false)
- }
-}
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/BranchIdTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/BranchIdTest.kt
index 9e3d7369..808cbc2a 100644
--- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/BranchIdTest.kt
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/jni/BranchIdTest.kt
@@ -9,6 +9,7 @@ import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+import java.io.File
/**
* This test is intended to run to make sure that branch ID logic works across all target devices.
@@ -45,8 +46,24 @@ class BranchIdTest internal constructor(
// 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 = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Testnet, ZcashNetwork.Testnet.saplingActivationHeight) }
- val mainnetBackend = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Mainnet, ZcashNetwork.Mainnet.saplingActivationHeight) }
+ val testnetBackend = runBlocking {
+ RustBackend.init(
+ File(""),
+ File(""),
+ File(""),
+ ZcashNetwork.Testnet,
+ ZcashNetwork.Testnet.saplingActivationHeight
+ )
+ }
+ val mainnetBackend = runBlocking {
+ RustBackend.init(
+ File(""),
+ File(""),
+ File(""),
+ ZcashNetwork.Mainnet,
+ ZcashNetwork.Mainnet.saplingActivationHeight
+ )
+ }
return listOf(
// Mainnet Cases
arrayOf("Sapling", BlockHeight.new(ZcashNetwork.Mainnet, 419_200), 1991772603L, "76b809bb", mainnetBackend),
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/test/Global.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/test/Global.kt
index cd859de7..10ba6fd1 100644
--- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/test/Global.kt
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/test/Global.kt
@@ -3,9 +3,23 @@ package cash.z.ecc.android.sdk.test
import android.content.Context
import androidx.annotation.StringRes
import androidx.test.core.app.ApplicationProvider
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.emitAll
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import kotlin.test.assertNotNull
fun getAppContext(): Context = ApplicationProvider.getApplicationContext()
fun getStringResource(@StringRes resId: Int) = getAppContext().getString(resId)
-fun getStringResourceWithArgs(@StringRes resId: Int, vararg formatArgs: String) = getAppContext().getString(resId, *formatArgs)
+fun getStringResourceWithArgs(@StringRes resId: Int, vararg formatArgs: String) =
+ getAppContext().getString(resId, *formatArgs)
+
+fun readFileLinesInFlow(filePathName: String) = flow {
+ val testFile = javaClass.getResourceAsStream(filePathName)
+ assertNotNull(testFile, "Test file read failure.")
+
+ emitAll(testFile.bufferedReader().lineSequence().asFlow())
+}.flowOn(Dispatchers.IO)
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt
index a4c91da1..58b8c666 100644
--- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt
@@ -1,25 +1,19 @@
package cash.z.ecc.android.sdk.util
-import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.ZcashNetwork
+import cash.z.ecc.android.sdk.test.readFileLinesInFlow
import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
-import okio.buffer
-import okio.source
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
-import java.io.IOException
@ExperimentalCoroutinesApi
class AddressGeneratorUtil {
- private val context = InstrumentationRegistry.getInstrumentation().context
-
private val mnemonics = SimpleMnemonics()
@Test
@@ -33,7 +27,7 @@ class AddressGeneratorUtil {
@Test
fun generateAddresses() = runBlocking {
- readLines()
+ readFileLinesInFlow("/utils/seeds.txt")
.map { seedPhrase ->
mnemonics.toSeed(seedPhrase.toCharArray())
}.map { seed ->
@@ -43,16 +37,4 @@ class AddressGeneratorUtil {
assertTrue(address.startsWith("u1"))
}
}
-
- @Throws(IOException::class)
- fun readLines() = flow {
- val seedFile = javaClass.getResourceAsStream("/utils/seeds.txt")!!
- seedFile.source().buffer().use { source ->
- var line: String? = source.readUtf8Line()
- while (line != null) {
- emit(line)
- line = source.readUtf8Line()
- }
- }
- }
}
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/BalancePrinterUtil.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/BalancePrinterUtil.kt
index fa5e2828..94aa6931 100644
--- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/BalancePrinterUtil.kt
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/BalancePrinterUtil.kt
@@ -11,15 +11,14 @@ 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.test.readFileLinesInFlow
import cash.z.ecc.android.sdk.tool.CheckpointTool
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
-import java.io.IOException
/**
* A tool for checking transactions since the given birthday and printing balances. This was useful for the Zcon1 app to
@@ -74,7 +73,7 @@ class BalancePrinterUtil {
@Test
@Ignore("This test is broken")
fun printBalances() = runBlocking {
- readLines()
+ readFileLinesInFlow("/utils/seeds.txt")
.map { seedPhrase ->
twig("checking balance for: $seedPhrase")
mnemonics.toSeed(seedPhrase.toCharArray())
@@ -141,9 +140,6 @@ class BalancePrinterUtil {
// assertEquals("foo", "bar")
// }
- @Throws(IOException::class)
- fun readLines() = javaClass.getResourceAsStream("/utils/seeds.txt")!!.bufferedReader().lineSequence().asFlow()
-
// private fun initWallet(seed: String): Wallet {
// val spendingKeyProvider = Delegates.notNull()
// return Wallet(
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt
index d11e21ff..b660fc87 100644
--- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt
@@ -213,7 +213,7 @@ class TestWallet(
1_330_190
),
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
- ),
+ )
;
}
}
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/DatabasePathFixture.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/DatabasePathFixture.kt
index ba9972f1..247c8938 100644
--- a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/DatabasePathFixture.kt
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/DatabasePathFixture.kt
@@ -1,6 +1,7 @@
package cash.z.ecc.fixture
import cash.z.ecc.android.sdk.internal.Files
+import cash.z.ecc.android.sdk.internal.ext.existsSuspend
import cash.z.ecc.android.sdk.internal.ext.getDatabasePathSuspend
import cash.z.ecc.android.sdk.internal.ext.getNoBackupFilesDirCompat
import cash.z.ecc.android.sdk.test.getAppContext
@@ -16,7 +17,7 @@ object DatabasePathFixture {
assert(parentFile != null) { "Failed to create database folder." }
parentFile!!.mkdirs()
- assert(parentFile.exists()) { "Failed to check database folder." }
+ assert(parentFile.existsSuspend()) { "Failed to check database folder." }
parentFile.absolutePath
}
}
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/SaplingParamToolFixture.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/SaplingParamToolFixture.kt
new file mode 100644
index 00000000..b0ce90d6
--- /dev/null
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/SaplingParamToolFixture.kt
@@ -0,0 +1,36 @@
+package cash.z.ecc.fixture
+
+import cash.z.ecc.android.sdk.internal.SaplingParamTool
+import cash.z.ecc.android.sdk.internal.SaplingParamToolProperties
+import cash.z.ecc.android.sdk.internal.SaplingParameters
+import java.io.File
+
+object SaplingParamToolFixture {
+
+ internal val PARAMS_DIRECTORY = SaplingParamsFixture.DESTINATION_DIRECTORY
+ internal val PARAMS_LEGACY_DIRECTORY = SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY
+ internal val SAPLING_PARAMS_FILES = listOf(
+ SaplingParameters(
+ PARAMS_DIRECTORY,
+ SaplingParamTool.SPEND_PARAM_FILE_NAME,
+ SaplingParamTool.SPEND_PARAM_FILE_MAX_BYTES_SIZE,
+ SaplingParamTool.SPEND_PARAM_FILE_SHA1_HASH
+ ),
+ SaplingParameters(
+ PARAMS_DIRECTORY,
+ SaplingParamTool.OUTPUT_PARAM_FILE_NAME,
+ SaplingParamTool.OUTPUT_PARAM_FILE_MAX_BYTES_SIZE,
+ SaplingParamTool.OUTPUT_PARAM_FILE_SHA1_HASH
+ )
+ )
+
+ internal fun new(
+ saplingParamsFiles: List = SAPLING_PARAMS_FILES,
+ paramsDirectory: File = PARAMS_DIRECTORY,
+ paramsLegacyDirectory: File = PARAMS_LEGACY_DIRECTORY
+ ) = SaplingParamToolProperties(
+ saplingParams = saplingParamsFiles,
+ paramsDirectory = paramsDirectory,
+ paramsLegacyDirectory = paramsLegacyDirectory
+ )
+}
diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/SaplingParamsFixture.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/SaplingParamsFixture.kt
new file mode 100644
index 00000000..961ceb46
--- /dev/null
+++ b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/SaplingParamsFixture.kt
@@ -0,0 +1,58 @@
+package cash.z.ecc.fixture
+
+import cash.z.ecc.android.sdk.internal.Files
+import cash.z.ecc.android.sdk.internal.SaplingParamTool
+import cash.z.ecc.android.sdk.internal.SaplingParameters
+import cash.z.ecc.android.sdk.internal.ext.createNewFileSuspend
+import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
+import cash.z.ecc.android.sdk.internal.ext.existsSuspend
+import cash.z.ecc.android.sdk.internal.ext.listFilesSuspend
+import cash.z.ecc.android.sdk.test.getAppContext
+import kotlinx.coroutines.runBlocking
+import java.io.File
+
+object SaplingParamsFixture {
+
+ internal val DESTINATION_DIRECTORY_LEGACY: File = File(
+ getAppContext().cacheDir,
+ SaplingParamTool.SAPLING_PARAMS_LEGACY_SUBDIRECTORY
+ )
+
+ internal val DESTINATION_DIRECTORY: File
+ get() = runBlocking {
+ Files.getZcashNoBackupSubdirectory(getAppContext())
+ }
+
+ internal const val SPEND_FILE_NAME = SaplingParamTool.SPEND_PARAM_FILE_NAME
+ internal const val SPEND_FILE_MAX_SIZE = SaplingParamTool.SPEND_PARAM_FILE_MAX_BYTES_SIZE
+ internal const val SPEND_FILE_HASH = SaplingParamTool.SPEND_PARAM_FILE_SHA1_HASH
+
+ internal const val OUTPUT_FILE_NAME = SaplingParamTool.OUTPUT_PARAM_FILE_NAME
+ internal const val OUTPUT_FILE_MAX_SIZE = SaplingParamTool.OUTPUT_PARAM_FILE_MAX_BYTES_SIZE
+ internal const val OUTPUT_FILE_HASH = SaplingParamTool.OUTPUT_PARAM_FILE_SHA1_HASH
+
+ internal fun new(
+ destinationDirectoryPath: File = DESTINATION_DIRECTORY,
+ fileName: String = SPEND_FILE_NAME,
+ fileMaxSize: Long = SPEND_FILE_MAX_SIZE,
+ fileHash: String = SPEND_FILE_HASH
+ ) = SaplingParameters(
+ destinationDirectory = destinationDirectoryPath,
+ fileName = fileName,
+ fileMaxSizeBytes = fileMaxSize,
+ fileHash = fileHash
+ )
+
+ internal suspend fun createFile(paramsFile: File) {
+ paramsFile.createNewFileSuspend()
+ }
+
+ internal suspend fun clearAllFilesFromDirectory(destinationDir: File) {
+ if (!destinationDir.existsSuspend()) {
+ return
+ }
+ for (file in destinationDir.listFilesSuspend()!!) {
+ file.deleteSuspend()
+ }
+ }
+}
diff --git a/sdk-lib/src/main/AndroidManifest.xml b/sdk-lib/src/main/AndroidManifest.xml
index 3ef6dabc..fdfe51af 100644
--- a/sdk-lib/src/main/AndroidManifest.xml
+++ b/sdk-lib/src/main/AndroidManifest.xml
@@ -1,5 +1,4 @@
-
+
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1767500.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1767500.json
new file mode 100644
index 00000000..248c4fcf
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1767500.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1767500",
+ "hash": "000000000062aefe96e5bdd113d850b89646e8ab600ddcbd57c2c3cca9ed9cdb",
+ "time": 1660081334,
+ "saplingTree": "017cc5d6752832ddd99fa1bd6e3c7f2113986ad5d5be3068bfe14243212e717257001800000157aab5fa6cc81d5a4ca70c0cb0c2bac34804dc237dc817418d45df04ac46ac3f01f5edbbffb54d8c60445812ce336ba434bb424f6ae71e1b3a84c522d345a973180001c5e144ba6cda0551733ebc2bbe098ab386b45c5a93012a3e5b756beca22c6c3e01dbe2ae2d51059351deaab0cc2bc8d56e71fefeee47491749b9cade247f576b0f01c75ccd49fcefece91dad36bf2c57808e290f33209375bd75ed26dcdfda27e1650177925c91eb349eeaa311898dae3c2832fece99426f877e73058780550acc0331000161343f9954a5082d850e2670038205296798f6080822deb44fb4c280f55f974501fc08c52c0f49521e463a91c9a3eb605b811b3916e82abfa6e2e004662cbce33000000191de3df92b233b4bc9ca3e9d5cd49e832eef5178c6ebaf3e0b1a1c5f748fa237000001f6d2aae532a8ef603f8d0e3484b9013e00f525afcc5c725aaaa531ca97514c030001a9ebb083071b5c81251c1c09b08606288769a32e79967db3fa8138075aa5825d000115c91e5fc9fd96cbf12a77777f1781cce55113f548b87f067f2d7d1af81dfa56018ed21e6b0098dd6f0902efde81f9d5da8d7068e8dcf01ab1c015f8f5936c2d5f017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
+ "orchardTree": "011354cb2375c9f06e3c3a1e21549f0d582b5b24b562880c3e12d41751d0878c3a0178e9447cb89b46eedb06346450cfca4680cf6e2d5f71aaea92cfbd0c4ee8ef181f000000011a0bb2dae9ccd72fcafab242a08d3e16a31f0699d440cef290f0998c378f003a011250096a18c4e0ac40bf72bedf005b97e24096b473d6f4422335fd67e485910c01e6338965eb59f946b5bce375e43414c829116025645d4fb50b49c3fa5f65fd2f00014735a76fb3ca0190df1e1b2a64ff0dd2b875d8d972d1e49333b7720e1d80233c01725410fb3633b5a3b484f386f31ad0a8372e744b8ebad06f0b267e8181ee3d1e00000001be342cefff27be290a0c6688149ec5078a5bb559e590773823e486c6d7a10111015a78bd51e8c203d1d4960791f29412b0ebd8e918a5a34203dac112fc5a07750e010c29539431c68b879515fe688b31c4c5c8479b5bff69c50207314b769450a32e01f2fb309ae3c4de7584b0c2974bf0b92af35ff03420b8e210009227648eef393d000001a111bede6c68ba8795c99c17b615d062944718ff12e5d890a90128409860982101374feb2041bfd423c6cc3e064ee2b4705748a082836d39dd723515357fb06e300000000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1770000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1770000.json
new file mode 100644
index 00000000..f75a24b3
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1770000.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1770000",
+ "hash": "0000000000c72df21cd00410b6ac1eabe0df652bd1de1acc8f7de0300ad0c579",
+ "time": 1660269507,
+ "saplingTree": "01a8a916c0922f6255920368229c9807bd08968d344e4cac1562f61d67acfc1712013b8c478aeb325ce90904248fa79e20088787b4c2f5c8437c4a05368a8e5286131801ac59d1048b60be971ae94994637d43c9c2b5f0096e3010388c15564bacc23d0a019c21f5c2045113d187f254cac44d84eb90e6a2fd13bdeb048ded1c7f6a23a57101f84c590ef2d748f73429528a1fa8c2631153ec0c9826fbf7e9250a3afe967d4f018a2e6f62c3adc2eb82c218c405456c736c0693bc15367db9f8be0098414345190000017645afc760c2f4677e7d0e6a06ec930b414092367c1682683bd98275a57f194e00000000000000017b5ec5fcc4428da2d6818a0b1af313c60954ba660e2e3cda6f450099eb5b221f0001ef818bb1081751e68503a93549f307ffc9c315434df34fcf02a015902b46cc180001e21ed183272df1ff7b85b11dba8cdbc6a4658e6590af95813267526b37026e38000154e243e3e5358aad170a344b945fecf54c40891e8bb5b5d3bfc4630543de7b4c0115c91e5fc9fd96cbf12a77777f1781cce55113f548b87f067f2d7d1af81dfa56018ed21e6b0098dd6f0902efde81f9d5da8d7068e8dcf01ab1c015f8f5936c2d5f017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
+ "orchardTree": "012b3f0b7513e55d38172c1823c882c72d905ff9202441a232abc52724539f680c017d53c8439f36df116448eadc2bc91d55a0f312a738dfc2137bde4776f9b6a4361f000000018b507a097c3386b3feccd3d9c51821906408e8b83ccbf13100783b0e837aa909000177c4e6e1b75416e4d92aab042e2725e050a5f076b11f2f4d2af70882202f33390001ba5d410df62cf48f7db09537333dc58577284c2715b8410ef15c363a33b7353700016c7e3e4e5cdb142564d4e4cf19d6daab524ec8bd50e53067c526918e906f171601b68907c6a34bc6281e7a5c7d3af957c02b21f39d6f0cf328dcc9d7a9456d8d3b0001e343584412ad061010f7a51650bbedc1158a89e38a94be46f0e5e1951caea61400000001359f584dee78fa97c62744b858526df6e936126f0abcf399e5649895e5d4160101a93fb5f55a0ed85c3dfdd84cafbe185bf7cdcc39df46f20b9e31c023f24f560a01a111bede6c68ba8795c99c17b615d062944718ff12e5d890a90128409860982101374feb2041bfd423c6cc3e064ee2b4705748a082836d39dd723515357fb06e300000000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1772500.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1772500.json
new file mode 100644
index 00000000..cecf93de
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1772500.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1772500",
+ "hash": "0000000001109c6049218a78d26eb16b0d5e6a46aa1918b39e8adb9c8d892320",
+ "time": 1660458773,
+ "saplingTree": "019ad7b4358d62778021a4080cf37a4217e7e11d2d81b38d3683cc2f8729a8aa3401d2559936f9aa3772ad890675b4c7df6c5a0a796274a172c303e74980c335bf5b1801e2b9ff9e93c5fdd152ea2402a01ee5e9fadca97a37e7843da1cca8703e5d174f01c0107854df305aec5fe7dc0fca338428d843445b7bbc93b782d44e502919530e0000000001715e39e97a1895488da1e615b3ecccfddbc1505376cb74e4a6cff974f4640e15000001c105403889f34c2a05928510ad2f88023b8823c92567ba29e274c596340f516f01bc4a6849c7428c63b06883f7d7c887f010ed34d4eec1b6ccc5cc6f1c9a5eac4401af80584abf7e1d3ce026c3c0adc9d94e9542f708050625a68f2d9dabdf4d1969012a4851fa8b991aeaa5546a9a9705b382d2045da32f86b6f95b5f9f0187b8506301b63830dd5a6d176498d96e4b6329715386e24697025a4148401541d0d745d70900000001d6815a159595ce4f0b7dabb78174abef61c557fbf5179b0b06ac629f5972c26e01b6feee0caca8f28b04cab9a5b7eda707f289719fd2b91c809f603ad23924c56401c0a384a2c6a8bdc8ec47b715670aa3bd71601872d593ad86e2f13ef6c65cbd490154e243e3e5358aad170a344b945fecf54c40891e8bb5b5d3bfc4630543de7b4c0115c91e5fc9fd96cbf12a77777f1781cce55113f548b87f067f2d7d1af81dfa56018ed21e6b0098dd6f0902efde81f9d5da8d7068e8dcf01ab1c015f8f5936c2d5f017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
+ "orchardTree": "012de2bdc6851434bb9bd9356387a3ef5e2391cc04ee303c5a9c3653e60c26ee3401a6ef18049293bb3cdb59c4d461a9612811b8ae2bca828884131f37603e456b0d1f00000142caeb8a953a2a3e03630aca15b02619af2a10a70de045bbb17fed9254bf6c16000000000001591d52db18cb890feda16bf333c1dfd6e82d76cf8972330ceac6483778eb5d2f00011aa73e3b53df7e1ee91b1ef57f87ac253d680e07b63f72dd11d6922f4aae700f000147cfe08c6d5288825731fb3a99336232656f2cdc8f31bff4ee54e693440e7d3900000001015358ef9ae69af71a7602e351a1184e95854b3936834c7aebaa3774bc648d2701cd7575644cb25d5a1c7edcf4e3552520f2da1b2c98fa04bdf419175fc4a1c708000001f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1775000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1775000.json
new file mode 100644
index 00000000..f9b8baa2
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1775000.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1775000",
+ "hash": "000000000019cdcd93e8a0704c40cd74e65424e4bfaaa2a21227db81355ccb46",
+ "time": 1660646308,
+ "saplingTree": "01597a94eed997b611665ea28eb0fbebf872dc4187b81baf2d7b061d9498e4be07001901289049f3d2fb297ec300241c8ca66400d8cb214272da11c7dddbf249a83b363200019448787f294385a1cf41c26b5ecc3a9f2b552d85dd959d71ad8fb79ab82f315e0000015692b62820a0affe568750d52d557ae99781acf19545f95404b3d7122e00e50d014c13e6f6cd1938716b4870921418d553fbc13ad5e174c720556e43eeedff992b01d5af984301be600c7cf840dc1fbbcdedb2d2e801813bb68a01be00c6822d5a6a017e978b347c274b38ee016285dd14132e83d59786019d1a46ce0163771dfdce0c000000017b24ec22b8e69bfebe3a24693645c8905f2c3756dd67d646d4266c5624ce29520001887d900f0f1c22d239b925d77200c12a7a637dfc7723f08aa34c0808cd1740200001e9358e668fd2780e13975fa8d46cd6ea762252a2ce849e2c82affb1c58edcb190000013eca32c92486829a47625ea4acb38897025abb7af0d1aba2d12c9e828a8d0a4f000000000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "0182a273f144dba41ed805d348a8eeae5692b737863da75ac3384af983d54fd109001f01f72799b4c4c3ec3048b5c724b0edf553fdac716a6f2e771b8e40be6f9ece050301eec827aaef0952edafa106766d905c03b29a58b8f502b579e18d6b8a36b9a2120001997dd960a2474458cc8d914bf46232ec351eb5b3ff614e455d0fe6cf1c0d961f01726cc90e9a3d35324d807825bc4eceb7c976735bf8d5072f24f916f11231aa2901e4d4b64f108c937551964335b41b8a6e43bca7b2f2c468f86bb27a938ffb912001395fc86db243265b7a2722b3a49c1ac5449d671980bfaaebd1f8b26d4324451f0001056bee64e920ba6e8604fa32b294591abb40b5fc35b9b6f257b3b465fce371270001ea6a4ce4349b49c0b9788bcf94d27cb8c95f8f5a640919110387cd66f063e113017d6446e58ba6f7f8e7f60594b91bb67739986c89c670ad3996205ca9df7eac1100000001188cb8c8ad6ec4d5a9f90689123894bc1d021fb63d53347246ef21657c04f3110000014c58c6ffa8b0fdd89c604006318b7901020edcaca498e3afd81af7579dafcd340001f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1777500.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1777500.json
new file mode 100644
index 00000000..64dbcbfd
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1777500.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1777500",
+ "hash": "000000000114eb90f65670123d685d4e1ac4208dcc39a9dc1111060789a2c418",
+ "time": 1660835117,
+ "saplingTree": "0179c12f2782a76e421834ecfcd800a5cbd1edc79663d3c58d25d14200c60a2b050019000000000000000001091c24ca6d7180e15a22a46f9fa2d0a90783ce8e8bf4346097b36c469f7859220113d4335546307bb7558b2bfa8a309abb341da3f88acfc3b7d0523519a993662701d99f7f7123501008b4b145d772c66240aa9bdfe1fadd8cd420a3f159126950350185b933c8fc56ff06180fc5d2dbaf7234d615c724b1dbcdd4130ddff0ac732e6e000000019fe9488e3027bf007c951a3706ac7422d242c3ece25c20d5fd8254fdc77d883c000177ac81d2104e4de33629aaf3187c8daff10899ef791b1fbb18d2f2c17296870a0000018679bff98f1311c6b1c4df01647556064d2cd34087c8f83fac1ee99babc986600000000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "01dde5b2585ed32435eba299a594f328983090c620d3e40c58e2c457c5adbc7835001f01430c6ec1ba7a552b02f897a8789639760786e29fc4ce7e133f4d8768fba4032b0001b99142688612333a2926ebf2b271c50018a4240c9522cdee66b68c4ffa8c3616000172a6fbb0176b644a4a11f6c658324fc5d8140845959cb3c924b190a8ef86e22a0153250a8de904795ee993882737ee5f13458561a0f78d6ab1432e165f0b19ed2f01b54e695a6d674420b22d8b52727140dbde2d83ed45966a2cde91393d89ea6a0101d3f0b23da54e9066a2e4621baa67d255a69f90f788e35975e5fc14ae62eb9b3d012d59e25ff1850760f04238d20341db41c77a069ce6146af0c66693c5a7e84c04014caf0366131977fe12e0ef793ad22fa3f75df7a2bca71acba67bbb206797ff3c0001ce3e5c7e4f546304954681fd0e8f0e68dc38e5c788c99c1cc2a5404c160df93a0001038ca3c0ad85cbc76b94531a2c8d7efc43af084abdc88d2d28b30bc3a8ba4d39000001aa7b1980e5656743063400012808a5a2920431959e0b721c9a915b37ca3b450801c4accd50986a74b77eddf2e5191d20e40035f38bc7c9b91a03339af16c5a7032014c58c6ffa8b0fdd89c604006318b7901020edcaca498e3afd81af7579dafcd340001f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1780000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1780000.json
new file mode 100644
index 00000000..14b075a1
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1780000.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1780000",
+ "hash": "000000000092cd2f7d70451422ec93bc0c32dbb4b7ba7bfab4c6bb2f646621cf",
+ "time": 1661023245,
+ "saplingTree": "01eb57dc27bf63678d8d3a9d19b422a4694a9d43765bd308abd6ac1ee838dc4524018572cb62805c814962aee358456f07f5494cf3fa7b9ec1bc35dcd8f217e0920b19014241b45704d5126780eede03872b8f87501e4184dc68f977aab8e20d1e83da330001be1052772495777a8224029288e3cbce5f6528380c7dfab653661fa068e43267000000000120d655140e8a7069e4bb227ef3be10337d1fe805f6c143042f39e04047dcaa4801edee36cd129d61db744d5c70d749a066ae0903b5310f309609d014d349b7e435000001e4730f055d123a824efda2abf609b64bca407df2582fb188f15876c17a93183f01428fe4cdc82c312139518ae3cfed4fc769329bc192c724029a38dbf221557a56000176138376dab12f9063c0d4be21178499d1f912756be2dda3cb7540469376905901b261f10e8b41673b7707c93d6a38c9b3ca331d7beaf312b17dc5536bf9ac262500018fc9b4ef24688bf42b782c41232f9ec501b1c4ab6c950b0c41d34eb23b17402800017412114010d9ffb2f7387eda6e29942c0c80b6330788102467e4a0afe325382d018679bff98f1311c6b1c4df01647556064d2cd34087c8f83fac1ee99babc986600000000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "01b02222e4afcd845dc497c76d04b9f6ddb9081d01b9f9d5a2aa05fbc8d1443b1601a0331c1517205b7e943e9668d61e77e875c662dc96ef7cc96299b6a83890b41d1f0000013c6b5a330ccb2ba9442273535baa2dc7fa6dbf999b72b31afe032c168f4bd7080001e74ec029cd1c152dc8b09f65985d6c0c2c78e56da80c941538520a28587d9f160181a58cf9d3ea78a5685698258689ebe5fa4ce66d0ef9eceb6ae478750ef63139012e734c4d341726e1c02519bca7c04706db73fede3e95e9eb81208265d7b5c51e01e12417482428167d1173531eb01c4f05257c8915bd7b3c81668f7e4c0525d91901673db13a5adedf791c6b644470c5f61d202abec03de1ad93ad9b534e93925e0c01256c8aa3be887192ee742261edd0c2788d1b625442150145f4d3b8ed4905c218017fcf9eb3250652f2d0edd5af7d812909570a857529d71182f1e67fe20131b43a01c11b33655a2af90f359b1372d26da8e92bd8885107b6cc623e96243d56ec051700010d01fcaf34b9d9127ad8aade1bc05ed6c70040a7146a9fd596073bfcea1aad390137638774e0047f7be64682876e9fa29021fb428b367731ff1dc7701b58633e390000000001081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1782500.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1782500.json
new file mode 100644
index 00000000..c3c876fb
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1782500.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1782500",
+ "hash": "0000000000c96f27e270aa37db650cf15c6b75f1fa59a947cca4f41abf004676",
+ "time": 1661211894,
+ "saplingTree": "01ed3ce95d71c92565819babefc0f1026beac767dd6a162ac0ae9b15f619446545001900000001ac44d394a0bac1701dd6b74ab80f53729963826696a1da89aae59e12cf4c1b5f01dacf1de9631ba5147d124a2502808ee3eabdccbcaee03b03521cf765324f52710001efe591a8f59c1b2271ff973d8204c588b21747c305a7603cac7476f62424ed0d000000000138279c0da3d1576d92933860f5de697d212036da77df1af780bebe1b0fb1df2700000001734a94d73f408cf9aa047cb69f2b67382cd2eac7ad9fd8d4dca6b9ef4706d93a01323df8cc8affced4411de770643c86329441d276e3d0af469a5387baf14b702e000196ea06c6c9b1bffd35111b29addc63a564d780556def4814bd2bdf44568add0000000190fd9fe569252b58449910aeb7a9d5bed775699b04b2b0d2373b948728ce291b00000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "0173e8d2617537311c62aa77f610f752e4553fac6d8f8cdb05248941a2afc25f0b001f00011093e632be4376c29ca004fa22ceeb426c283d9006436eba5185b63ec4adab09000001ed13450a8b52b2467727d09d721f8054d408cc007a3574f090bca255d94da90800012d81bec0d20bb2214b91f3caf8d03c66c779b67f06a9908bfb239e25b2156c1400019971eb95fe89873ce0271ed1e70b8ea4498e54a81d4db7b092fefc7b9f540212000146181485362325772638425fe945f012e9b3884886dfc0f5c689f556d40d8032000001cbc56207feb2d10de3c2dffcc93040d4ed95a1d0c653c9587739681a162a601401aad0d336606800990785d124bdfe61ffbd399167bbc344f5855d3d4de468de190001464e545a0f5a15e6c09f82335c092a66ff8f10d0abe8dd0db230ca90dbcc0d2401f11d7f967046007bf41c33b30c0dc3b52c1a4a9cc3a7a85657a2fc11a39d393a0001081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1785000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1785000.json
new file mode 100644
index 00000000..51e2fe6f
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1785000.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1785000",
+ "hash": "000000000048a2a2cc1132d171fd58ae0483322d33a4ca4e5814d2f928358ddb",
+ "time": 1661400251,
+ "saplingTree": "019cdc2862d4223e86a3b2780f9cdf19b2b22431faa4c849d39f681bd01d32a0630145fdfaf100b993c09d16b34c6bb30834fbd9519be36b7cf5adc645fd2727805a190196199419320b54a8acf9a9b6883abf64a7e80fe75a17a6227bd47b8a2636b84700000001c5c6f40ccdd6c8c7c8b8257c1dbed8c9ab5b99320c0b367bd80c471f676dfe3d01324e46f8529d6becc636a9b57eb864b3cc6a19f9e1cf3da6fcd4b96456fdf1350111fe0737c21b236b783ca356947277888a785e569d1c8953d341741df944742800010e4303d3df4670374eec9b5fff8b9b7cead7c3720ccfc57bc05e9f15e005420a019a74161a408604546f401b8616c50dcd38a10efca83d1a5f0bc87f43ba3359670187130c30b7023f7d9a80fe75fcc1c7864007ae29e63c9a85dff0cc60e9055f5201d7a644ef58eeed93ab36be7ef27d90dbe43696ce2c0c2d1969e3de9f1cc9e51c013dc034d5b0eff46aa39a5461cdc285a75705eadf417e14fcb6a07feb4ed3a0530001f3a3cab7e488e34450427f914284cff92aca699a2e3bee42c42212419bb72a00000001c9c6bba2bb2a19be1e993af5f9bc35979454afa6237a44ad46ed0cd06c7eca4901e1a9472dadbe9da7956818483b066069ad0030dee06a787200e5519a5e0b96600001757bfb5957a0fa9b4fc9e8c105df1e9f0f9e156d653ba54a5fd2ebaa237afa510190fd9fe569252b58449910aeb7a9d5bed775699b04b2b0d2373b948728ce291b00000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "01fcefe6872d4fa4b8bf4cde106616dd137593b43fd761478eb4d1dd6715a4cc21001f0000000118ef33685241afb399b6d9c0697404998528096ae550dcd75365912cfb64953100019ccc0dc23d60dddcadb86ecabcfe1e5d42829a741f7e6705a368ead59ac492260158cda00196a2f05b86d770724944d4db0ff55d7442962dec634e3968822df01b019f815362606a86082ca9c3a332d4fbea2faef7c6be9cf4f248fa09672ab6012301ce1619c190c7fb2f62560a1723b9daf6ab97d03406bc4537ef4da5dbe4ea993e0000000001cbd9a766f5802ce880a9741919503a6b5b2e58104d1331b7c477d758aad8fe1400018ea310e6e8006b04172395acba6ef11cbef90cf053489a7f8b3287be3935ed3d0000019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1787500.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1787500.json
new file mode 100644
index 00000000..be24e89d
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1787500.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1787500",
+ "hash": "00000000013abc1b6771cf8ce5d7b385bef21fdcff3e809e96ad9ea119c0d0fa",
+ "time": 1661588407,
+ "saplingTree": "0103e6f1a4409bc20886cfe28eb0d0eb002050edec85b427da3164dd8e8f3d7d4500190000000153f1e09fad18b246d3acf132c9abb67fbb13b5116eeaa226b8553d3e19b25a530000015af73bf615b470dbb8f7d024c13ece622e091cce7bee801e9d54c1ad4e40e02f0149816bfc59f0deaccd1ada4c1e86b19e4413d0721784c4d052c3dc67f5c1805000000001b50664d826ff3b88aaa2ba08c69b8f21dfa710d79860c1441b5c6dfaddae483d00016378d71fb6680e282583c075d721a06b4b3b75d31df554364d4b0251def8a71001936597568d67c917899fd32efdff7afa023fbca108ebcbb402bf0ea9baacf33701f1dfff89a6bedf83c3219c1fd7261883687af993e64603b2b2be8a5fbf89754b01d31d49250f8d4970c03c939f38c3e321a8a96cdaf82a3bee23e73bd7eba7f55a01adf75581cb0b5b7aa87ebed8f1e54ac62217bf676b979298dd8b4300506dd72a01b9e02a948bb911d47d1e51ab6c7050074518cb1f6ff3856f93356d6dde2f8e0700000001f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "01e6a2858cd4376f3333f54f2033cf1211a3bfc0eb24b9b80ac2cf1446dae2f31c001f0140e2ea00e36545674bc7f09751f3391d628c78399ba803b6173e9d738a5fcc320000000179b60c3bf1b8cce227df010f5c49c22c7d777d5209738683c9b697756b31a81f016f91c16f2e3960269dac6fb301b534e027bb03374126e5dbf5e202c7422c861800011e71b797efd14accdf8a33abde065d670e6fa02a777d9e7d47be234e86b5431701deb97e143f39b0820e3421fd8c38cd340bc9cfac21262ddf86e0eafd75ff570a01b5045572a2467061c9282dea3618ba48f5b6dc05d83d1ad38d57e70735e8b514013b5384e5504f03e481ba341a2f14cd449a0a390acfeb5c56a155f6d99e188813000001794870c4958f10ee3be337bd2bd6ff6784d19662c242a40ad54a3f48bb2523230001bdd331201a0524ecdd6f812cf323d1fe0173f165c181bd74199480a6211bb91f01782a84e2fe01efa2b41d6875b0537d55ead77c75811ee929f0cf9236f976383700019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1790000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1790000.json
new file mode 100644
index 00000000..ab42998e
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1790000.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1790000",
+ "hash": "000000000100dac22c6e557360ef3c4283d9f60c6f4c5a1e0076d3b1fe97d0d4",
+ "time": 1661777577,
+ "saplingTree": "019d3cd586fd43ea7b42b862cf823edcd874462ed3a955f1c1dfd371d86dccd5700019000000015df1e3c5a797bbbe4f4848f60f21c0bb5f7bbf3a3168cee8a1cf5689b22286590001144654b0777c00cbe78b716bacc819d39f13945657bea3ff7e0c88bf1f68894d0001164cf0832981b185067cef308aefc2cc7c93a218b7ef456b3813f58ec2162d4301ec4614750b2c957a25a24685bbd536845cebe0739d3baad2ce645105e8584d5d000168ac0ff0fb620aab894183850de738f2a79424b36e11d5daf6a77e68b771081e00012a04464db06df9410ca6b17e29f72c4b4b3c5795cee583c5422168eabb7e84210001e8e58e5db11fbfce460285fef5113a83e8b5f0ac542ce3887d722a8e2a413c2f0001fd8156cb2a632eb0265b6f49309d30a762bdbbe2e8d6148ed26ab69e6861472a01187bf80dcc94180caeacb5860ca78ef66b0977c7ef6b773428a3e19b676aeb47014e7c8a253ae2f1d791c4bb7f5026736ea71839162c1b1da41cb6a6e64a49261e0001367383639b55ca56d7008a7a64d73b875703860d1a657cc1cc0df5a9ae5d5c2b0001f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "017fed4159767f6bfb26e8136b85e4a179606f7298de17664fdcc220df21b7a02d001f01cdd388ecf7f914294552caad1d570970b47974ec73494e2f04a80e7b391afc10011f6a01e993dbacd0a5487b8a7b06275a252d0d3895fab490e2030a255a9b0c06000000012c0afb3c10723bacf85c6611273be9e7d4676128dbabec978f0fa5221223893600000001285b5cb86d8544386b6a36c49e2bdb88fbd5f59a43437cfe271d95dfb4bcf82e01b20850820c9e07dfee4fd137ebe7f4d92ff7e318f3f1581b4b53014118d6d1170000000001045adfb00ce9f9ceddcebe2658b24be1beb059888c4f93f68bde2615aca3bf0c010d67deaf001eabcb4ded2f92fd69bb00fea4118f38c2b18b9dc2ae7e8c52480701f6a82e767e738485f26942fae63d20665479e996d8404cf44a451f9585fc5b32019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1792500.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1792500.json
new file mode 100644
index 00000000..892f919b
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1792500.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1792500",
+ "hash": "0000000000feda98d580a542579c369cbedb9677e2a60b45858dd3c3970aec48",
+ "time": 1661965969,
+ "saplingTree": "01056e79c206e1ec677117980ba8ffd3109f8df1911bf60d6a1f17194ca42d7a23018271f1b6a7b1034a1cb52e214e01fa76fe9b57b4ecc51513fdfee851a552e55c1901d12b2f51a9766ca6bb0e39e2fb8f15d3d27a620b7807b156df4d5519b115c239000166bee33838512fb2a13169dbec4a023d71eddccda1f6f364429b9012970e430f0001f498d5859edb460c87552d732c800e3cb655f87dd39edfb1123ee44f170e6b710001b8ea5420dbf802c4aa031ea066906c68e0b134ecd9f7ce1a8a9595301621b1270159b5fea41595326ec50f60bb37bc915c5525204dbb077ee6096a733e5e6be95d0001a18b7820f47bf1fe5304af022fa5c0dbe4d6d2b3c2b8f43d82e89bd29603ee3e0001ef928eac8166f4fa704d28411595a6d9e48fbce2fdb0cc1c22ff5eb65b8ea60301f62c314465ee352a914c74a495044a37f32ed19057b1e8b1139c24ae5fe1eb6d000001a54b39b5536ee32eaccd615c97b5894e59ca339fa58b170542e565957973c2640001abc74ef752650de527e13ef419675a18fa8abc0ea648bb79c9bad4ddc3a3c33300000001707d2f9501c9e36a368499bad696bc5aaa721b6d08f9a40f4ec8851e9deb373401f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "01d12a9e0d3c96789f73acf81fbbd59e1352c9510eddd29d7b3e79fdb6c34e873801ac79591488cf371014ca4577af93bf3b81be2312c23315f0d30007fee8d5a22e1f0115bacc61ac12c6c35f4601d1fa17909700158b676b305700615c8b22c50c0f11012075bbb910e3877bea5dfd4594729900eef32aa66effa3aaa2354a455a507f1801f5a06cb7bb2283e8e1bb8e5eb22ac57741f666f1c5ef2db8e5f644957c86481e0001f4924dc1afdcef8b935fda84922dab90e523b0100e329d24295e4ab809f38d19012ce7342f56d5319d6c54a63a326e33b8dced10d6cd696abf270e8305d8b6101c01e05e25ecc131faff939ad965861a12eda859ef69fd6928c65c978ae0d827592301d646e453f3ee10362226de45b8e9377ac12191351816efdf5d3254668345422e01b2642250d10f8ec40181ee561a18b60870aa700977107805b91d2f52b872f122012eb7bc0fff59658e28f1bf9b79fa184d21ff2679637745b2e6c51657993e001b01a50a617a7ffe94d3b52041db8dd9a0830c2fe42e394ce95ad4b6265bae2c7112000001f901961738a6f66400aee2908298ed5b83c53adea294db77b57d10eedbfcb5370187ae41d92d3780530a81c2db1a35768479f5843a62c64846320039681551062e00000000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1795000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1795000.json
new file mode 100644
index 00000000..6f9f8be5
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1795000.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1795000",
+ "hash": "0000000000080087f9812e87732355691e4bdd43fdbbf517618959c81cccdda1",
+ "time": 1662152700,
+ "saplingTree": "01b68aa765ae73bf3eddef03a2a0127126964d9a543859d462540467a4772425610019000001aa8687526bc08a818db298cf940aa78c30792a927a58ee1b6d11715b2507d9280000013788d7a49d2a299ce893eeb4175e702b4380f070ccd32d4b4beebdaae9d2dc3d00012653cc481a3bd5901dd5728d9754f9555932c00647c15ebd5a13cb1db579383301764ef57a84dee173a7d8b135065b75be0b012d5931af754bd339f6572ad84a5a01642fd0ec665cb43eff91032c290ee9cccf50b234af0f5ea94798973b3efc392f000190a2aa5d8963fe49141ced07138011754b00493211523505d488a2cf3ba24a320168569b74e5b9370152695d797cbe7174538b8e33304539f72efef6b58a9a4f04010b05111ee7d748441446ecc08f652247197356bbf83d73d7d07448ed4754192b017e137de7b7b21bbf9c3f9720196427864e7fe2fd8070aba9237b24fbec95914a0113c1af933a4048a7b09cb0e6ccd27691f4649385596d8111fe602bf4b255db29010470b81e369a600bdd7d6bb96c409554913044eb1c66dd7659a6e68129136165014d7bd573014ae0f6d462262ec28df786ef65aa9bcd5e2d6c54f26b4ee3633c2b0127bd15f9da068d7c81f1396a0ee87b700755451b9f4cebfa11919f3a4285713c016bce4a3d7e7ae87037b3ba0b9789dd1a1737e355cb862aba330cec03aec244710001707d2f9501c9e36a368499bad696bc5aaa721b6d08f9a40f4ec8851e9deb373401f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "012d8880e630c07430af4529f928e8d5d6d7f19f69a4449e7b6ce739f73a2e2b2f014fc16e1b120f140566bfd6a4e40797c72c87af205508ae98259bcdba377d3a0d1f01caa4adc8466eb027a6c6095ab1a20fecbe058ef63db381fd1d1f08628bb5a41801235d173241458aebb1b6d757489969e24a1a09720606ba56b1eae566bfbdb01300000126b2135c0942f13da6de1470ee2e5bc26c98f8d6d808ee71f2e7d7683ada763400000171071f52e8986c28e68db191de9cf8b88e39af84909289207ca4f56d346e85160000018f9f481c4fb6151f6868afef177dbd28f208ddb31bc4951185f31f4b6996420d014e57bc25f0a5322059b756e0b4bea83442a5e418d3486b57c617757d9098552501e0e364cb2abe4d1967bca145c4af1774eb2d84bca3af756581870661685cfb160001eb3af18b241fa811548c29205f42601bee7b58ba55fc529542836f1cf477bd080001f63a616ca09e1106337cee385439f0bdb7e14ebf6d2b3eaafc4c69ed04f147300000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1797500.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1797500.json
new file mode 100644
index 00000000..c9b787c3
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1797500.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1797500",
+ "hash": "000000000062762b9e9de093b9375fe1bfc451ff05a2c11a0339c90ec3e879b0",
+ "time": 1662341718,
+ "saplingTree": "0125d05417c5144dfe8aec67961d71a700c93b51d82486803f8c4719be2708274b01c675445684630272ec881b8283397137140ef8089bac18b3f07b0a7f3f8f6c54190192d55e6fef209eede1572a74f52fb42e347bb17221b179f28443e8ac190e480701489b0714407a588eaa9b6be421f77049a98bb005b79c3df1f8d592201360f94a000000000163669ec32a05e1726bfd63268924daf5bb3f6794a05a7824f87492708cbc3b2a0001e4bfc91695ef7bce9e29f48595b8cc278af7b1857039f2fdcf75bd58b579bc6201b6a313b5daf1ebb237d55661ce5d6f6a93018b407f2386635b727bdfe8ea6019000001dfe1241acfb16d56a4163ad86a0bb7b763b4f5da28b17b4413a3e6e8dbb7d728019a4fe2a045f95d86fe1a5fa9d413c4f36e42e1f03fbb2b5bed842d96d3f71e1b018e123a755b1d549c6f87ac048fa15664bbc3b10a0ef155b046eb4bc7e5b7061d000000000000000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "01a23430438ea5dafb4b7b4db2111f6280226cf69c172a8a763d1218f92ed6c23a001f01e4aeacad6fe90d4122dffbad65537ce856480aadcca1866d9682b2b968cc5e2f000001d142373141d5e6a48be5d097631fec923007c886d34e2b1ff5fbb55fa4f33810010f5906e6e3c4bf7d8015ff88c532795ea4b49f35dbb41a6bdf8592d09439a92c01c6580c00bd5c6da40f2026554c86788192aa5e8cd5f7486d8d51cc258cb312200001e41507a73031aa566ffb009506cd5b30992b0ebd34fc3fc78449a886a4317a0d0000015b224fa4142c9508f0b2dfd1e64fff63e996b697a4dd9502f9a3fd917cbfc42200010fbd0f1d4f428961e0b32b28994bfdd80e1e68b330883bd0889f9ca8f948b6190136b0d3765a45a227aedb1925c5670bc6364856ffcbaf19b52ab06f179ed897350132fe04f3088f035c4ca4c818b3e720ac26ac172f30b27dcd3e3bbcbb8192c11001fe9e4b5837bc7acc9356055fda92475bd0593e36a180e93549bb1ce2ab52e20e01f63a616ca09e1106337cee385439f0bdb7e14ebf6d2b3eaafc4c69ed04f147300000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1800000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1800000.json
new file mode 100644
index 00000000..8a0cab04
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1800000.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1800000",
+ "hash": "0000000000f908c93f3a7f4288b1f73a5c5621397ae94e4a894ef44ecceaa961",
+ "time": 1662529994,
+ "saplingTree": "01394fa4810bafa4d2231cb3dcc560e43e9f02418e82635d6a3619276e76407118014a496ba29efc52eaf66430ebe6be9d9b8dc10ab790cf624bd9b16d404e32e52619017ce5fbd2d63aee2d62820d40951f1efdb1ea9d4b7b3c40470d44b61b3537303c01b4c88e366d13efe090502c5e1c147a70f9be10226d97a16f726aef466245a9400001ecaf654b7bb55264015e8e5cf8ca20a01f79dafea9ea174a216727160a3596180128dfcd78a6488832dedf7d4bc7af36360a3ced5ac10f26a49451d2543096672b0001867d8b2f5148a10776a109b8cc6c0fbe006256eddd007ecdc8ac703ae03db126000001836b32ac862719c718b58435f87bba6d66a476711854121212299657ab973d440120d4f1b55c4e9e43442c08a201d4518369927b976a61c12add79cfdcd62c515000000109545546d941eb229dba133374ec57484d65febeffed0eaf9101e039db7eb06d01f98dd99c797f8ff594d93854a1f42f17bcf85d22e75fa9a06ce53b395ae1e671000148f810d1aaaa173f8c744fca6efe293f1b7be578def47bd9e45e647a35df5e3001d224af2c322521ed4297213b51e327cfa001cb6b7c04fd647a8c60d5ecff6717010bfec213bc4e3bfb6039ad8e0d01f1b6662367b4e38a2b185f227d77591b3b2001cf2ef2a7179af6eb4d2552757c05466c345a169d15c5da6dc13ba8fd34e3495700000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "01e0e4bfc911716e9548d6d4cb84cf8b66f62df3647a176f9aa3c66453013ff206010b0e83345c27139830a0fff89f018987eaf3b6ed776ea138ab846e064c7685041f0001821067594b408dbac72a9967d0d135053f174f0adc604af6887867ba1a1f0735017f46cc72632f201b8854c67456f91fe83418040e8be1bf983231da9d4e8c1a0000013a6f5f208f42b3640fe9a182cc4653f0b46cf1c62f7ca31ffcd14a542d8f7917016560e4ea4138ea5bcae07924ce84454871d1da8efb6ce4a59acb78f3287a30140001cb756a9be049d541b1cb9d4e58344c3cc6a34863345ac0c122744c026f29f7200000000000000001c5633b986425941f3be7634113839a2de1d40a43529148a16fc979cfd2245d310001c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1802500.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1802500.json
new file mode 100644
index 00000000..07a7e6fa
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1802500.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1802500",
+ "hash": "00000000017fbac093c9dd6d349b88685f369cbbe2c12b9a0c9ed6830bfc8e91",
+ "time": 1662718546,
+ "saplingTree": "018672213d179722fdc5221d5199441fc70a068db8c20a02ebf8bec70dac9f4e08017a0d68f3d30e1f5e3690c627ead792ae7ce1d08761b095ed717db285e60a0166190001c44e548845f6a825e9fdcdcec964674a84c48cff34c112d9828c1d5f21a5502a00000000014aea74e6cc2dbae2ff085618b475820a68a5969215246e00353f68129994a72900011489a9121bb1593a210f489a44b3fb8e2fe7b0b1143d89cf338b1da874438149012ea4d7a0b0e68673080d9f47f4c92728839fe0991626ae337f9ad6bfa598872b000001629e5d548ad857baaa6716719e67708b8b696b77b23ef900d49820d028ac604101cf6d58fea133cccc9307a56ef90ca4f8b32af3fb8b494b583724275da946a1370000000001cddcf07098de80d473262d9e1594738168a73a2e27bb986b9ce8a43635ccd03d00013f6c68973c08f026717c32a6be0d4083febb8c2d63f2e895a6d07021afa3a51c000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "011618cffe419b3ae6d6f94ed2138925766f616262a81b11220dd64310db9a602201b50f80ddf6be2c181b9e4795a48aa30def25318410e19071bbcae266517237061f01000ec86443fda797bdee68d22ad1c8e3159ce016343aed4bdc4044b30808ff0d000192dd02c90b3e87a42dc8a5a1bc59b832e21937614d71aec0ca0dcb4293c8802e000000015226ac6b1c91f0b90680b4cf4175a38450727aeaec887792adb9ef13c411de0b0001bc359c17ecd3d5fb2896bfcf890ebc51de0eda3c55734dc24523a41b24267f1a0001c8b40d0cea90b7642884cdca72286ca44b278ae6e9ad2e2bbee1b30680b1a60d00010deddae26bf93da080b26610dae9ddccb9243015487b922f3c05198476c4ca2400019902d40b5aab956a3001b26499f3f689c08ffc9b5989d14840ea01f443dfe61c01c5633b986425941f3be7634113839a2de1d40a43529148a16fc979cfd2245d310001c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1805000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1805000.json
new file mode 100644
index 00000000..7a068b5e
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1805000.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1805000",
+ "hash": "00000000011cfeaec0ee1d6230aa8d3642ad4c22db6d541ff43520ed2035f7eb",
+ "time": 1662907506,
+ "saplingTree": "016d31b4c64ae5c17308b9d90572e6de5993b3755e861e040949fe4438caf1000e00190000016c0be2c033839aec91d32becbaf583282f918ad917865f323a5c88d687f1bb1501ba7e5ccb08c9579b4fa81fa555c3fa94e6872eee2bf8f01641f6103e9a975b4b019c049c4abf367e07e168ba327e86a7cb04d11d9ad446e2356f2870cfe9e958340001f29ca1e8f612cc422bfeb2713a89062d8695f44436cb8ac0b57ff5f2f1d8da5401e4195653dc559c9590e0248719443c666c82d6e7521d2688d32a914070eb7a540001f8d79dde2f627bfd60d0c1131cd3ad6b779c9619f6dbb0ab0515778a9a9fe24d0000000001235b463d441fae8a4ee172bb40b1a3853ba4a07ea44377b06295db356608ea3a013c2d33c9c5afba6b720ffd9ea7c69af1635c2f98d291e2eaf358f2c87232ec2a0000013ec9722bf3aeef59c292db71e3de87d2a3868cdd2902b867328e24c30c8dbe1b0000018c2d6adea2ad4faf20eccfc2c2a2c59192fb53d3204b3a2757f1c247dadec16b0001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "01a1eac07fe90382f2003de69239f281a9798b7f984bc20a3559d5f886a9e07035001f019f6fb5bad161ee9f26dbe23c485e0f25d083e17c1e68b91525f33d659d35ac1d00013957a17ebe53945197d684304f29a77486a0f081c6a4a3f4c73b33b88a834a1101e151d0e8b30866bf9779bb3d1105c3124db863690458c855537e967679bd903c000000000001bad8cfde5bec689cc645ed5c9e37f6e6f1753fa9a95f40ccfe25ac91bcc2481e019927318c01f928c75112b5d0c116e9a7244d58684396512e5e24569acb94581a0000010ca90449f94a0ea6eb64ff88b438e14649280613827022401d54a8635e63943100011759fa02e2fae57b40cd62bd3829225c15bad6430caf25a526d51f329091293e014660573ed7dbd889063d06f1d814a4f28a5ba4da76c2c5c8e11ad980f070353601c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1807500.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1807500.json
new file mode 100644
index 00000000..902d00ec
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1807500.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1807500",
+ "hash": "0000000000b92234ffe6efd360a2fb1528e2f5cf891a54e58a9b83cd6c513ad9",
+ "time": 1663095814,
+ "saplingTree": "0146b9cd1dc80b44c19108d53ecf72b5bacd405e8ed5c8038c413ad2d7bf9f486201fb61737c80f0c3816ddc487ff9ba360c60b1663151cbd07c08dd8c09c648760d190177aa27c08a1ff4754710984f2f94c5a101a10f57eb6ad49f7b1df47f7e8a5730014c0fb4e36c1b7968082d0355f318a67e265e5a5533ffcc01b939e4bf1779841b000000019315fe44f132446949cd9ea1f34ec20f32c0dd0ba4f732e563c040c692fd5d49010e6b6ae8956bd722b0f5e3f355adfc7c9b6a39542c07fb0bcd317d0eff8d99080001382db97fabb726a88e7a7baab050a1c9169d2142a9ce0b7f3022349acf74981a0175d44cdd04e213c2d95fb281f96fc7d734c65be70a77b7aa4fef8a4b6fb2475b015fe654f11132fc9c133b46dbbf19b0113cc45715f52dfd3282ede76f6880740c01d14f83f0fd7d09f4f52c8ae9d39c63b57c59a37666d211b9a2deb290808c02720001701df279d9a2270a82379486df546fafeaeb831993cde1cd9e5c9cb17be5191f01cc6ae86e9147b0b1c3f5fb32fb7acc012b4cb4384a1a1331ae3c2324a804483e00017fb2e2890b05355ba797af2f77e38cab3e8ec1623d29a912bdd0dc4a78cf554a0001de17a599d0c6d73eaf1a5939e95af4427f75b89f703749e00d853cce3d6af84f00014f6313837ed19d2b480ec531529fb6b425006f2c1d981077640be21627659410018c2d6adea2ad4faf20eccfc2c2a2c59192fb53d3204b3a2757f1c247dadec16b0001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "013de3a4ad28b5df77a979fc925643e69b7dd26ba787a3122fcd6a445c47d9280e01b68287983d323c90e6a1ed5980ab5b1a846d49340ee6d40d2349795c132a382c1f0001c2db91c5b9baa1c09623429cb4005ae12e521a018eb8df2d051d6793a307eb3e01c6b2f8d93635310d470d4a6d011ea77f59e28bee6ffca3df88f5f2a98980331a01777860ffe739b8047045e2dff8ba77070666075214a0f7702568205410351b39019ed7c5c3e958cb8b9d5324c290ff384dc3ca6cbc870950002f64398478ff1904000114b5ad56c8f210854a1688f47116b5d272fea09559646cee33ad3e6958306a15000001110e689714d772b170f63bfbf4b144c6a48e0a57c4a2780513633d5c799a0d1300010cf94eaf4d5268d9e0878064a458eaa3363dfcfcf2602681c443baf989d5de20000000019776dec2ea06cc5ecd2d212d37023972f526cb2ffa7ce1e8cf8eb4ef04700b01000001c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1810000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1810000.json
new file mode 100644
index 00000000..e1edd90b
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1810000.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1810000",
+ "hash": "0000000000bdf01be068ff1f0b21b8b266f839b31ed066b72888b67f672c9800",
+ "time": 1663283618,
+ "saplingTree": "019dbc466ad114f2b4a6d0d91198c0a2c5c20c1dec7c2e7f932c9f9197d76b80020019015b272118101cac0ee6b9b8cf26d5104ab42913d2f5253388bac28e7998f2c41b0001868d5008c587f0fa3d26fc42097d34df49a70508a85a6acccf1263d39cb62c28000001b2325a6ecbf023f3ce4b74c1c14bac8d9c462560dce9611bd7b08cd55ecf4b0100000001cd62e4e2142c664656f956fd0ea8d1de1027c327580398fe8912e6264801650201ccc9b305f6e65d641a4e461ea5e853354a3ac4b2acf2591849e00421bd58fb48000000013a591632f71e1fe214ab46b464112520e98f15d171da599a350f7d8dba79595d018d254447626cf40828102a60e2b433d05498a780599cdf56a14f3888c2f42008000148af2e64d92d1944a451180f1738c9f468f608525b0273967db19029b53ba16d0000000001f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "0180c286284cd52360af960fce56fe7dc339667c35580c75e9bc339483230e932701260b8984058125b5e3558fb65172d59718427111cbc06891017cc7633f74e1001f00000000000001cde58ba8e982a34499406e06a763ede11353d39ab93a5af1b34905cf268c033701226087d6a9bb28c2ed6890b989224791093cd5012a27285040025c0a72b2ce06000001ba71f8a1897b754e9fe37a29eb2c1a93ddc1678298498b4a84da732ebf056f15018073f4aff677a24eaac68c20d271ea228041f1e77710b0504d3f6c0b71d63d24000146b37a3e6167ae7f07725ab4e32247619c37e2a91c87182dd68b8feb99d5a22201e18dad85447ef2e9b8d647c9b9f1e6cef3e1d03f908975cd5e1d5c5808e443010001898b4a8f384f342a67efb3f6c4afd87310df4ff1532b86ca8d1394975aab5a1e0001c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1812500.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1812500.json
new file mode 100644
index 00000000..72e51cf5
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1812500.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1812500",
+ "hash": "00000000008f6e5ac009390e7daa092a3207581c211e2be70b60ce04b88505c3",
+ "time": 1663471780,
+ "saplingTree": "0180d672d65ad56265eeb9632dbe62473447bc7bf3e598d2e896cdcb798887b53d00190139d37c3a694b0104d15e447f51d96800d260a5a1053558cd4b7134b8c77f803400017175873eec18949e09f6df019a3d20530716d2307ba1f00fc0c09bae6656022400000198c1229f3df738745986e8aabc1d3ec92944865b43a910b8c74221f99b8ee0320000014c8a851d7fdb90d17b97621005c826c8b3ce8ee463062e324e2ad886f202b6440158720dd7a931f66eac2ff934b0765f807c9504ea318c18f606831c80feb3770401252adc68c56eae8331764a2d9d72851e7352bd33364963b6618f47d56bc21c6900017d8e001c7be91297f727edf47b650f44cbfbebb535b4f409e357b37c43a621040001ba60d34966fcefe1b8058d28b784f87089e693fde8b99902a4fc956021f7ee04017547ef39c632d9bfd0cf48e3e909dbc2e0890f2791a6929ca0d6674484f4c046000190ca77d38f0a408e33a8633f140441488cdedf7508a06d04771b53e4e4de9861019db47a02109d9232f4c4fa044613a8a0cc508865b185ee28318c86d16829895801acc2d2141fe4e5c43c11710e48bd0b12c0493b1721fad3b03470a4400157e020000001f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "01cfc873591e05c184abdc8a1a78659e28df418ea5320a5bd81d2d761c1af5ed0a001f01244feb50d761d34ab22b04045ae9eface10df4b17bbded9d1da9fb71e48cf01400018a07d6cf54e4da1fb1f501850cba2a98ff5a8a8a5722896c3906c09447500f26014be19370bcb96d495a7c1e9719ccc057104a66a5ef6ea217f3fe29e26459131d000172482cc1f1fad25e86299334a24a63e6e86c0b04d319a230bd3aba2dd38279110124de7fbfe4a050ae1427d73491bdddedda1e2e7db48d2365b3cb108eb9833b34000000000000018eb08300830f838c7d212857b7e045a05458264f44f33a9f5d726631634da70201778055c0e5ef68c30c7fa81eb67f0cbf32b64dc5d6ff8138f380cb8f115418260000010fb2cb03705826ca086f605974429cfb0dc60df50a0d15d06c2cfef80017902f01c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1815000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1815000.json
new file mode 100644
index 00000000..4805a8fe
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/1815000.json
@@ -0,0 +1,8 @@
+{
+ "network": "main",
+ "height": "1815000",
+ "hash": "0000000000ff971a219416d483ec48af6e055182ab566572fdda7fedb2d15368",
+ "time": 1663659973,
+ "saplingTree": "013010baaf80742269ab7061e012c90e4ef805cc07bb62ef71869fe0badc141f6901d003b1d99e5dfc26894e0b1f393399560374425c34f89853df55a4e32be616251901b34b07fcc9a91eb89bc409a51f452bd21327cfdf3328e9913289760e464824710122ccb843dd5775365b143f0cd1cf309c1940a7c3ba7070c92d041f35b8fc3f4601da8ae93a665218fe154683bb6c75e5ded94f1780358591ce7524ca324f22a53601962ea341367529954ac02e5479c129499aae960e8448ec5697f216b45986b3040168c82ca2a31ca7b833886ee0b3dbb60c519a917b2dfcb6590175b38a0cf17944000196c2704fda3d5e124118cc6cbb1514bbdbdff5bab3a334bda2af27b3e1c7f42a0103bdd297e11f1bbf4d438079aefdc797b012e2e90903194011f9f23cd7ed5b510198ba7189c4d8aeaa58402a655214c69bfa4d09c7818a9691629a8643cca9c91d0000016fbc427c11055083f9330cb82d07c7ce7ca37ee43c7bcc687683f6a50cfdfa3e01201e7639e97113adcb84691cca72efb6f8f44293109ccf02f1beabe46412b75c01046ace0bc25d6c3889da89753306be90ce721bc463e9a6662f3ea10141b5db2801ac5fca146985e234d3ae22d84b09f7924d8baeee82fba8235dd9db60db8f6565000001f513025ec4ad6f392fa842db7cb2f0be10e1bc3662046db7cb6d5de314abe33a01ff0944072256da35186a74026d56db14a2c80ffc4dc7a3b2b5a93885c2622b5201471bdfaa9efa8b9adce9654553a07278f7e37156c73b03394ca8288d8983403001bc76949736399cf6d26d21a468ad6c8ee3e563e0ccef01d615c45a91bc9a612e0001f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
+ "orchardTree": "013f012dabdfb54100613c405576e16bbc19a802910fde444e98827f568143ed0c001f010853c8f2d2423d321b9630781d679b884cde7b3160108ddc1233f955e1bd9628000001b8ef98edd02dbc2b74a63778968d66cb1361cc01f7b4434b141ec35c12a2741901c5a9a64d994fbe4c8d044c80f8f551595ff8a33b2bf98857058ab080aa2fbf31000000000116e9a922d1b143d61c100ff15dc2ffdf92e70721aed5a6c37270d7136fb38a1b013dedd8fa148f4930ad5fc0ac019753a21886453af5c94e8f8817a4e13b7ef810000001f69a0bb2a07942810983d469f50f2285c1ba6f0b3355378356cc62a17860cf170175dca061b72ede5cfcb867aa38ce3a37fc84b7586cbeac75d077c5e0eb33b3250001ccf521db485f5532163391b715519775dc86bbb7b938687551a8547a7c81b425010fb2cb03705826ca086f605974429cfb0dc60df50a0d15d06c2cfef80017902f01c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2000000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2000000.json
new file mode 100644
index 00000000..70647786
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2000000.json
@@ -0,0 +1,8 @@
+{
+ "network": "test",
+ "height": "2000000",
+ "hash": "000bb3c6d575a155a56b1aab0e7f14a1141d25c1f36aa6f15c1cb1ef1d7d498c",
+ "time": 1660621966,
+ "saplingTree": "0133914238639e4f22ddc8ca2cab7e2d7ec676b06f12a0bf42bc757909cec4f47200100103217f60b7d9bdc625ecd5d6170e3ab57a81eb29f7beacecd07c4f3f0d61ef2801066673d6ab6b62a91f6e04a49fc0e74083cfcccdc851db681e01db333ee1cb470000018da55ded8e55006c2a6c41474da220a9a179a85a99a48df0a31a74291317720e00012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
+ "orchardTree": "01a682706317caa5aec999385ac580445ff4eff6347e4a3c844ac18fcb5fe9bf1c01cca6f37237f27037fa7f8fe5ec8d2cc251b791cfb9cdd08cd1215229fa9435221f0001590d3e7e3f4cd572274f79f4a95b41fa72ed9b42a7c6dbcaec9637eaf368ac0e0000018843337920418307fa7699d506bb0f47a79aea7f6fe8efc1e25b9dde8966e22f013b5a8ef020d8b30fa8beb8406dd30b2a1944755f5549713e4fe24de78ab72e12000001a46523754a6d3fbc3226d6221dafca357d930e183297a0ba1cfa2db5d0500e1f01b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2010000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2010000.json
new file mode 100644
index 00000000..2e7d62f6
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2010000.json
@@ -0,0 +1,8 @@
+{
+ "network": "test",
+ "height": "2010000",
+ "hash": "0013159a578c874aeecddad8707b8e274a018078fd80fc3b9e2d04065abeb05d",
+ "time": 1661274815,
+ "saplingTree": "012bd51ef4da530bb488d43a0f770109df7186ef164a64f618038bb00b7861840a00100001d3a6dfc2fac7c968ce7f96efdddc9764bc320230d8ce166637f8b73b6453e8670000000197e98f0a36d7a5ccac4b4ec963223a4eaab38188eb2a658ff01da4880839996e012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
+ "orchardTree": "014b0ab9afb38a6c819d64806b3de5397c6469ca0f7e58274f5c779ecb75d94217018646890daed861a8f66a3966e884da1b1c8240bb47327a348fae9169eb8205051f000118681f82320257f5a5cfc5c846fd4e88641d4ed7b87fcd5ec8e65e68e3ab0636000155cd8735904f69800ba500bf8af6fc70bfb2abb3d7ca1c75a1cce183b763f30400016e57cbad21bab804109f19479a88046b5c7f8da0d21e8217ac467801235c52200001a8773a2b01e9efd1011ea7d8d1071ad0a01c6c73e2fb6df802e307df17b4ac0601a46523754a6d3fbc3226d6221dafca357d930e183297a0ba1cfa2db5d0500e1f01b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2020000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2020000.json
new file mode 100644
index 00000000..46c5ddbd
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2020000.json
@@ -0,0 +1,8 @@
+{
+ "network": "test",
+ "height": "2020000",
+ "hash": "000777cc07627b8a0a0086f3e7cab5121372c6caad19d387a58dbd9576827a9a",
+ "time": 1661953684,
+ "saplingTree": "010620a4db0c09f044c0edfddb2e13ee69ae7385cf2ea483b9924b6af8d4d51771001001edfd2f5620d6913ff9b1e56d7e60ead3f140e9756c1351dd29f6acb98643c96901d3a6dfc2fac7c968ce7f96efdddc9764bc320230d8ce166637f8b73b6453e8670000000197e98f0a36d7a5ccac4b4ec963223a4eaab38188eb2a658ff01da4880839996e012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
+ "orchardTree": "01f33757a6d93a793fe0c9316ea63db47d01dd6a4a7861a046bca399b6dc4cae1101baae6b740a3589fbecc9b7bc5648d4e7bc1aaaec9dacb1f1fad48dd0d60d4b0d1f0001dc89cf3d764acdccad19c5f0fd2b4a133e132221e20c8bc42529127ac8486b3501e86d5e712791cfdc0abc80d6cf13cf2ac8b96ce89dd4d24c36a0f87dd8192b330155cd8735904f69800ba500bf8af6fc70bfb2abb3d7ca1c75a1cce183b763f30400016e57cbad21bab804109f19479a88046b5c7f8da0d21e8217ac467801235c52200001a8773a2b01e9efd1011ea7d8d1071ad0a01c6c73e2fb6df802e307df17b4ac0601a46523754a6d3fbc3226d6221dafca357d930e183297a0ba1cfa2db5d0500e1f01b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2030000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2030000.json
new file mode 100644
index 00000000..01435dc1
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2030000.json
@@ -0,0 +1,8 @@
+{
+ "network": "test",
+ "height": "2030000",
+ "hash": "0020f1af6bad0db43f2c42baae19ecf784142cc06973c0054f82259096acc700",
+ "time": 1662635959,
+ "saplingTree": "01e9be53684e763faf4f718a731191ea92872c0ebe725ab22a5e4c9564d4854e6c01bcbedf283945d4a3ae20c57c9786f98be2b51c83fc14084bf81bdab4905c95291001be49bd71757b254fe215647852f1ebb4e9c6539470d1f883b260ae31f287c139011dac0abc733ee5359d9ebaf2fd1e27d97ce2dd318461e9458b21fa7460dccb4101f763bd441cf06dceca58869da07c620bf7b09cb89edf2eff0887ae3e2280d11300000197e98f0a36d7a5ccac4b4ec963223a4eaab38188eb2a658ff01da4880839996e012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
+ "orchardTree": "01d1cfe443088811d9043242dd694da0274ab90ec4775b8dd9b1d2177d8a1bd72b001f000001fd6d2d94e0aeb520a55825da8640517d4a8a4643ad93174d860d3ed25061580d012636bb4ba0c2e449cd2e3cb5f3e6a372d51f84879ade82345700afbcbad65128000001b51a157d0d095cc0ffbb19b8d0afd168f167c36601da2ba7c6dbd301bc0b4e2a01a8773a2b01e9efd1011ea7d8d1071ad0a01c6c73e2fb6df802e307df17b4ac0601a46523754a6d3fbc3226d6221dafca357d930e183297a0ba1cfa2db5d0500e1f01b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
+}
diff --git a/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2040000.json b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2040000.json
new file mode 100644
index 00000000..c7b939e9
--- /dev/null
+++ b/sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint/testnet/2040000.json
@@ -0,0 +1,8 @@
+{
+ "network": "test",
+ "height": "2040000",
+ "hash": "000eb52c4e328d62f1534aa382e8694e76a5a2390322f83e33e503b5dc7ae06f",
+ "time": 1663304586,
+ "saplingTree": "0156434fee1f941dbe0b18b17f3dc9dc758c012090a4abaff688497eb29b8c856800100116548b6eab46750ad5b63ecf449bcd4a17297d2188181580c64dcdbba3a8af0c000001339480cf39fd7dc1a01cacc143be0b8298ec3c94d6b191810c22e1aa1b2b0518000197e98f0a36d7a5ccac4b4ec963223a4eaab38188eb2a658ff01da4880839996e012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
+ "orchardTree": "01e4caaed868ac7bf736481a945a4f800587e2dc7e36004f4d65a9bcc151b9c42801cb2fab44427363a0438ae221de1e6d07ca76b67b00debb77a81f68bc67cdff1e1f0001bdabf0b09af83b73aafa98e9713ca35c48f88d034f98cf33523f567a82248e320000015a497aaf21034cbbcda9cd0805f0a550c0de19229618be23b0bc64e285265f030001bccbf993ceea1d160380db1b90b634b1a8ee1e75b6be3b4c2fa92ade249f2e39000000012e8eedc0e41ccd50e4a4fa428336f7bae0e1ddc5e80786ebb3f5ef065cc05b03016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
+}
diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt
index 6bd7061a..e6363aa8 100644
--- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt
+++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt
@@ -32,6 +32,7 @@ import cash.z.ecc.android.sdk.db.entity.isSubmitted
import cash.z.ecc.android.sdk.exception.SynchronizerException
import cash.z.ecc.android.sdk.ext.ConsensusBranchId
import cash.z.ecc.android.sdk.ext.ZcashSdk
+import cash.z.ecc.android.sdk.internal.SaplingParamTool
import cash.z.ecc.android.sdk.internal.block.CompactBlockDbStore
import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader
import cash.z.ecc.android.sdk.internal.block.CompactBlockStore
@@ -73,11 +74,9 @@ import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
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
@@ -103,7 +102,6 @@ import kotlin.coroutines.EmptyCoroutineContext
* @property processor saves the downloaded compact blocks to the cache and then scans those blocks for
* data related to this wallet.
*/
-@OptIn(kotlinx.coroutines.ObsoleteCoroutinesApi::class)
@FlowPreview
@Suppress("TooManyFunctions")
class SdkSynchronizer internal constructor(
@@ -117,9 +115,7 @@ class SdkSynchronizer internal constructor(
private val _saplingBalances = MutableStateFlow(null)
private val _transparentBalances = MutableStateFlow(null)
- // TODO [#288]: Remove Deprecated Usage of ConflatedBroadcastChannel
- // TODO [#288]: https://github.com/zcash/zcash-android-wallet-sdk/issues/288
- private val _status = ConflatedBroadcastChannel(DISCONNECTED)
+ private val _status = MutableStateFlow(DISCONNECTED)
/**
* The lifespan of this Synchronizer. This scope is initialized once the Synchronizer starts
@@ -181,10 +177,7 @@ class SdkSynchronizer internal constructor(
* processor is finished scanning, the synchronizer updates transaction and balance info and
* then emits a [SYNCED] status.
*/
- // TODO [#658] Replace ComputableFlow and asFlow() obsolete Coroutine usage
- // TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
- @Suppress("DEPRECATION")
- override val status = _status.asFlow()
+ override val status = _status.asStateFlow()
/**
* Indicates the download progress of the Synchronizer. When progress reaches 100, that
@@ -310,7 +303,7 @@ class SdkSynchronizer internal constructor(
twig("Synchronizer::stop: coroutineScope.cancel()")
coroutineScope.cancel()
twig("Synchronizer::stop: _status.cancel()")
- _status.cancel()
+ _status.value = STOPPED
twig("Synchronizer::stop: COMPLETE")
}
}
@@ -420,7 +413,7 @@ class SdkSynchronizer internal constructor(
// ignore enhancing status for now
// TODO [#682]: clean this up and handle enhancing gracefully
// TODO [#682]: https://github.com/zcash/zcash-android-wallet-sdk/issues/682
- if (synchronizerStatus != ENHANCING) _status.send(synchronizerStatus)
+ if (synchronizerStatus != ENHANCING) _status.value = synchronizerStatus
}
}.launchIn(this)
processor.start()
@@ -817,9 +810,9 @@ object DefaultSynchronizerFactory {
val coordinator = DatabaseCoordinator.getInstance(context)
return RustBackend.init(
- coordinator.cacheDbFile(network, alias).absolutePath,
- coordinator.dataDbFile(network, alias).absolutePath,
- File(context.getCacheDirSuspend(), "params").absolutePath,
+ coordinator.cacheDbFile(network, alias),
+ coordinator.dataDbFile(network, alias),
+ File(context.getCacheDirSuspend(), "params"),
network,
blockHeight
)
@@ -864,8 +857,9 @@ object DefaultSynchronizerFactory {
internal fun defaultEncoder(
rustBackend: RustBackend,
+ saplingParamTool: SaplingParamTool,
repository: TransactionRepository
- ): TransactionEncoder = WalletTransactionEncoder(rustBackend, repository)
+ ): TransactionEncoder = WalletTransactionEncoder(rustBackend, saplingParamTool, repository)
fun defaultDownloader(
service: LightWalletService,
diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt
index 6acf2651..89d748a4 100644
--- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt
+++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt
@@ -6,6 +6,7 @@ import cash.z.ecc.android.sdk.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
import cash.z.ecc.android.sdk.ext.ZcashSdk
+import cash.z.ecc.android.sdk.internal.SaplingParamTool
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
@@ -521,9 +522,12 @@ interface Synchronizer {
viewingKeys,
seed
)
+
+ val saplingParamTool = SaplingParamTool.new(applicationContext)
+
val blockStore = DefaultSynchronizerFactory.defaultBlockStore(applicationContext, rustBackend, zcashNetwork)
val service = DefaultSynchronizerFactory.defaultService(applicationContext, lightWalletEndpoint)
- val encoder = DefaultSynchronizerFactory.defaultEncoder(rustBackend, repository)
+ val encoder = DefaultSynchronizerFactory.defaultEncoder(rustBackend, saplingParamTool, repository)
val downloader = DefaultSynchronizerFactory.defaultDownloader(service, blockStore)
val txManager =
DefaultSynchronizerFactory.defaultTxManager(applicationContext, zcashNetwork, alias, encoder, service)
diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/CompactBlockProcessor.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/CompactBlockProcessor.kt
index 7c50ddb8..c0d87252 100644
--- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/CompactBlockProcessor.kt
+++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/CompactBlockProcessor.kt
@@ -48,10 +48,8 @@ import cash.z.wallet.sdk.rpc.Service
import io.grpc.StatusRuntimeException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.isActive
@@ -79,7 +77,6 @@ import kotlin.time.Duration.Companion.days
* in when considering initial range to download. In most cases, this should be the birthday height
* of the current wallet--the height before which we do not need to scan for transactions.
*/
-@OptIn(kotlinx.coroutines.ObsoleteCoroutinesApi::class)
@OpenForTesting
@Suppress("TooManyFunctions", "LargeClass")
class CompactBlockProcessor internal constructor(
@@ -131,12 +128,9 @@ class CompactBlockProcessor internal constructor(
)
)
- // TODO [#288]: Remove Deprecated Usage of ConflatedBroadcastChannel
- // TODO [#288]: https://github.com/zcash/zcash-android-wallet-sdk/issues/288
- private val _state: ConflatedBroadcastChannel = ConflatedBroadcastChannel(Initialized)
- private val _progress = ConflatedBroadcastChannel(0)
- private val _processorInfo =
- ConflatedBroadcastChannel(ProcessorInfo(null, null, null, null, null))
+ private val _state: MutableStateFlow = MutableStateFlow(Initialized)
+ private val _progress = MutableStateFlow(0)
+ private val _processorInfo = MutableStateFlow(ProcessorInfo(null, null, null, null, null))
private val _networkHeight = MutableStateFlow(null)
private val processingMutex = Mutex()
@@ -168,28 +162,19 @@ class CompactBlockProcessor internal constructor(
* The flow of state values so that a wallet can monitor the state of this class without needing
* to poll.
*/
- // TODO [#658] Replace ComputableFlow and asFlow() obsolete Coroutine usage
- // TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
- @Suppress("DEPRECATION")
- val state = _state.asFlow()
+ val state = _state.asStateFlow()
/**
* The flow of progress values so that a wallet can monitor how much downloading remains
* without needing to poll.
*/
- // TODO [#658] Replace ComputableFlow and asFlow() obsolete Coroutine usage
- // TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
- @Suppress("DEPRECATION")
- val progress = _progress.asFlow()
+ val progress = _progress.asStateFlow()
/**
* The flow of detailed processorInfo like the range of blocks that shall be downloaded and
* scanned. This gives the wallet a lot of insight into the work of this processor.
*/
- // TODO [#658] Replace ComputableFlow and asFlow() obsolete Coroutine usage
- // TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
- @Suppress("DEPRECATION")
- val processorInfo = _processorInfo.asFlow()
+ val processorInfo = _processorInfo.asStateFlow()
/**
* The flow of network height. This value is updated at the same time that [currentInfo] is
@@ -240,9 +225,13 @@ class CompactBlockProcessor internal constructor(
consecutiveChainErrors.set(0)
val napTime = calculatePollInterval()
twig(
- "$summary${if (result == BlockProcessingResult.FailedEnhance) " (but there were" +
- " enhancement errors! We ignore those, for now. Memos in this block range are" +
- " probably missing! This will be improved in a future release.)" else ""}! Sleeping" +
+ "$summary${if (result == BlockProcessingResult.FailedEnhance) {
+ " (but there were" +
+ " enhancement errors! We ignore those, for now. Memos in this block range are" +
+ " probably missing! This will be improved in a future release.)"
+ } else {
+ ""
+ }}! Sleeping" +
" for ${napTime}ms (latest height: ${currentInfo.networkBlockHeight})."
)
delay(napTime)
@@ -262,7 +251,7 @@ class CompactBlockProcessor internal constructor(
}
}
}
- } while (isActive && !_state.isClosedForSend && _state.value !is Stopped)
+ } while (isActive && _state.value !is Stopped)
twig("processor complete")
stop()
}
@@ -609,7 +598,7 @@ class CompactBlockProcessor internal constructor(
if (null == range || range.isEmpty()) {
twig("no blocks to download")
} else {
- _state.send(Downloading)
+ _state.value = Downloading
Twig.sprout("downloading")
twig("downloading blocks in range $range", -1)
@@ -642,7 +631,7 @@ class CompactBlockProcessor internal constructor(
}
twig("downloaded $count blocks!")
progress = (i / batches.toFloat() * 100).roundToInt()
- _progress.send(progress)
+ _progress.value = progress
val lastDownloadedHeight = downloader.getLastDownloadedHeight()
updateProgress(lastDownloadedHeight = lastDownloadedHeight)
downloadedBlockHeight = end + 1
@@ -650,7 +639,7 @@ class CompactBlockProcessor internal constructor(
}
Twig.clip("downloading")
}
- _progress.send(100)
+ _progress.value = 100
}
/**
@@ -765,7 +754,7 @@ class CompactBlockProcessor internal constructor(
withContext(IO) {
_networkHeight.value = networkBlockHeight
- _processorInfo.send(currentInfo)
+ _processorInfo.value = currentInfo
}
}
@@ -868,7 +857,7 @@ class CompactBlockProcessor internal constructor(
lastDownloadRange = (targetHeight + 1)..currentNetworkBlockHeight
)
}
- _progress.send(0)
+ _progress.value = 0
} else {
if (null == currentNetworkBlockHeight) {
updateProgress(
@@ -882,7 +871,7 @@ class CompactBlockProcessor internal constructor(
)
}
- _progress.send(0)
+ _progress.value = 0
if (null != lastScannedHeight) {
val range = (targetHeight + 1)..lastScannedHeight
@@ -1089,7 +1078,7 @@ class CompactBlockProcessor internal constructor(
* Transmits the given state for this processor.
*/
private suspend fun setState(newState: State) {
- _state.send(newState)
+ _state.value = newState
}
/**
diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/db/DatabaseCoordinator.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/db/DatabaseCoordinator.kt
index 81ac8b78..dd02b0ba 100644
--- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/db/DatabaseCoordinator.kt
+++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/db/DatabaseCoordinator.kt
@@ -260,11 +260,11 @@ internal class DatabaseCoordinator private constructor(context: Context) {
}
/**
- * The purpose of this function is to move database files between the old location (given by
- * the legacyLocationDbFile parameter) and the new location (given by preferredLocationDbFile).
- * The actual move operation is performed with the renameTo function, which simply renames
- * a file path and persists the metadata information. The mechanism deals with the additional
- * database files -journal and -wal too, if they exist.
+ * The purpose of this function is to move database files between the old location (given by the {@code
+ * legacyLocationDbFile} parameter) and the new location (given by {@code preferredLocationDbFile}). The actual
+ * move operation is performed with the renameTo function, which simply renames a file path and persists the
+ * metadata information. The mechanism deals with the additional database files -journal and -wal too, if they
+ * exist.
*
* @param legacyLocationDbFile the previously used file location (rename from)
* @param preferredLocationDbFile the newly used file location (rename to)
diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt
index 824f2242..bb5adfd4 100644
--- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt
+++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/exception/Exceptions.kt
@@ -1,5 +1,6 @@
package cash.z.ecc.android.sdk.exception
+import cash.z.ecc.android.sdk.internal.SaplingParameters
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
@@ -158,7 +159,9 @@ sealed class BirthdayException(message: String, cause: Throwable? = null) : SdkE
"Unable to find birthday that exactly matches $birthday.${
if (nearestMatch != null) {
" An exact match was request but the nearest match found was ${nearestMatch.height}."
- } else ""
+ } else {
+ ""
+ }
}"
)
class BirthdayFileNotFoundException(directory: String, height: BlockHeight?) : BirthdayException(
@@ -268,8 +271,18 @@ sealed class LightWalletException(message: String, cause: Throwable? = null) : S
/**
* Potentially user-facing exceptions thrown while encoding transactions.
*/
-sealed class TransactionEncoderException(message: String, cause: Throwable? = null) : SdkException(message, cause) {
- class FetchParamsException(message: String) : TransactionEncoderException("Failed to fetch params due to: $message")
+sealed class TransactionEncoderException(
+ message: String,
+ cause: Throwable? = null
+) : SdkException(message, cause) {
+ class FetchParamsException internal constructor(
+ internal val parameters: SaplingParameters,
+ message: String
+ ) : TransactionEncoderException("Failed to fetch params: $parameters, due to: $message")
+ class ValidateParamsException internal constructor(
+ internal val parameters: SaplingParameters,
+ message: String
+ ) : TransactionEncoderException("Failed to validate fetched params: $parameters, due to:$message")
object MissingParamsException : TransactionEncoderException(
"Cannot send funds due to missing spend or output params and attempting to download them failed."
)
diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/CurrencyFormatter.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/CurrencyFormatter.kt
index 7d35054e..6ae5a194 100644
--- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/CurrencyFormatter.kt
+++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/CurrencyFormatter.kt
@@ -363,8 +363,11 @@ fun String.toAbbreviatedAddress(startLength: Int = 8, endLength: Int = 8) =
* @return the masked version of this string, typically for use in logs.
*/
internal fun String.masked(addressCharsToShow: Int = 4): String =
- if (startsWith("ztest") || startsWith("zs")) "****${takeLast(addressCharsToShow)}"
- else "***masked***"
+ if (startsWith("ztest") || startsWith("zs")) {
+ "****${takeLast(addressCharsToShow)}"
+ } else {
+ "***masked***"
+ }
/**
* Convenience function that returns true when this string starts with 'z'.
diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ZcashSdk.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ZcashSdk.kt
index 3def355c..e1f02bb1 100644
--- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ZcashSdk.kt
+++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/ext/ZcashSdk.kt
@@ -74,23 +74,6 @@ object ZcashSdk {
*/
const val REWIND_DISTANCE = 10
- /**
- * File name for the sappling spend params
- */
- const val SPEND_PARAM_FILE_NAME = "sapling-spend.params"
-
- /**
- * File name for the sapling output params
- */
- const val OUTPUT_PARAM_FILE_NAME = "sapling-output.params"
-
- /**
- * The Url that is used by default in zcashd.
- * We'll want to make this externally configurable, rather than baking it into the SDK but
- * this will do for now, since we're using a cloudfront URL that already redirects.
- */
- const val CLOUD_PARAM_DIR_URL = "https://z.cash/downloads/"
-
/**
* The default memo to use when shielding transparent funds.
*/
diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/SaplingParamTool.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/SaplingParamTool.kt
index dcd82204..49767cd3 100644
--- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/SaplingParamTool.kt
+++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/SaplingParamTool.kt
@@ -1,141 +1,344 @@
package cash.z.ecc.android.sdk.internal
+import android.content.Context
import cash.z.ecc.android.sdk.exception.TransactionEncoderException
-import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.internal.ext.deleteRecursivelySuspend
+import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
import cash.z.ecc.android.sdk.internal.ext.existsSuspend
+import cash.z.ecc.android.sdk.internal.ext.getCacheDirSuspend
+import cash.z.ecc.android.sdk.internal.ext.getSha1Hash
import cash.z.ecc.android.sdk.internal.ext.mkdirsSuspend
+import cash.z.ecc.android.sdk.internal.ext.renameToSuspend
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okio.buffer
-import okio.sink
import java.io.File
+import java.io.IOException
+import java.net.URL
+import java.nio.channels.Channels
+import kotlin.time.Duration.Companion.milliseconds
-@Suppress("UtilityClassWithPublicConstructor")
-class SaplingParamTool {
-
+internal class SaplingParamTool(val properties: SaplingParamToolProperties) {
companion object {
/**
- * Checks the given directory for the output and spending params and calls [fetchParams] if
- * they're missing.
- *
- * @param destinationDir the directory where the params should be stored.
+ * Maximum file size for the sapling spend params - 50MB
*/
- suspend fun ensureParams(destinationDir: String) {
- var hadError = false
- arrayOf(
- ZcashSdk.SPEND_PARAM_FILE_NAME,
- ZcashSdk.OUTPUT_PARAM_FILE_NAME
- ).forEach { paramFileName ->
- if (!File(destinationDir, paramFileName).existsSuspend()) {
- twig("WARNING: $paramFileName not found at location: $destinationDir")
- hadError = true
- }
- }
- if (hadError) {
- @Suppress("TooGenericExceptionCaught")
- try {
- Bush.trunk.twigTask("attempting to download missing params") {
- fetchParams(destinationDir)
+ internal const val SPEND_PARAM_FILE_MAX_BYTES_SIZE = 50L * 1024L * 1024L
+
+ /**
+ * Maximum file size for the sapling spend params - 5MB
+ */
+ internal const val OUTPUT_PARAM_FILE_MAX_BYTES_SIZE = 5L * 1024L * 1024L
+
+ /**
+ * Subdirectory name, in which are the sapling params files stored.
+ */
+ internal const val SAPLING_PARAMS_LEGACY_SUBDIRECTORY = "params"
+
+ /**
+ * File name for the sapling spend params
+ */
+ internal const val SPEND_PARAM_FILE_NAME = "sapling-spend.params"
+
+ /**
+ * File name for the sapling output params
+ */
+ internal const val OUTPUT_PARAM_FILE_NAME = "sapling-output.params"
+
+ /**
+ * Temporary file prefix to fulfill atomicity requirement of file handling
+ */
+ private const val TEMPORARY_FILE_NAME_PREFIX = "_"
+
+ /**
+ * File SHA1 hash for the sapling spend params
+ */
+ internal const val SPEND_PARAM_FILE_SHA1_HASH = "a15ab54c2888880e53c823a3063820c728444126"
+
+ /**
+ * File SHA1 hash for the sapling output params
+ */
+ internal const val OUTPUT_PARAM_FILE_SHA1_HASH = "0ebc5a1ef3653948e1c46cf7a16071eac4b7e352"
+
+ /**
+ * The Url that is used by default in zcashd
+ */
+ private const val CLOUD_PARAM_DIR_URL = "https://z.cash/downloads/"
+
+ private val checkFilesMutex = Mutex()
+
+ /**
+ * Initialization of needed properties. This is necessary entry point for other operations from {@code
+ * SaplingParamTool}. This type of implementation also simplifies its testing.
+ *
+ * @param context
+ */
+ internal suspend fun new(context: Context): SaplingParamTool {
+ val paramsDirectory = Files.getZcashNoBackupSubdirectory(context)
+ val toolProperties = SaplingParamToolProperties(
+ paramsDirectory = paramsDirectory,
+ paramsLegacyDirectory = File(context.getCacheDirSuspend(), SAPLING_PARAMS_LEGACY_SUBDIRECTORY),
+ saplingParams = listOf(
+ SaplingParameters(
+ paramsDirectory,
+ SPEND_PARAM_FILE_NAME,
+ SPEND_PARAM_FILE_MAX_BYTES_SIZE,
+ SPEND_PARAM_FILE_SHA1_HASH
+ ),
+ SaplingParameters(
+ paramsDirectory,
+ OUTPUT_PARAM_FILE_NAME,
+ OUTPUT_PARAM_FILE_MAX_BYTES_SIZE,
+ OUTPUT_PARAM_FILE_SHA1_HASH
+ )
+ )
+ )
+ return SaplingParamTool(toolProperties)
+ }
+
+ /**
+ * Returns file object pointing to the parameters files parent directory. We need to check if the parameters
+ * files don't sit in the legacy folder first. If they do, then we move the files to the currently used
+ * directory and validate files hashes.
+ *
+ * @return params destination directory file
+ */
+ internal suspend fun initAndGetParamsDestinationDir(toolProperties: SaplingParamToolProperties): File {
+ checkFilesMutex.withLock {
+ toolProperties.saplingParams.forEach {
+ val legacyFile = File(toolProperties.paramsLegacyDirectory, it.fileName)
+ val currentFile = File(toolProperties.paramsDirectory, it.fileName)
+
+ if (legacyFile.existsSuspend() && isFileHashValid(legacyFile, it.fileHash)) {
+ twig("Moving params file: ${it.fileName} from legacy folder to the currently used folder.")
+ currentFile.parentFile?.mkdirsSuspend()
+ if (!renameParametersFile(legacyFile, currentFile)) {
+ twig("Failed while moving the params file: ${it.fileName} to the preferred location.")
+ }
+ } else {
+ twig(
+ "Legacy file either does not exist or is not valid. Will be fetched to the preferred " +
+ "location."
+ )
}
- } catch (e: Throwable) {
- twig("failed to fetch params due to: $e")
- throw TransactionEncoderException.MissingParamsException
}
+ // remove the params folder and its files - a new sapling files will be fetched to the preferred
+ // location
+ toolProperties.paramsLegacyDirectory.deleteRecursivelySuspend()
+ }
+
+ return toolProperties.paramsDirectory
+ }
+
+ /**
+ * Compares the input file parameter SHA1 hash with the given input hash.
+ *
+ * @param parametersFile file of which SHA1 hash will be checked
+ * @param fileHash hash to compare with
+ *
+ * @return true in case of hashes are the same, false otherwise
+ */
+ private suspend fun isFileHashValid(parametersFile: File, fileHash: String): Boolean {
+ return try {
+ fileHash == parametersFile.getSha1Hash()
+ } catch (e: IOException) {
+ twig("Failed in comparing file's hashes with: ${e.message}, caused by: ${e.cause}.")
+ false
}
}
/**
- * Download and store the params into the given directory.
+ * The purpose of this function is to rename parameters file from the old name (given by the {@code
+ * fromParamFile} parameter) to the new name (given by {@code toParamFile}). This operation covers also the file
+ * move, if it's in a different location.
*
- * @param destinationDir the directory where the params will be stored. It's assumed that we
- * have write access to this directory. Typically, this should be the app's cache directory
- * because it is not harmful if these files are cleared by the user since they are downloaded
- * on-demand.
+ * @param fromParamFile the previously used file name/location
+ * @param toParamFile the newly used file name/location
*/
- suspend fun fetchParams(destinationDir: String) {
- val client = createHttpClient()
- var failureMessage = ""
- arrayOf(
- ZcashSdk.SPEND_PARAM_FILE_NAME,
- ZcashSdk.OUTPUT_PARAM_FILE_NAME
- ).forEach { paramFileName ->
- val url = "${ZcashSdk.CLOUD_PARAM_DIR_URL}/$paramFileName"
- val request = Request.Builder().url(url).build()
- val response = withContext(Dispatchers.IO) { client.newCall(request).execute() }
- if (response.isSuccessful) {
- twig("fetch succeeded", -1)
- val file = File(destinationDir, paramFileName)
- if (file.parentFile?.existsSuspend() == true) {
- twig("directory exists!", -1)
- } else {
- twig("directory did not exist attempting to make it")
- file.parentFile?.mkdirsSuspend()
- }
- withContext(Dispatchers.IO) {
- response.body?.let { body ->
- body.source().use { source ->
- file.sink().buffer().use { sink ->
- twig("writing to $file")
- sink.writeAll(source)
- }
- }
+ private suspend fun renameParametersFile(
+ fromParamFile: File,
+ toParamFile: File
+ ): Boolean {
+ return runCatching {
+ return@runCatching fromParamFile.renameToSuspend(toParamFile)
+ }.onFailure {
+ twig("Failed while renaming parameters file with: $it")
+ }.getOrDefault(false)
+ }
+ }
+
+ /**
+ * Checks the given directory for the output and spending params and calls [fetchParams] for those, which are
+ * missing.
+ *
+ * Note: Don't forget to call the entry point function {@code initSaplingParamTool} first. Make sure you also
+ * called {@code initAndGetParamsDestinationDir} previously, as it's always better to check the
+ * legacy destination folder first.
+ *
+ * @param destinationDir the directory where the params should be stored.
+ *
+ * @throws TransactionEncoderException.MissingParamsException in case of failure while checking sapling params
+ * @throws TransactionEncoderException.FetchParamsException
+ * @throws TransactionEncoderException.ValidateParamsException
+ * files
+ */
+ @Throws(
+ TransactionEncoderException.ValidateParamsException::class,
+ TransactionEncoderException.FetchParamsException::class,
+ TransactionEncoderException.MissingParamsException::class
+ )
+ internal suspend fun ensureParams(destinationDir: File) {
+ properties.saplingParams.filter {
+ !File(it.destinationDirectory, it.fileName).existsSuspend()
+ }.forEach {
+ try {
+ twig("Attempting to download missing params: ${it.fileName}.")
+ fetchParams(it)
+ } catch (e: TransactionEncoderException.FetchParamsException) {
+ twig(
+ "Failed to fetch param file ${it.fileName} due to: $e. The second attempt is starting with a " +
+ "little delay."
+ )
+ // Re-run the fetch with a little delay, if it failed previously (as it can be caused by network
+ // conditions). We do it only once, the next failure is delivered to the caller of this method.
+ delay(200.milliseconds)
+ fetchParams(it)
+ } catch (e: TransactionEncoderException.ValidateParamsException) {
+ twig(
+ "Failed to validate fetched param file ${it.fileName} due to: $e. The second attempt is starting" +
+ " now."
+ )
+ // Re-run the fetch for invalid param file immediately, if it failed previously. We do it again only
+ // once, the next failure is delivered to the caller of this method.
+ fetchParams(it)
+ }
+ }
+
+ if (!validate(destinationDir)) {
+ twig("Fetching sapling params files failed.")
+ throw TransactionEncoderException.MissingParamsException
+ }
+ }
+
+ /**
+ * Download and store the params file into the given directory. It also checks the file size to eliminate the
+ * risk of downloading potentially large file from a malicious server.
+ *
+ * @param paramsToFetch parameters wrapper class, which holds information about it
+ *
+ * @throws TransactionEncoderException.FetchParamsException if any error while downloading the params file occurs
+ * @throws TransactionEncoderException.ValidateParamsException if a failure in validation of fetched file occurs
+ */
+ @Throws(
+ TransactionEncoderException.ValidateParamsException::class,
+ TransactionEncoderException.FetchParamsException::class
+ )
+ internal suspend fun fetchParams(paramsToFetch: SaplingParameters) {
+ val url = URL("$CLOUD_PARAM_DIR_URL/${paramsToFetch.fileName}")
+ val temporaryFile = File(
+ paramsToFetch.destinationDirectory,
+ "$TEMPORARY_FILE_NAME_PREFIX${paramsToFetch.fileName}"
+ )
+
+ withContext(Dispatchers.IO) {
+ runCatching {
+ Channels.newChannel(url.openStream()).use { readableByteChannel ->
+ temporaryFile.outputStream().use { fileOutputStream ->
+ fileOutputStream.channel.use { fileChannel ->
+ // Transfers bytes from stream to file from position 0 to end position or to max
+ // file size limit. This eliminates the risk of downloading potentially large files
+ // from a malicious server. We need to make a check of the file hash then.
+ fileChannel.transferFrom(readableByteChannel, 0, paramsToFetch.fileMaxSizeBytes)
}
}
- } else {
- failureMessage += "Error while fetching $paramFileName : $response\n"
- twig(failureMessage)
}
-
- twig("fetch succeeded, done writing $paramFileName")
- }
- if (failureMessage.isNotEmpty()) throw TransactionEncoderException.FetchParamsException(
- failureMessage
- )
- }
-
- suspend fun clear(destinationDir: String) {
- if (validate(destinationDir)) {
- arrayOf(
- ZcashSdk.SPEND_PARAM_FILE_NAME,
- ZcashSdk.OUTPUT_PARAM_FILE_NAME
- ).forEach { paramFileName ->
- val file = File(destinationDir, paramFileName)
- if (file.deleteRecursivelySuspend()) {
- twig("Files deleted successfully")
- } else {
- twig("Error: Files not able to be deleted!")
- }
+ }.onFailure { exception ->
+ // IllegalArgumentException - If the preconditions on the parameters do not hold
+ // NonReadableChannelException - If the source channel was not opened for reading
+ // NonWritableChannelException - If this channel was not opened for writing
+ // ClosedChannelException - If either this channel or the source channel is closed
+ // AsynchronousCloseException - If another thread closes either channel while the transfer is
+ // in progress
+ // ClosedByInterruptException - If another thread interrupts the current thread while the
+ // transfer is in progress, thereby closing both channels and setting the current thread's
+ // interrupt status
+ // IOException - If some other I/O error occurs
+ finalizeAndReportError(
+ temporaryFile,
+ exception = TransactionEncoderException.FetchParamsException(
+ paramsToFetch,
+ "Error while fetching ${paramsToFetch.fileName}, caused by $exception."
+ )
+ )
+ }.onSuccess {
+ twig(
+ "Fetch and write of the temporary ${temporaryFile.name} succeeded. Validating and moving it to " +
+ "the final destination."
+ )
+ if (!isFileHashValid(temporaryFile, paramsToFetch.fileHash)) {
+ finalizeAndReportError(
+ temporaryFile,
+ exception = TransactionEncoderException.ValidateParamsException(
+ paramsToFetch,
+ "Failed while validating fetched param file: ${paramsToFetch.fileName}."
+ )
+ )
+ }
+ val resultFile = File(paramsToFetch.destinationDirectory, paramsToFetch.fileName)
+ if (!renameParametersFile(temporaryFile, resultFile)) {
+ finalizeAndReportError(
+ temporaryFile,
+ resultFile,
+ exception = TransactionEncoderException.ValidateParamsException(
+ paramsToFetch,
+ "Failed while renaming result param file: ${paramsToFetch.fileName}."
+ )
+ )
}
}
}
+ }
- suspend fun validate(destinationDir: String): Boolean {
- return arrayOf(
- ZcashSdk.SPEND_PARAM_FILE_NAME,
- ZcashSdk.OUTPUT_PARAM_FILE_NAME
- ).all { paramFileName ->
- File(destinationDir, paramFileName).existsSuspend()
- }.also {
- println("Param files${if (!it) "did not" else ""} both exist!")
- }
+ @Throws(TransactionEncoderException.FetchParamsException::class)
+ private suspend fun finalizeAndReportError(vararg files: File, exception: TransactionEncoderException) {
+ files.forEach {
+ it.deleteSuspend()
}
+ exception.also {
+ twig(it)
+ throw it
+ }
+ }
- //
- // Helpers
- //
- /**
- * Http client is only used for downloading sapling spend and output params data, which are
- * necessary for the wallet to scan blocks.
- *
- * @return an http client suitable for downloading params data.
- */
- private fun createHttpClient(): OkHttpClient {
- // TODO [#686]: add logging and timeouts
- // TODO [#686]: https://github.com/zcash/zcash-android-wallet-sdk/issues/686
- return OkHttpClient()
+ internal suspend fun validate(destinationDir: File): Boolean {
+ return arrayOf(
+ SPEND_PARAM_FILE_NAME,
+ OUTPUT_PARAM_FILE_NAME
+ ).all { paramFileName ->
+ File(destinationDir, paramFileName).existsSuspend()
+ }.also {
+ twig("Param files ${if (!it) "did not" else ""} both exist!")
}
}
}
+
+/**
+ * Sapling file parameter class to hold each sapling file attributes.
+ */
+internal data class SaplingParameters(
+ val destinationDirectory: File,
+ val fileName: String,
+ val fileMaxSizeBytes: Long,
+ val fileHash: String
+)
+
+/**
+ * Sapling param tool helper properties. The goal of this implementation is to ease its testing.
+ */
+internal data class SaplingParamToolProperties(
+ val saplingParams: List,
+ val paramsDirectory: File,
+ val paramsLegacyDirectory: File
+)
diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/FileExt.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/FileExt.kt
index 5c4a98dd..7926bf3f 100644
--- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/FileExt.kt
+++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/FileExt.kt
@@ -5,6 +5,9 @@ package cash.z.ecc.android.sdk.internal.ext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
+import java.io.FileInputStream
+import java.security.DigestInputStream
+import java.security.MessageDigest
internal suspend fun File.deleteSuspend() = withContext(Dispatchers.IO) { delete() }
@@ -17,3 +20,37 @@ internal suspend fun File.canWriteSuspend() = withContext(Dispatchers.IO) { canW
internal suspend fun File.renameToSuspend(dest: File) = withContext(Dispatchers.IO) { renameTo(dest) }
suspend fun File.deleteRecursivelySuspend() = withContext(Dispatchers.IO) { deleteRecursively() }
+
+suspend fun File.listFilesSuspend(): Array? = withContext(Dispatchers.IO) { listFiles() }
+
+suspend fun File.inputStreamSuspend(): FileInputStream = withContext(Dispatchers.IO) { inputStream() }
+
+suspend fun File.createNewFileSuspend() = withContext(Dispatchers.IO) { createNewFile() }
+
+/**
+ * Preferred buffer size. We use the same buffer size as BufferedInputStream does.
+ */
+private const val BUFFER_SIZE_BYTES_SIZE = 8192
+
+/**
+ * Encrypts File to SHA1 format.
+ *
+ * @return String SHA1 encryption of the input file
+ */
+suspend fun File.getSha1Hash(): String {
+ return withContext(Dispatchers.IO) {
+ val messageDigest = MessageDigest.getInstance("SHA-1")
+ inputStreamSuspend().use { fis ->
+ DigestInputStream(fis, messageDigest).use { dis ->
+ val buffer = ByteArray(BUFFER_SIZE_BYTES_SIZE)
+ while (dis.read(buffer) >= 0) {
+ // reading the whole buffered stream, which results in update on the message digest
+ }
+ return@withContext messageDigest.digest().joinToString(
+ separator = "",
+ transform = { "%02x".format(it) }
+ )
+ }
+ }
+ }
+}
diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/android/ComputableFlow.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/android/ComputableFlow.kt
index b9829e45..c4550b84 100644
--- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/android/ComputableFlow.kt
+++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/ext/android/ComputableFlow.kt
@@ -3,37 +3,29 @@ package cash.z.ecc.android.sdk.internal.ext.android
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ObsoleteCoroutinesApi
-import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.channels.ConflatedBroadcastChannel
-import kotlinx.coroutines.flow.asFlow
-import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
/* Adapted from ComputableLiveData */
-// TODO [#658] https://github.com/zcash/zcash-android-wallet-sdk/issues/658
-@Suppress("DEPRECATION")
-@OptIn(ObsoleteCoroutinesApi::class)
abstract class ComputableFlow(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
- private val computationScope: CoroutineScope = CoroutineScope(dispatcher + SupervisorJob())
- private val computationChannel: ConflatedBroadcastChannel = ConflatedBroadcastChannel()
- internal val flow = computationChannel.asFlow().flowOn(dispatcher).onStart {
- invalidate()
- }
+ private val computationScope: CoroutineScope = CoroutineScope(dispatcher)
+ private val computationFlow: MutableSharedFlow = MutableSharedFlow(replay = 1)
+ internal val flow = computationFlow.asSharedFlow().onStart { invalidate() }
/**
* Invalidates the flow.
* This will trigger a call to [.compute].
*/
fun invalidate() {
- computationScope.launch { computationChannel.send(compute()) }
+ computationScope.launch { computationFlow.emit(compute()) }
}
fun cancel() {
computationScope.cancel()
- computationChannel.cancel()
+ computationFlow.resetReplayCache()
}
protected abstract fun compute(): T
diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/WalletTransactionEncoder.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/WalletTransactionEncoder.kt
index 2b7dae14..20581870 100644
--- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/WalletTransactionEncoder.kt
+++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/WalletTransactionEncoder.kt
@@ -6,7 +6,6 @@ import cash.z.ecc.android.sdk.ext.masked
import cash.z.ecc.android.sdk.internal.SaplingParamTool
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.internal.twigTask
-import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.jni.RustBackendWelding
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.Zatoshi
@@ -22,6 +21,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
*/
internal class WalletTransactionEncoder(
private val rustBackend: RustBackendWelding,
+ private val saplingParamTool: SaplingParamTool,
private val repository: TransactionRepository
) : TransactionEncoder {
@@ -122,7 +122,7 @@ internal class WalletTransactionEncoder(
) {
@Suppress("TooGenericExceptionCaught")
try {
- SaplingParamTool.ensureParams((rustBackend as RustBackend).pathParamsDir)
+ saplingParamTool.ensureParams(rustBackend.saplingParamDir)
twig("params exist! attempting to send...")
rustBackend.createToAddress(
usk,
@@ -146,7 +146,7 @@ internal class WalletTransactionEncoder(
return twigTask("creating transaction to shield all UTXOs") {
@Suppress("TooGenericExceptionCaught")
try {
- SaplingParamTool.ensureParams((rustBackend as RustBackend).pathParamsDir)
+ saplingParamTool.ensureParams(rustBackend.saplingParamDir)
twig("params exist! attempting to shield...")
rustBackend.shieldToAddress(
usk,
diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/jni/RustBackend.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/jni/RustBackend.kt
index 76ed2d9c..eb4b82c5 100644
--- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/jni/RustBackend.kt
+++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/jni/RustBackend.kt
@@ -1,7 +1,6 @@
package cash.z.ecc.android.sdk.jni
-import cash.z.ecc.android.sdk.ext.ZcashSdk.OUTPUT_PARAM_FILE_NAME
-import cash.z.ecc.android.sdk.ext.ZcashSdk.SPEND_PARAM_FILE_NAME
+import cash.z.ecc.android.sdk.internal.SaplingParamTool
import cash.z.ecc.android.sdk.internal.SdkDispatchers
import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
import cash.z.ecc.android.sdk.internal.model.Checkpoint
@@ -27,7 +26,7 @@ internal class RustBackend private constructor(
val birthdayHeight: BlockHeight,
val dataDbFile: File,
val cacheDbFile: File,
- val pathParamsDir: String
+ override val saplingParamDir: File
) : RustBackendWelding {
suspend fun clear(clearCacheDb: Boolean = true, clearDataDb: Boolean = true) {
@@ -236,8 +235,8 @@ internal class RustBackend private constructor(
to,
value,
memo ?: ByteArray(0),
- "$pathParamsDir/$SPEND_PARAM_FILE_NAME",
- "$pathParamsDir/$OUTPUT_PARAM_FILE_NAME",
+ File(saplingParamDir, SaplingParamTool.SPEND_PARAM_FILE_NAME).absolutePath,
+ File(saplingParamDir, SaplingParamTool.OUTPUT_PARAM_FILE_NAME).absolutePath,
networkId = network.id
)
}
@@ -252,8 +251,8 @@ internal class RustBackend private constructor(
dataDbFile.absolutePath,
usk.copyBytes(),
memo ?: ByteArray(0),
- "$pathParamsDir/$SPEND_PARAM_FILE_NAME",
- "$pathParamsDir/$OUTPUT_PARAM_FILE_NAME",
+ File(saplingParamDir, SaplingParamTool.SPEND_PARAM_FILE_NAME).absolutePath,
+ File(saplingParamDir, SaplingParamTool.OUTPUT_PARAM_FILE_NAME).absolutePath,
networkId = network.id
)
}
@@ -342,9 +341,9 @@ internal class RustBackend private constructor(
* function once, it is idempotent.
*/
suspend fun init(
- cacheDbPath: String,
- dataDbPath: String,
- paramsPath: String,
+ cacheDbFile: File,
+ dataDbFile: File,
+ saplingParamsDir: File,
zcashNetwork: ZcashNetwork,
birthdayHeight: BlockHeight
): RustBackend {
@@ -353,9 +352,9 @@ internal class RustBackend private constructor(
return RustBackend(
zcashNetwork,
birthdayHeight,
- dataDbFile = File(dataDbPath),
- cacheDbFile = File(cacheDbPath),
- pathParamsDir = paramsPath
+ dataDbFile = dataDbFile,
+ cacheDbFile = cacheDbFile,
+ saplingParamDir = saplingParamsDir
)
}
diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/jni/RustBackendWelding.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/jni/RustBackendWelding.kt
index 539b528e..68855692 100644
--- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/jni/RustBackendWelding.kt
+++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/jni/RustBackendWelding.kt
@@ -8,6 +8,7 @@ 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.UnifiedFullViewingKey
+import java.io.File
/**
* Contract defining the exposed capabilities of the Rust backend.
@@ -20,6 +21,8 @@ internal interface RustBackendWelding {
val network: ZcashNetwork
+ val saplingParamDir: File
+
@Suppress("LongParameterList")
suspend fun createToAddress(
usk: UnifiedSpendingKey,
diff --git a/sdk-lib/src/test/java/cash/z/ecc/android/sdk/internal/ext/android/ComputableFlowTest.kt b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/internal/ext/android/ComputableFlowTest.kt
new file mode 100644
index 00000000..4f3d10b5
--- /dev/null
+++ b/sdk-lib/src/test/java/cash/z/ecc/android/sdk/internal/ext/android/ComputableFlowTest.kt
@@ -0,0 +1,35 @@
+package cash.z.ecc.android.sdk.internal.ext.android
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import kotlin.test.assertEquals
+
+class ComputableFlowTest {
+
+ private class TestComputableFlow : ComputableFlow(dispatcher = Dispatchers.Unconfined) {
+ var computationCounter: Int = 0
+ private set
+
+ override fun compute() = ++computationCounter
+ }
+
+ @Test
+ fun shouldComputeOnInvalidation() {
+ val testComputableFlow = TestComputableFlow()
+
+ testComputableFlow.invalidate()
+ testComputableFlow.invalidate()
+
+ assertEquals(2, testComputableFlow.computationCounter)
+ }
+
+ @Test
+ fun shouldInvalidateOnCollection() = runBlocking {
+ val testComputableFlow = TestComputableFlow()
+ testComputableFlow.flow.first()
+
+ assertEquals(1, testComputableFlow.computationCounter)
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 1c9046c7..c3bd3bc0 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -92,8 +92,6 @@ dependencyResolutionManagement {
val kotlinxCoroutinesVersion = extra["KOTLINX_COROUTINES_VERSION"].toString()
val mockitoKotlinVersion = extra["MOCKITO_KOTLIN_VERSION"].toString()
val mockitoVersion = extra["MOCKITO_VERSION"].toString()
- val okhttpVersion = extra["OKHTTP_VERSION"].toString()
- val okioVersion = extra["OKIO_VERSION"].toString()
val protocVersion = extra["PROTOC_VERSION"].toString()
val rustGradlePluginVersion = extra["RUST_GRADLE_PLUGIN_VERSION"].toString()
val zcashWalletPluginVersion = extra["ZCASH_WALLET_PLUGINS_VERSION"].toString()
@@ -141,8 +139,6 @@ dependencyResolutionManagement {
library("kotlinx-coroutines-android", "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinxCoroutinesVersion")
library("kotlinx-coroutines-core", "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion")
library("material", "com.google.android.material:material:$googleMaterialVersion")
- library("okhttp", "com.squareup.okhttp3:okhttp:$okhttpVersion")
- library("okio", "com.squareup.okio:okio:$okioVersion")
library("zcashwalletplgn", "com.github.zcash:zcash-android-wallet-plugins:$zcashWalletPluginVersion")
// Test libraries