Merge branch 'main' of github.com:zcash/zcash-android-wallet-sdk into merge-main

# Conflicts:
#	CHANGELOG.md
#	sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/ext/TestExtensions.kt
#	sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/SanityTest.kt
#	sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/SmokeTest.kt
#	sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/AddressGeneratorUtil.kt
#	sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/BalancePrinterUtil.kt
#	sdk-lib/src/main/java/cash/z/ecc/android/sdk/Initializer.kt
#	sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt
#	sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt
#	sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/WalletTransactionEncoder.kt
#	sdk-lib/src/main/java/cash/z/ecc/android/sdk/jni/RustBackendWelding.kt
This commit is contained in:
Carter Jernigan 2022-10-17 16:11:16 -04:00
commit f2effc8dd2
97 changed files with 1701 additions and 643 deletions

View File

@ -8,7 +8,7 @@ runs:
run: |
echo "home=${HOME}" >> "$GITHUB_ENV"
- name: Set up Java
uses: actions/setup-java@2c7a4878f5d120bd643426d54ae1209b29cc01a3
uses: actions/setup-java@a18c333f3f14249953dab3e186e5e21bf3390f1d
with:
distribution: 'temurin'
java-version: 17
@ -31,13 +31,13 @@ runs:
echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties
- name: Gradle Wrapper Cache
id: gradle-wrapper-cache
uses: actions/cache@a7c34adf76222e77931dedbf4a45b2e4648ced19
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7
with:
path: ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles(format('{0}{1}', github.workspace, '/gradle/wrapper/gradle-wrapper.properties')) }}
- name: Gradle Dependency Cache
id: gradle-dependency-cache
uses: actions/cache@a7c34adf76222e77931dedbf4a45b2e4648ced19
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7
with:
path: ~/.gradle/caches/modules-2
key: ${{ runner.os }}-gradle-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/gradle.properties')) }}
@ -48,7 +48,7 @@ runs:
# 2. Relying on the sha for an exact match so that the prime_cache job is re-used by all dependent jobs in a single workflow run
- name: Gradle Build Cache
id: gradle-build-cache
uses: actions/cache@a7c34adf76222e77931dedbf4a45b2e4648ced19
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7
with:
path: |
~/.gradle/caches/build-cache-1
@ -59,7 +59,7 @@ runs:
${{ runner.os }}-gradle-build-
- name: Rust Cache
id: rust-cache
uses: actions/cache@a7c34adf76222e77931dedbf4a45b2e4648ced19
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7
with:
path: |
sdk-lib/target

View File

@ -18,7 +18,7 @@ jobs:
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
# Gradle Wrapper validation can be flaky
# https://github.com/gradle/wrapper-validation-action/issues/40
- name: Gradle Wrapper Validation
@ -51,7 +51,7 @@ jobs:
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
- name: Setup
id: setup
timeout-minutes: 30
@ -87,7 +87,7 @@ jobs:
- name: Upload Artifacts
if: ${{ always() }}
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8
timeout-minutes: 1
timeout-minutes: 2
with:
name: Release binaries
path: ~/artifacts

View File

@ -28,7 +28,7 @@ jobs:
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
# Gradle Wrapper validation can be flaky
# https://github.com/gradle/wrapper-validation-action/issues/40
- name: Gradle Wrapper Validation
@ -59,7 +59,7 @@ jobs:
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
- name: Setup
id: setup
timeout-minutes: 30
@ -94,7 +94,7 @@ jobs:
- name: Upload Artifacts
if: ${{ always() }}
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8
timeout-minutes: 1
timeout-minutes: 2
with:
name: Snapshot binaries
path: ~/artifacts

View File

@ -27,7 +27,7 @@ jobs:
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
# Gradle Wrapper validation can be flaky
# https://github.com/gradle/wrapper-validation-action/issues/40
- name: Gradle Wrapper Validation
@ -42,7 +42,7 @@ jobs:
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
- name: Setup
id: setup
timeout-minutes: 30
@ -80,7 +80,7 @@ jobs:
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
- name: Setup
id: setup
timeout-minutes: 5
@ -115,7 +115,7 @@ jobs:
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
- name: Setup
id: setup
timeout-minutes: 5
@ -150,7 +150,7 @@ jobs:
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
- name: Setup
id: setup
timeout-minutes: 5
@ -163,6 +163,7 @@ jobs:
run: |
./gradlew :sdk-lib:lintRelease :demo-app:lintZcashmainnetRelease
- name: Collect Artifacts
if: ${{ always() }}
timeout-minutes: 1
env:
ARTIFACTS_DIR_PATH: ${{ format('{0}/artifacts', env.home) }}
@ -171,6 +172,7 @@ jobs:
mkdir ${ARTIFACTS_DIR_PATH}
zip -r ${LINT_ZIP_PATH} . -i \*build/reports/\*
- name: Upload Artifacts
if: ${{ always() }}
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8
timeout-minutes: 1
with:
@ -185,7 +187,7 @@ jobs:
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
- name: Setup
id: setup
timeout-minutes: 5
@ -223,7 +225,7 @@ jobs:
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
- name: Setup
id: setup
timeout-minutes: 5
@ -234,7 +236,7 @@ jobs:
./gradlew assembleDebug assembleAndroidTest
- name: Authenticate to Google Cloud for Firebase Test Lab
id: auth_test_lab
uses: google-github-actions/auth@ceee102ec2387dd9e844e01b530ccd4ec87ce955
uses: google-github-actions/auth@c4799db9111fba4461e9f9da8732e5057b394f72
with:
create_credentials_file: true
project_id: ${{ secrets.FIREBASE_TEST_LAB_PROJECT }}
@ -278,7 +280,7 @@ jobs:
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
- name: Setup
id: setup
timeout-minutes: 5
@ -315,7 +317,7 @@ jobs:
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
- name: Setup
id: setup
timeout-minutes: 5
@ -361,17 +363,18 @@ jobs:
permissions:
packages: read
contents: read
id-token: write
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
- name: Setup
id: setup
timeout-minutes: 5
uses: ./.github/actions/setup
- name: Authenticate to Google Cloud for Firebase Test Lab
id: auth_test_lab
uses: google-github-actions/auth@ceee102ec2387dd9e844e01b530ccd4ec87ce955
uses: google-github-actions/auth@c4799db9111fba4461e9f9da8732e5057b394f72
with:
create_credentials_file: true
project_id: ${{ secrets.FIREBASE_TEST_LAB_PROJECT }}
@ -381,7 +384,7 @@ jobs:
- name: Download a single artifact
uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741
with:
name: Release binaries
name: Demo app release binaries
- name: Robo test
timeout-minutes: 15
env:

View File

@ -4,12 +4,14 @@
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="detektAll" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list />
<list>
<option value="detektAll" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>

View File

@ -4,14 +4,19 @@
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="assembleAndroidTest assembleDebug assembleZcashmainnetDebug assembleZcashtestnetDebug" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list />
<list>
<option value="assembleAndroidTest" />
<option value="assembleDebug" />
<option value="assembleZcashmainnetDebug" />
<option value="assembleZcashtestnetDebug" />
</list>
</option>
<option name="vmOptions" />
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>

View File

@ -4,12 +4,14 @@
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="ktlintFormat" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list />
<list>
<option value="ktlintFormat" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>

View File

@ -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

View File

@ -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

View File

@ -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<ManagedVirtualDevice>("pixel2Min") {
device = "Pixel 2"
apiLevel = testDeviceMinSdkVersion
systemImageSource = "aosp"
}
create<ManagedVirtualDevice>("pixel2Target") {
device = "Pixel 2"
apiLevel = testDeviceMaxSdkVersion
systemImageSource = "aosp"
}
}
}
}
if (this is CommonExtension<*, *, *, *>) {

View File

@ -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")

View File

@ -6,6 +6,8 @@ plugins {
}
android {
namespace = "cash.z.ecc.android.sdk.darkside"
defaultConfig {
//targetSdk = 30 //Integer.parseInt(project.property("targetSdkVersion"))
multiDexEnabled = true

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cash.z.ecc.android.sdk.darkside">
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- For code coverage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

View File

@ -211,7 +211,7 @@ class TestWallet(
1_330_190
),
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
),
)
;
}
}

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cash.z.ecc.android.sdk.darkside">
xmlns:android="http://schemas.android.com/apk/res/android">
<application android:name="androidx.multidex.MultiDexApplication" />
</manifest>

View File

@ -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())

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cash.z.ecc.android.sdk.demoapp">
xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".App"

View File

@ -156,12 +156,12 @@ class MainActivity :
/* DrawerListener implementation */
@Suppress("EmptyFunctionBlock")
override fun onDrawerStateChanged(newState: Int) {
twig("Drawer state changed to: $newState.")
}
@Suppress("EmptyFunctionBlock")
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
twig("Drawer slides with offset: $slideOffset.")
}
override fun onDrawerClosed(drawerView: View) {

View File

@ -9,7 +9,6 @@ import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentHomeBinding
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
/**
@ -113,7 +112,9 @@ class HomeFragment : BaseDemoFragment<FragmentHomeBinding>() {
val lastSpace = lastIndexOf(' ')
return if (firstSpace != -1 && lastSpace >= firstSpace) {
"${take(firstSpace)}...${takeLast(length - 1 - lastSpace)}"
} else this
} else {
this
}
}
}
}

View File

@ -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

View File

@ -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)
}

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="cash.z.ecc.android.sdk.demoapp.demos.gallery.GalleryFragment">
<TextView
android:id="@+id/text_gallery"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -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"

View File

@ -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"

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="cash.z.ecc.android.sdk.demoapp.demos.home.HomeSecondFragment">
<TextView
android:id="@+id/textview_home_second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@id/button_home_second"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_home_second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/previous"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textview_home_second" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -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"

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="cash.z.ecc.android.sdk.demoapp.demos.slideshow.SlideshowFragment">
<TextView
android:id="@+id/text_slideshow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -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
* 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.

View File

@ -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

View File

@ -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.

View File

@ -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.
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.

View File

@ -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.

View File

@ -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
ANDROID_JVM_TARGET=1.8

View File

@ -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

10
sdk-lib/Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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)

View File

@ -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

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="cash.z.ecc.android.sdk">
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- For code coverage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

View File

@ -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
}
/**

View File

@ -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())
}
}
}

View File

@ -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."
)
}
}
}

View File

@ -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")
}

View File

@ -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"
)
)
}

View File

@ -46,7 +46,9 @@ class SmokeTest {
)
assertTrue(
"Invalid CacheDB params dir",
rustBackend.pathParamsDir.endsWith("cache/params")
rustBackend.saplingParamDir.endsWith(
"no_backup/co.electricoin.zcash"
)
)
}

View File

@ -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<TransactionEncoderException.MissingParamsException> {
saplingParamTool.ensureParams(SaplingParamToolFixture.PARAMS_DIRECTORY)
}
}
}

View File

@ -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<TransactionEncoderException.FetchParamsException> {
saplingParamTool.fetchParams(spendSaplingParams)
}
assertFalse(saplingParamTool.validate(SaplingParamsFixture.DESTINATION_DIRECTORY))
}
@Test
@LargeTest
fun fetch_params_incorrect_hash_test() = runTest {
val saplingParamTool = SaplingParamTool.new(getAppContext())
assertFailsWith<TransactionEncoderException.ValidateParamsException> {
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<TransactionEncoderException.ValidateParamsException> {
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<TransactionEncoderException.FetchParamsException> {
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<TransactionEncoderException.ValidateParamsException> {
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())
}
}

View File

@ -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)
}
}

View File

@ -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),

View File

@ -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<String> {
val testFile = javaClass.getResourceAsStream(filePathName)
assertNotNull(testFile, "Test file read failure.")
emitAll(testFile.bufferedReader().lineSequence().asFlow())
}.flowOn(Dispatchers.IO)

View File

@ -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<String> {
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()
}
}
}
}

View File

@ -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<String>()
// return Wallet(

View File

@ -213,7 +213,7 @@ class TestWallet(
1_330_190
),
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
),
)
;
}
}

View File

@ -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
}
}

View File

@ -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<SaplingParameters> = SAPLING_PARAMS_FILES,
paramsDirectory: File = PARAMS_DIRECTORY,
paramsLegacyDirectory: File = PARAMS_LEGACY_DIRECTORY
) = SaplingParamToolProperties(
saplingParams = saplingParamsFiles,
paramsDirectory = paramsDirectory,
paramsLegacyDirectory = paramsLegacyDirectory
)
}

View File

@ -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()
}
}
}

View File

@ -1,5 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cash.z.ecc.android.sdk">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1767500",
"hash": "000000000062aefe96e5bdd113d850b89646e8ab600ddcbd57c2c3cca9ed9cdb",
"time": 1660081334,
"saplingTree": "017cc5d6752832ddd99fa1bd6e3c7f2113986ad5d5be3068bfe14243212e717257001800000157aab5fa6cc81d5a4ca70c0cb0c2bac34804dc237dc817418d45df04ac46ac3f01f5edbbffb54d8c60445812ce336ba434bb424f6ae71e1b3a84c522d345a973180001c5e144ba6cda0551733ebc2bbe098ab386b45c5a93012a3e5b756beca22c6c3e01dbe2ae2d51059351deaab0cc2bc8d56e71fefeee47491749b9cade247f576b0f01c75ccd49fcefece91dad36bf2c57808e290f33209375bd75ed26dcdfda27e1650177925c91eb349eeaa311898dae3c2832fece99426f877e73058780550acc0331000161343f9954a5082d850e2670038205296798f6080822deb44fb4c280f55f974501fc08c52c0f49521e463a91c9a3eb605b811b3916e82abfa6e2e004662cbce33000000191de3df92b233b4bc9ca3e9d5cd49e832eef5178c6ebaf3e0b1a1c5f748fa237000001f6d2aae532a8ef603f8d0e3484b9013e00f525afcc5c725aaaa531ca97514c030001a9ebb083071b5c81251c1c09b08606288769a32e79967db3fa8138075aa5825d000115c91e5fc9fd96cbf12a77777f1781cce55113f548b87f067f2d7d1af81dfa56018ed21e6b0098dd6f0902efde81f9d5da8d7068e8dcf01ab1c015f8f5936c2d5f017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
"orchardTree": "011354cb2375c9f06e3c3a1e21549f0d582b5b24b562880c3e12d41751d0878c3a0178e9447cb89b46eedb06346450cfca4680cf6e2d5f71aaea92cfbd0c4ee8ef181f000000011a0bb2dae9ccd72fcafab242a08d3e16a31f0699d440cef290f0998c378f003a011250096a18c4e0ac40bf72bedf005b97e24096b473d6f4422335fd67e485910c01e6338965eb59f946b5bce375e43414c829116025645d4fb50b49c3fa5f65fd2f00014735a76fb3ca0190df1e1b2a64ff0dd2b875d8d972d1e49333b7720e1d80233c01725410fb3633b5a3b484f386f31ad0a8372e744b8ebad06f0b267e8181ee3d1e00000001be342cefff27be290a0c6688149ec5078a5bb559e590773823e486c6d7a10111015a78bd51e8c203d1d4960791f29412b0ebd8e918a5a34203dac112fc5a07750e010c29539431c68b879515fe688b31c4c5c8479b5bff69c50207314b769450a32e01f2fb309ae3c4de7584b0c2974bf0b92af35ff03420b8e210009227648eef393d000001a111bede6c68ba8795c99c17b615d062944718ff12e5d890a90128409860982101374feb2041bfd423c6cc3e064ee2b4705748a082836d39dd723515357fb06e300000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1770000",
"hash": "0000000000c72df21cd00410b6ac1eabe0df652bd1de1acc8f7de0300ad0c579",
"time": 1660269507,
"saplingTree": "01a8a916c0922f6255920368229c9807bd08968d344e4cac1562f61d67acfc1712013b8c478aeb325ce90904248fa79e20088787b4c2f5c8437c4a05368a8e5286131801ac59d1048b60be971ae94994637d43c9c2b5f0096e3010388c15564bacc23d0a019c21f5c2045113d187f254cac44d84eb90e6a2fd13bdeb048ded1c7f6a23a57101f84c590ef2d748f73429528a1fa8c2631153ec0c9826fbf7e9250a3afe967d4f018a2e6f62c3adc2eb82c218c405456c736c0693bc15367db9f8be0098414345190000017645afc760c2f4677e7d0e6a06ec930b414092367c1682683bd98275a57f194e00000000000000017b5ec5fcc4428da2d6818a0b1af313c60954ba660e2e3cda6f450099eb5b221f0001ef818bb1081751e68503a93549f307ffc9c315434df34fcf02a015902b46cc180001e21ed183272df1ff7b85b11dba8cdbc6a4658e6590af95813267526b37026e38000154e243e3e5358aad170a344b945fecf54c40891e8bb5b5d3bfc4630543de7b4c0115c91e5fc9fd96cbf12a77777f1781cce55113f548b87f067f2d7d1af81dfa56018ed21e6b0098dd6f0902efde81f9d5da8d7068e8dcf01ab1c015f8f5936c2d5f017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
"orchardTree": "012b3f0b7513e55d38172c1823c882c72d905ff9202441a232abc52724539f680c017d53c8439f36df116448eadc2bc91d55a0f312a738dfc2137bde4776f9b6a4361f000000018b507a097c3386b3feccd3d9c51821906408e8b83ccbf13100783b0e837aa909000177c4e6e1b75416e4d92aab042e2725e050a5f076b11f2f4d2af70882202f33390001ba5d410df62cf48f7db09537333dc58577284c2715b8410ef15c363a33b7353700016c7e3e4e5cdb142564d4e4cf19d6daab524ec8bd50e53067c526918e906f171601b68907c6a34bc6281e7a5c7d3af957c02b21f39d6f0cf328dcc9d7a9456d8d3b0001e343584412ad061010f7a51650bbedc1158a89e38a94be46f0e5e1951caea61400000001359f584dee78fa97c62744b858526df6e936126f0abcf399e5649895e5d4160101a93fb5f55a0ed85c3dfdd84cafbe185bf7cdcc39df46f20b9e31c023f24f560a01a111bede6c68ba8795c99c17b615d062944718ff12e5d890a90128409860982101374feb2041bfd423c6cc3e064ee2b4705748a082836d39dd723515357fb06e300000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1772500",
"hash": "0000000001109c6049218a78d26eb16b0d5e6a46aa1918b39e8adb9c8d892320",
"time": 1660458773,
"saplingTree": "019ad7b4358d62778021a4080cf37a4217e7e11d2d81b38d3683cc2f8729a8aa3401d2559936f9aa3772ad890675b4c7df6c5a0a796274a172c303e74980c335bf5b1801e2b9ff9e93c5fdd152ea2402a01ee5e9fadca97a37e7843da1cca8703e5d174f01c0107854df305aec5fe7dc0fca338428d843445b7bbc93b782d44e502919530e0000000001715e39e97a1895488da1e615b3ecccfddbc1505376cb74e4a6cff974f4640e15000001c105403889f34c2a05928510ad2f88023b8823c92567ba29e274c596340f516f01bc4a6849c7428c63b06883f7d7c887f010ed34d4eec1b6ccc5cc6f1c9a5eac4401af80584abf7e1d3ce026c3c0adc9d94e9542f708050625a68f2d9dabdf4d1969012a4851fa8b991aeaa5546a9a9705b382d2045da32f86b6f95b5f9f0187b8506301b63830dd5a6d176498d96e4b6329715386e24697025a4148401541d0d745d70900000001d6815a159595ce4f0b7dabb78174abef61c557fbf5179b0b06ac629f5972c26e01b6feee0caca8f28b04cab9a5b7eda707f289719fd2b91c809f603ad23924c56401c0a384a2c6a8bdc8ec47b715670aa3bd71601872d593ad86e2f13ef6c65cbd490154e243e3e5358aad170a344b945fecf54c40891e8bb5b5d3bfc4630543de7b4c0115c91e5fc9fd96cbf12a77777f1781cce55113f548b87f067f2d7d1af81dfa56018ed21e6b0098dd6f0902efde81f9d5da8d7068e8dcf01ab1c015f8f5936c2d5f017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
"orchardTree": "012de2bdc6851434bb9bd9356387a3ef5e2391cc04ee303c5a9c3653e60c26ee3401a6ef18049293bb3cdb59c4d461a9612811b8ae2bca828884131f37603e456b0d1f00000142caeb8a953a2a3e03630aca15b02619af2a10a70de045bbb17fed9254bf6c16000000000001591d52db18cb890feda16bf333c1dfd6e82d76cf8972330ceac6483778eb5d2f00011aa73e3b53df7e1ee91b1ef57f87ac253d680e07b63f72dd11d6922f4aae700f000147cfe08c6d5288825731fb3a99336232656f2cdc8f31bff4ee54e693440e7d3900000001015358ef9ae69af71a7602e351a1184e95854b3936834c7aebaa3774bc648d2701cd7575644cb25d5a1c7edcf4e3552520f2da1b2c98fa04bdf419175fc4a1c708000001f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1775000",
"hash": "000000000019cdcd93e8a0704c40cd74e65424e4bfaaa2a21227db81355ccb46",
"time": 1660646308,
"saplingTree": "01597a94eed997b611665ea28eb0fbebf872dc4187b81baf2d7b061d9498e4be07001901289049f3d2fb297ec300241c8ca66400d8cb214272da11c7dddbf249a83b363200019448787f294385a1cf41c26b5ecc3a9f2b552d85dd959d71ad8fb79ab82f315e0000015692b62820a0affe568750d52d557ae99781acf19545f95404b3d7122e00e50d014c13e6f6cd1938716b4870921418d553fbc13ad5e174c720556e43eeedff992b01d5af984301be600c7cf840dc1fbbcdedb2d2e801813bb68a01be00c6822d5a6a017e978b347c274b38ee016285dd14132e83d59786019d1a46ce0163771dfdce0c000000017b24ec22b8e69bfebe3a24693645c8905f2c3756dd67d646d4266c5624ce29520001887d900f0f1c22d239b925d77200c12a7a637dfc7723f08aa34c0808cd1740200001e9358e668fd2780e13975fa8d46cd6ea762252a2ce849e2c82affb1c58edcb190000013eca32c92486829a47625ea4acb38897025abb7af0d1aba2d12c9e828a8d0a4f000000000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "0182a273f144dba41ed805d348a8eeae5692b737863da75ac3384af983d54fd109001f01f72799b4c4c3ec3048b5c724b0edf553fdac716a6f2e771b8e40be6f9ece050301eec827aaef0952edafa106766d905c03b29a58b8f502b579e18d6b8a36b9a2120001997dd960a2474458cc8d914bf46232ec351eb5b3ff614e455d0fe6cf1c0d961f01726cc90e9a3d35324d807825bc4eceb7c976735bf8d5072f24f916f11231aa2901e4d4b64f108c937551964335b41b8a6e43bca7b2f2c468f86bb27a938ffb912001395fc86db243265b7a2722b3a49c1ac5449d671980bfaaebd1f8b26d4324451f0001056bee64e920ba6e8604fa32b294591abb40b5fc35b9b6f257b3b465fce371270001ea6a4ce4349b49c0b9788bcf94d27cb8c95f8f5a640919110387cd66f063e113017d6446e58ba6f7f8e7f60594b91bb67739986c89c670ad3996205ca9df7eac1100000001188cb8c8ad6ec4d5a9f90689123894bc1d021fb63d53347246ef21657c04f3110000014c58c6ffa8b0fdd89c604006318b7901020edcaca498e3afd81af7579dafcd340001f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1777500",
"hash": "000000000114eb90f65670123d685d4e1ac4208dcc39a9dc1111060789a2c418",
"time": 1660835117,
"saplingTree": "0179c12f2782a76e421834ecfcd800a5cbd1edc79663d3c58d25d14200c60a2b050019000000000000000001091c24ca6d7180e15a22a46f9fa2d0a90783ce8e8bf4346097b36c469f7859220113d4335546307bb7558b2bfa8a309abb341da3f88acfc3b7d0523519a993662701d99f7f7123501008b4b145d772c66240aa9bdfe1fadd8cd420a3f159126950350185b933c8fc56ff06180fc5d2dbaf7234d615c724b1dbcdd4130ddff0ac732e6e000000019fe9488e3027bf007c951a3706ac7422d242c3ece25c20d5fd8254fdc77d883c000177ac81d2104e4de33629aaf3187c8daff10899ef791b1fbb18d2f2c17296870a0000018679bff98f1311c6b1c4df01647556064d2cd34087c8f83fac1ee99babc986600000000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01dde5b2585ed32435eba299a594f328983090c620d3e40c58e2c457c5adbc7835001f01430c6ec1ba7a552b02f897a8789639760786e29fc4ce7e133f4d8768fba4032b0001b99142688612333a2926ebf2b271c50018a4240c9522cdee66b68c4ffa8c3616000172a6fbb0176b644a4a11f6c658324fc5d8140845959cb3c924b190a8ef86e22a0153250a8de904795ee993882737ee5f13458561a0f78d6ab1432e165f0b19ed2f01b54e695a6d674420b22d8b52727140dbde2d83ed45966a2cde91393d89ea6a0101d3f0b23da54e9066a2e4621baa67d255a69f90f788e35975e5fc14ae62eb9b3d012d59e25ff1850760f04238d20341db41c77a069ce6146af0c66693c5a7e84c04014caf0366131977fe12e0ef793ad22fa3f75df7a2bca71acba67bbb206797ff3c0001ce3e5c7e4f546304954681fd0e8f0e68dc38e5c788c99c1cc2a5404c160df93a0001038ca3c0ad85cbc76b94531a2c8d7efc43af084abdc88d2d28b30bc3a8ba4d39000001aa7b1980e5656743063400012808a5a2920431959e0b721c9a915b37ca3b450801c4accd50986a74b77eddf2e5191d20e40035f38bc7c9b91a03339af16c5a7032014c58c6ffa8b0fdd89c604006318b7901020edcaca498e3afd81af7579dafcd340001f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1780000",
"hash": "000000000092cd2f7d70451422ec93bc0c32dbb4b7ba7bfab4c6bb2f646621cf",
"time": 1661023245,
"saplingTree": "01eb57dc27bf63678d8d3a9d19b422a4694a9d43765bd308abd6ac1ee838dc4524018572cb62805c814962aee358456f07f5494cf3fa7b9ec1bc35dcd8f217e0920b19014241b45704d5126780eede03872b8f87501e4184dc68f977aab8e20d1e83da330001be1052772495777a8224029288e3cbce5f6528380c7dfab653661fa068e43267000000000120d655140e8a7069e4bb227ef3be10337d1fe805f6c143042f39e04047dcaa4801edee36cd129d61db744d5c70d749a066ae0903b5310f309609d014d349b7e435000001e4730f055d123a824efda2abf609b64bca407df2582fb188f15876c17a93183f01428fe4cdc82c312139518ae3cfed4fc769329bc192c724029a38dbf221557a56000176138376dab12f9063c0d4be21178499d1f912756be2dda3cb7540469376905901b261f10e8b41673b7707c93d6a38c9b3ca331d7beaf312b17dc5536bf9ac262500018fc9b4ef24688bf42b782c41232f9ec501b1c4ab6c950b0c41d34eb23b17402800017412114010d9ffb2f7387eda6e29942c0c80b6330788102467e4a0afe325382d018679bff98f1311c6b1c4df01647556064d2cd34087c8f83fac1ee99babc986600000000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01b02222e4afcd845dc497c76d04b9f6ddb9081d01b9f9d5a2aa05fbc8d1443b1601a0331c1517205b7e943e9668d61e77e875c662dc96ef7cc96299b6a83890b41d1f0000013c6b5a330ccb2ba9442273535baa2dc7fa6dbf999b72b31afe032c168f4bd7080001e74ec029cd1c152dc8b09f65985d6c0c2c78e56da80c941538520a28587d9f160181a58cf9d3ea78a5685698258689ebe5fa4ce66d0ef9eceb6ae478750ef63139012e734c4d341726e1c02519bca7c04706db73fede3e95e9eb81208265d7b5c51e01e12417482428167d1173531eb01c4f05257c8915bd7b3c81668f7e4c0525d91901673db13a5adedf791c6b644470c5f61d202abec03de1ad93ad9b534e93925e0c01256c8aa3be887192ee742261edd0c2788d1b625442150145f4d3b8ed4905c218017fcf9eb3250652f2d0edd5af7d812909570a857529d71182f1e67fe20131b43a01c11b33655a2af90f359b1372d26da8e92bd8885107b6cc623e96243d56ec051700010d01fcaf34b9d9127ad8aade1bc05ed6c70040a7146a9fd596073bfcea1aad390137638774e0047f7be64682876e9fa29021fb428b367731ff1dc7701b58633e390000000001081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1782500",
"hash": "0000000000c96f27e270aa37db650cf15c6b75f1fa59a947cca4f41abf004676",
"time": 1661211894,
"saplingTree": "01ed3ce95d71c92565819babefc0f1026beac767dd6a162ac0ae9b15f619446545001900000001ac44d394a0bac1701dd6b74ab80f53729963826696a1da89aae59e12cf4c1b5f01dacf1de9631ba5147d124a2502808ee3eabdccbcaee03b03521cf765324f52710001efe591a8f59c1b2271ff973d8204c588b21747c305a7603cac7476f62424ed0d000000000138279c0da3d1576d92933860f5de697d212036da77df1af780bebe1b0fb1df2700000001734a94d73f408cf9aa047cb69f2b67382cd2eac7ad9fd8d4dca6b9ef4706d93a01323df8cc8affced4411de770643c86329441d276e3d0af469a5387baf14b702e000196ea06c6c9b1bffd35111b29addc63a564d780556def4814bd2bdf44568add0000000190fd9fe569252b58449910aeb7a9d5bed775699b04b2b0d2373b948728ce291b00000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "0173e8d2617537311c62aa77f610f752e4553fac6d8f8cdb05248941a2afc25f0b001f00011093e632be4376c29ca004fa22ceeb426c283d9006436eba5185b63ec4adab09000001ed13450a8b52b2467727d09d721f8054d408cc007a3574f090bca255d94da90800012d81bec0d20bb2214b91f3caf8d03c66c779b67f06a9908bfb239e25b2156c1400019971eb95fe89873ce0271ed1e70b8ea4498e54a81d4db7b092fefc7b9f540212000146181485362325772638425fe945f012e9b3884886dfc0f5c689f556d40d8032000001cbc56207feb2d10de3c2dffcc93040d4ed95a1d0c653c9587739681a162a601401aad0d336606800990785d124bdfe61ffbd399167bbc344f5855d3d4de468de190001464e545a0f5a15e6c09f82335c092a66ff8f10d0abe8dd0db230ca90dbcc0d2401f11d7f967046007bf41c33b30c0dc3b52c1a4a9cc3a7a85657a2fc11a39d393a0001081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1785000",
"hash": "000000000048a2a2cc1132d171fd58ae0483322d33a4ca4e5814d2f928358ddb",
"time": 1661400251,
"saplingTree": "019cdc2862d4223e86a3b2780f9cdf19b2b22431faa4c849d39f681bd01d32a0630145fdfaf100b993c09d16b34c6bb30834fbd9519be36b7cf5adc645fd2727805a190196199419320b54a8acf9a9b6883abf64a7e80fe75a17a6227bd47b8a2636b84700000001c5c6f40ccdd6c8c7c8b8257c1dbed8c9ab5b99320c0b367bd80c471f676dfe3d01324e46f8529d6becc636a9b57eb864b3cc6a19f9e1cf3da6fcd4b96456fdf1350111fe0737c21b236b783ca356947277888a785e569d1c8953d341741df944742800010e4303d3df4670374eec9b5fff8b9b7cead7c3720ccfc57bc05e9f15e005420a019a74161a408604546f401b8616c50dcd38a10efca83d1a5f0bc87f43ba3359670187130c30b7023f7d9a80fe75fcc1c7864007ae29e63c9a85dff0cc60e9055f5201d7a644ef58eeed93ab36be7ef27d90dbe43696ce2c0c2d1969e3de9f1cc9e51c013dc034d5b0eff46aa39a5461cdc285a75705eadf417e14fcb6a07feb4ed3a0530001f3a3cab7e488e34450427f914284cff92aca699a2e3bee42c42212419bb72a00000001c9c6bba2bb2a19be1e993af5f9bc35979454afa6237a44ad46ed0cd06c7eca4901e1a9472dadbe9da7956818483b066069ad0030dee06a787200e5519a5e0b96600001757bfb5957a0fa9b4fc9e8c105df1e9f0f9e156d653ba54a5fd2ebaa237afa510190fd9fe569252b58449910aeb7a9d5bed775699b04b2b0d2373b948728ce291b00000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01fcefe6872d4fa4b8bf4cde106616dd137593b43fd761478eb4d1dd6715a4cc21001f0000000118ef33685241afb399b6d9c0697404998528096ae550dcd75365912cfb64953100019ccc0dc23d60dddcadb86ecabcfe1e5d42829a741f7e6705a368ead59ac492260158cda00196a2f05b86d770724944d4db0ff55d7442962dec634e3968822df01b019f815362606a86082ca9c3a332d4fbea2faef7c6be9cf4f248fa09672ab6012301ce1619c190c7fb2f62560a1723b9daf6ab97d03406bc4537ef4da5dbe4ea993e0000000001cbd9a766f5802ce880a9741919503a6b5b2e58104d1331b7c477d758aad8fe1400018ea310e6e8006b04172395acba6ef11cbef90cf053489a7f8b3287be3935ed3d0000019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1787500",
"hash": "00000000013abc1b6771cf8ce5d7b385bef21fdcff3e809e96ad9ea119c0d0fa",
"time": 1661588407,
"saplingTree": "0103e6f1a4409bc20886cfe28eb0d0eb002050edec85b427da3164dd8e8f3d7d4500190000000153f1e09fad18b246d3acf132c9abb67fbb13b5116eeaa226b8553d3e19b25a530000015af73bf615b470dbb8f7d024c13ece622e091cce7bee801e9d54c1ad4e40e02f0149816bfc59f0deaccd1ada4c1e86b19e4413d0721784c4d052c3dc67f5c1805000000001b50664d826ff3b88aaa2ba08c69b8f21dfa710d79860c1441b5c6dfaddae483d00016378d71fb6680e282583c075d721a06b4b3b75d31df554364d4b0251def8a71001936597568d67c917899fd32efdff7afa023fbca108ebcbb402bf0ea9baacf33701f1dfff89a6bedf83c3219c1fd7261883687af993e64603b2b2be8a5fbf89754b01d31d49250f8d4970c03c939f38c3e321a8a96cdaf82a3bee23e73bd7eba7f55a01adf75581cb0b5b7aa87ebed8f1e54ac62217bf676b979298dd8b4300506dd72a01b9e02a948bb911d47d1e51ab6c7050074518cb1f6ff3856f93356d6dde2f8e0700000001f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01e6a2858cd4376f3333f54f2033cf1211a3bfc0eb24b9b80ac2cf1446dae2f31c001f0140e2ea00e36545674bc7f09751f3391d628c78399ba803b6173e9d738a5fcc320000000179b60c3bf1b8cce227df010f5c49c22c7d777d5209738683c9b697756b31a81f016f91c16f2e3960269dac6fb301b534e027bb03374126e5dbf5e202c7422c861800011e71b797efd14accdf8a33abde065d670e6fa02a777d9e7d47be234e86b5431701deb97e143f39b0820e3421fd8c38cd340bc9cfac21262ddf86e0eafd75ff570a01b5045572a2467061c9282dea3618ba48f5b6dc05d83d1ad38d57e70735e8b514013b5384e5504f03e481ba341a2f14cd449a0a390acfeb5c56a155f6d99e188813000001794870c4958f10ee3be337bd2bd6ff6784d19662c242a40ad54a3f48bb2523230001bdd331201a0524ecdd6f812cf323d1fe0173f165c181bd74199480a6211bb91f01782a84e2fe01efa2b41d6875b0537d55ead77c75811ee929f0cf9236f976383700019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1790000",
"hash": "000000000100dac22c6e557360ef3c4283d9f60c6f4c5a1e0076d3b1fe97d0d4",
"time": 1661777577,
"saplingTree": "019d3cd586fd43ea7b42b862cf823edcd874462ed3a955f1c1dfd371d86dccd5700019000000015df1e3c5a797bbbe4f4848f60f21c0bb5f7bbf3a3168cee8a1cf5689b22286590001144654b0777c00cbe78b716bacc819d39f13945657bea3ff7e0c88bf1f68894d0001164cf0832981b185067cef308aefc2cc7c93a218b7ef456b3813f58ec2162d4301ec4614750b2c957a25a24685bbd536845cebe0739d3baad2ce645105e8584d5d000168ac0ff0fb620aab894183850de738f2a79424b36e11d5daf6a77e68b771081e00012a04464db06df9410ca6b17e29f72c4b4b3c5795cee583c5422168eabb7e84210001e8e58e5db11fbfce460285fef5113a83e8b5f0ac542ce3887d722a8e2a413c2f0001fd8156cb2a632eb0265b6f49309d30a762bdbbe2e8d6148ed26ab69e6861472a01187bf80dcc94180caeacb5860ca78ef66b0977c7ef6b773428a3e19b676aeb47014e7c8a253ae2f1d791c4bb7f5026736ea71839162c1b1da41cb6a6e64a49261e0001367383639b55ca56d7008a7a64d73b875703860d1a657cc1cc0df5a9ae5d5c2b0001f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "017fed4159767f6bfb26e8136b85e4a179606f7298de17664fdcc220df21b7a02d001f01cdd388ecf7f914294552caad1d570970b47974ec73494e2f04a80e7b391afc10011f6a01e993dbacd0a5487b8a7b06275a252d0d3895fab490e2030a255a9b0c06000000012c0afb3c10723bacf85c6611273be9e7d4676128dbabec978f0fa5221223893600000001285b5cb86d8544386b6a36c49e2bdb88fbd5f59a43437cfe271d95dfb4bcf82e01b20850820c9e07dfee4fd137ebe7f4d92ff7e318f3f1581b4b53014118d6d1170000000001045adfb00ce9f9ceddcebe2658b24be1beb059888c4f93f68bde2615aca3bf0c010d67deaf001eabcb4ded2f92fd69bb00fea4118f38c2b18b9dc2ae7e8c52480701f6a82e767e738485f26942fae63d20665479e996d8404cf44a451f9585fc5b32019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1792500",
"hash": "0000000000feda98d580a542579c369cbedb9677e2a60b45858dd3c3970aec48",
"time": 1661965969,
"saplingTree": "01056e79c206e1ec677117980ba8ffd3109f8df1911bf60d6a1f17194ca42d7a23018271f1b6a7b1034a1cb52e214e01fa76fe9b57b4ecc51513fdfee851a552e55c1901d12b2f51a9766ca6bb0e39e2fb8f15d3d27a620b7807b156df4d5519b115c239000166bee33838512fb2a13169dbec4a023d71eddccda1f6f364429b9012970e430f0001f498d5859edb460c87552d732c800e3cb655f87dd39edfb1123ee44f170e6b710001b8ea5420dbf802c4aa031ea066906c68e0b134ecd9f7ce1a8a9595301621b1270159b5fea41595326ec50f60bb37bc915c5525204dbb077ee6096a733e5e6be95d0001a18b7820f47bf1fe5304af022fa5c0dbe4d6d2b3c2b8f43d82e89bd29603ee3e0001ef928eac8166f4fa704d28411595a6d9e48fbce2fdb0cc1c22ff5eb65b8ea60301f62c314465ee352a914c74a495044a37f32ed19057b1e8b1139c24ae5fe1eb6d000001a54b39b5536ee32eaccd615c97b5894e59ca339fa58b170542e565957973c2640001abc74ef752650de527e13ef419675a18fa8abc0ea648bb79c9bad4ddc3a3c33300000001707d2f9501c9e36a368499bad696bc5aaa721b6d08f9a40f4ec8851e9deb373401f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01d12a9e0d3c96789f73acf81fbbd59e1352c9510eddd29d7b3e79fdb6c34e873801ac79591488cf371014ca4577af93bf3b81be2312c23315f0d30007fee8d5a22e1f0115bacc61ac12c6c35f4601d1fa17909700158b676b305700615c8b22c50c0f11012075bbb910e3877bea5dfd4594729900eef32aa66effa3aaa2354a455a507f1801f5a06cb7bb2283e8e1bb8e5eb22ac57741f666f1c5ef2db8e5f644957c86481e0001f4924dc1afdcef8b935fda84922dab90e523b0100e329d24295e4ab809f38d19012ce7342f56d5319d6c54a63a326e33b8dced10d6cd696abf270e8305d8b6101c01e05e25ecc131faff939ad965861a12eda859ef69fd6928c65c978ae0d827592301d646e453f3ee10362226de45b8e9377ac12191351816efdf5d3254668345422e01b2642250d10f8ec40181ee561a18b60870aa700977107805b91d2f52b872f122012eb7bc0fff59658e28f1bf9b79fa184d21ff2679637745b2e6c51657993e001b01a50a617a7ffe94d3b52041db8dd9a0830c2fe42e394ce95ad4b6265bae2c7112000001f901961738a6f66400aee2908298ed5b83c53adea294db77b57d10eedbfcb5370187ae41d92d3780530a81c2db1a35768479f5843a62c64846320039681551062e00000000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1795000",
"hash": "0000000000080087f9812e87732355691e4bdd43fdbbf517618959c81cccdda1",
"time": 1662152700,
"saplingTree": "01b68aa765ae73bf3eddef03a2a0127126964d9a543859d462540467a4772425610019000001aa8687526bc08a818db298cf940aa78c30792a927a58ee1b6d11715b2507d9280000013788d7a49d2a299ce893eeb4175e702b4380f070ccd32d4b4beebdaae9d2dc3d00012653cc481a3bd5901dd5728d9754f9555932c00647c15ebd5a13cb1db579383301764ef57a84dee173a7d8b135065b75be0b012d5931af754bd339f6572ad84a5a01642fd0ec665cb43eff91032c290ee9cccf50b234af0f5ea94798973b3efc392f000190a2aa5d8963fe49141ced07138011754b00493211523505d488a2cf3ba24a320168569b74e5b9370152695d797cbe7174538b8e33304539f72efef6b58a9a4f04010b05111ee7d748441446ecc08f652247197356bbf83d73d7d07448ed4754192b017e137de7b7b21bbf9c3f9720196427864e7fe2fd8070aba9237b24fbec95914a0113c1af933a4048a7b09cb0e6ccd27691f4649385596d8111fe602bf4b255db29010470b81e369a600bdd7d6bb96c409554913044eb1c66dd7659a6e68129136165014d7bd573014ae0f6d462262ec28df786ef65aa9bcd5e2d6c54f26b4ee3633c2b0127bd15f9da068d7c81f1396a0ee87b700755451b9f4cebfa11919f3a4285713c016bce4a3d7e7ae87037b3ba0b9789dd1a1737e355cb862aba330cec03aec244710001707d2f9501c9e36a368499bad696bc5aaa721b6d08f9a40f4ec8851e9deb373401f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "012d8880e630c07430af4529f928e8d5d6d7f19f69a4449e7b6ce739f73a2e2b2f014fc16e1b120f140566bfd6a4e40797c72c87af205508ae98259bcdba377d3a0d1f01caa4adc8466eb027a6c6095ab1a20fecbe058ef63db381fd1d1f08628bb5a41801235d173241458aebb1b6d757489969e24a1a09720606ba56b1eae566bfbdb01300000126b2135c0942f13da6de1470ee2e5bc26c98f8d6d808ee71f2e7d7683ada763400000171071f52e8986c28e68db191de9cf8b88e39af84909289207ca4f56d346e85160000018f9f481c4fb6151f6868afef177dbd28f208ddb31bc4951185f31f4b6996420d014e57bc25f0a5322059b756e0b4bea83442a5e418d3486b57c617757d9098552501e0e364cb2abe4d1967bca145c4af1774eb2d84bca3af756581870661685cfb160001eb3af18b241fa811548c29205f42601bee7b58ba55fc529542836f1cf477bd080001f63a616ca09e1106337cee385439f0bdb7e14ebf6d2b3eaafc4c69ed04f147300000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1797500",
"hash": "000000000062762b9e9de093b9375fe1bfc451ff05a2c11a0339c90ec3e879b0",
"time": 1662341718,
"saplingTree": "0125d05417c5144dfe8aec67961d71a700c93b51d82486803f8c4719be2708274b01c675445684630272ec881b8283397137140ef8089bac18b3f07b0a7f3f8f6c54190192d55e6fef209eede1572a74f52fb42e347bb17221b179f28443e8ac190e480701489b0714407a588eaa9b6be421f77049a98bb005b79c3df1f8d592201360f94a000000000163669ec32a05e1726bfd63268924daf5bb3f6794a05a7824f87492708cbc3b2a0001e4bfc91695ef7bce9e29f48595b8cc278af7b1857039f2fdcf75bd58b579bc6201b6a313b5daf1ebb237d55661ce5d6f6a93018b407f2386635b727bdfe8ea6019000001dfe1241acfb16d56a4163ad86a0bb7b763b4f5da28b17b4413a3e6e8dbb7d728019a4fe2a045f95d86fe1a5fa9d413c4f36e42e1f03fbb2b5bed842d96d3f71e1b018e123a755b1d549c6f87ac048fa15664bbc3b10a0ef155b046eb4bc7e5b7061d000000000000000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01a23430438ea5dafb4b7b4db2111f6280226cf69c172a8a763d1218f92ed6c23a001f01e4aeacad6fe90d4122dffbad65537ce856480aadcca1866d9682b2b968cc5e2f000001d142373141d5e6a48be5d097631fec923007c886d34e2b1ff5fbb55fa4f33810010f5906e6e3c4bf7d8015ff88c532795ea4b49f35dbb41a6bdf8592d09439a92c01c6580c00bd5c6da40f2026554c86788192aa5e8cd5f7486d8d51cc258cb312200001e41507a73031aa566ffb009506cd5b30992b0ebd34fc3fc78449a886a4317a0d0000015b224fa4142c9508f0b2dfd1e64fff63e996b697a4dd9502f9a3fd917cbfc42200010fbd0f1d4f428961e0b32b28994bfdd80e1e68b330883bd0889f9ca8f948b6190136b0d3765a45a227aedb1925c5670bc6364856ffcbaf19b52ab06f179ed897350132fe04f3088f035c4ca4c818b3e720ac26ac172f30b27dcd3e3bbcbb8192c11001fe9e4b5837bc7acc9356055fda92475bd0593e36a180e93549bb1ce2ab52e20e01f63a616ca09e1106337cee385439f0bdb7e14ebf6d2b3eaafc4c69ed04f147300000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1800000",
"hash": "0000000000f908c93f3a7f4288b1f73a5c5621397ae94e4a894ef44ecceaa961",
"time": 1662529994,
"saplingTree": "01394fa4810bafa4d2231cb3dcc560e43e9f02418e82635d6a3619276e76407118014a496ba29efc52eaf66430ebe6be9d9b8dc10ab790cf624bd9b16d404e32e52619017ce5fbd2d63aee2d62820d40951f1efdb1ea9d4b7b3c40470d44b61b3537303c01b4c88e366d13efe090502c5e1c147a70f9be10226d97a16f726aef466245a9400001ecaf654b7bb55264015e8e5cf8ca20a01f79dafea9ea174a216727160a3596180128dfcd78a6488832dedf7d4bc7af36360a3ced5ac10f26a49451d2543096672b0001867d8b2f5148a10776a109b8cc6c0fbe006256eddd007ecdc8ac703ae03db126000001836b32ac862719c718b58435f87bba6d66a476711854121212299657ab973d440120d4f1b55c4e9e43442c08a201d4518369927b976a61c12add79cfdcd62c515000000109545546d941eb229dba133374ec57484d65febeffed0eaf9101e039db7eb06d01f98dd99c797f8ff594d93854a1f42f17bcf85d22e75fa9a06ce53b395ae1e671000148f810d1aaaa173f8c744fca6efe293f1b7be578def47bd9e45e647a35df5e3001d224af2c322521ed4297213b51e327cfa001cb6b7c04fd647a8c60d5ecff6717010bfec213bc4e3bfb6039ad8e0d01f1b6662367b4e38a2b185f227d77591b3b2001cf2ef2a7179af6eb4d2552757c05466c345a169d15c5da6dc13ba8fd34e3495700000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01e0e4bfc911716e9548d6d4cb84cf8b66f62df3647a176f9aa3c66453013ff206010b0e83345c27139830a0fff89f018987eaf3b6ed776ea138ab846e064c7685041f0001821067594b408dbac72a9967d0d135053f174f0adc604af6887867ba1a1f0735017f46cc72632f201b8854c67456f91fe83418040e8be1bf983231da9d4e8c1a0000013a6f5f208f42b3640fe9a182cc4653f0b46cf1c62f7ca31ffcd14a542d8f7917016560e4ea4138ea5bcae07924ce84454871d1da8efb6ce4a59acb78f3287a30140001cb756a9be049d541b1cb9d4e58344c3cc6a34863345ac0c122744c026f29f7200000000000000001c5633b986425941f3be7634113839a2de1d40a43529148a16fc979cfd2245d310001c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1802500",
"hash": "00000000017fbac093c9dd6d349b88685f369cbbe2c12b9a0c9ed6830bfc8e91",
"time": 1662718546,
"saplingTree": "018672213d179722fdc5221d5199441fc70a068db8c20a02ebf8bec70dac9f4e08017a0d68f3d30e1f5e3690c627ead792ae7ce1d08761b095ed717db285e60a0166190001c44e548845f6a825e9fdcdcec964674a84c48cff34c112d9828c1d5f21a5502a00000000014aea74e6cc2dbae2ff085618b475820a68a5969215246e00353f68129994a72900011489a9121bb1593a210f489a44b3fb8e2fe7b0b1143d89cf338b1da874438149012ea4d7a0b0e68673080d9f47f4c92728839fe0991626ae337f9ad6bfa598872b000001629e5d548ad857baaa6716719e67708b8b696b77b23ef900d49820d028ac604101cf6d58fea133cccc9307a56ef90ca4f8b32af3fb8b494b583724275da946a1370000000001cddcf07098de80d473262d9e1594738168a73a2e27bb986b9ce8a43635ccd03d00013f6c68973c08f026717c32a6be0d4083febb8c2d63f2e895a6d07021afa3a51c000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "011618cffe419b3ae6d6f94ed2138925766f616262a81b11220dd64310db9a602201b50f80ddf6be2c181b9e4795a48aa30def25318410e19071bbcae266517237061f01000ec86443fda797bdee68d22ad1c8e3159ce016343aed4bdc4044b30808ff0d000192dd02c90b3e87a42dc8a5a1bc59b832e21937614d71aec0ca0dcb4293c8802e000000015226ac6b1c91f0b90680b4cf4175a38450727aeaec887792adb9ef13c411de0b0001bc359c17ecd3d5fb2896bfcf890ebc51de0eda3c55734dc24523a41b24267f1a0001c8b40d0cea90b7642884cdca72286ca44b278ae6e9ad2e2bbee1b30680b1a60d00010deddae26bf93da080b26610dae9ddccb9243015487b922f3c05198476c4ca2400019902d40b5aab956a3001b26499f3f689c08ffc9b5989d14840ea01f443dfe61c01c5633b986425941f3be7634113839a2de1d40a43529148a16fc979cfd2245d310001c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1805000",
"hash": "00000000011cfeaec0ee1d6230aa8d3642ad4c22db6d541ff43520ed2035f7eb",
"time": 1662907506,
"saplingTree": "016d31b4c64ae5c17308b9d90572e6de5993b3755e861e040949fe4438caf1000e00190000016c0be2c033839aec91d32becbaf583282f918ad917865f323a5c88d687f1bb1501ba7e5ccb08c9579b4fa81fa555c3fa94e6872eee2bf8f01641f6103e9a975b4b019c049c4abf367e07e168ba327e86a7cb04d11d9ad446e2356f2870cfe9e958340001f29ca1e8f612cc422bfeb2713a89062d8695f44436cb8ac0b57ff5f2f1d8da5401e4195653dc559c9590e0248719443c666c82d6e7521d2688d32a914070eb7a540001f8d79dde2f627bfd60d0c1131cd3ad6b779c9619f6dbb0ab0515778a9a9fe24d0000000001235b463d441fae8a4ee172bb40b1a3853ba4a07ea44377b06295db356608ea3a013c2d33c9c5afba6b720ffd9ea7c69af1635c2f98d291e2eaf358f2c87232ec2a0000013ec9722bf3aeef59c292db71e3de87d2a3868cdd2902b867328e24c30c8dbe1b0000018c2d6adea2ad4faf20eccfc2c2a2c59192fb53d3204b3a2757f1c247dadec16b0001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01a1eac07fe90382f2003de69239f281a9798b7f984bc20a3559d5f886a9e07035001f019f6fb5bad161ee9f26dbe23c485e0f25d083e17c1e68b91525f33d659d35ac1d00013957a17ebe53945197d684304f29a77486a0f081c6a4a3f4c73b33b88a834a1101e151d0e8b30866bf9779bb3d1105c3124db863690458c855537e967679bd903c000000000001bad8cfde5bec689cc645ed5c9e37f6e6f1753fa9a95f40ccfe25ac91bcc2481e019927318c01f928c75112b5d0c116e9a7244d58684396512e5e24569acb94581a0000010ca90449f94a0ea6eb64ff88b438e14649280613827022401d54a8635e63943100011759fa02e2fae57b40cd62bd3829225c15bad6430caf25a526d51f329091293e014660573ed7dbd889063d06f1d814a4f28a5ba4da76c2c5c8e11ad980f070353601c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1807500",
"hash": "0000000000b92234ffe6efd360a2fb1528e2f5cf891a54e58a9b83cd6c513ad9",
"time": 1663095814,
"saplingTree": "0146b9cd1dc80b44c19108d53ecf72b5bacd405e8ed5c8038c413ad2d7bf9f486201fb61737c80f0c3816ddc487ff9ba360c60b1663151cbd07c08dd8c09c648760d190177aa27c08a1ff4754710984f2f94c5a101a10f57eb6ad49f7b1df47f7e8a5730014c0fb4e36c1b7968082d0355f318a67e265e5a5533ffcc01b939e4bf1779841b000000019315fe44f132446949cd9ea1f34ec20f32c0dd0ba4f732e563c040c692fd5d49010e6b6ae8956bd722b0f5e3f355adfc7c9b6a39542c07fb0bcd317d0eff8d99080001382db97fabb726a88e7a7baab050a1c9169d2142a9ce0b7f3022349acf74981a0175d44cdd04e213c2d95fb281f96fc7d734c65be70a77b7aa4fef8a4b6fb2475b015fe654f11132fc9c133b46dbbf19b0113cc45715f52dfd3282ede76f6880740c01d14f83f0fd7d09f4f52c8ae9d39c63b57c59a37666d211b9a2deb290808c02720001701df279d9a2270a82379486df546fafeaeb831993cde1cd9e5c9cb17be5191f01cc6ae86e9147b0b1c3f5fb32fb7acc012b4cb4384a1a1331ae3c2324a804483e00017fb2e2890b05355ba797af2f77e38cab3e8ec1623d29a912bdd0dc4a78cf554a0001de17a599d0c6d73eaf1a5939e95af4427f75b89f703749e00d853cce3d6af84f00014f6313837ed19d2b480ec531529fb6b425006f2c1d981077640be21627659410018c2d6adea2ad4faf20eccfc2c2a2c59192fb53d3204b3a2757f1c247dadec16b0001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "013de3a4ad28b5df77a979fc925643e69b7dd26ba787a3122fcd6a445c47d9280e01b68287983d323c90e6a1ed5980ab5b1a846d49340ee6d40d2349795c132a382c1f0001c2db91c5b9baa1c09623429cb4005ae12e521a018eb8df2d051d6793a307eb3e01c6b2f8d93635310d470d4a6d011ea77f59e28bee6ffca3df88f5f2a98980331a01777860ffe739b8047045e2dff8ba77070666075214a0f7702568205410351b39019ed7c5c3e958cb8b9d5324c290ff384dc3ca6cbc870950002f64398478ff1904000114b5ad56c8f210854a1688f47116b5d272fea09559646cee33ad3e6958306a15000001110e689714d772b170f63bfbf4b144c6a48e0a57c4a2780513633d5c799a0d1300010cf94eaf4d5268d9e0878064a458eaa3363dfcfcf2602681c443baf989d5de20000000019776dec2ea06cc5ecd2d212d37023972f526cb2ffa7ce1e8cf8eb4ef04700b01000001c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1810000",
"hash": "0000000000bdf01be068ff1f0b21b8b266f839b31ed066b72888b67f672c9800",
"time": 1663283618,
"saplingTree": "019dbc466ad114f2b4a6d0d91198c0a2c5c20c1dec7c2e7f932c9f9197d76b80020019015b272118101cac0ee6b9b8cf26d5104ab42913d2f5253388bac28e7998f2c41b0001868d5008c587f0fa3d26fc42097d34df49a70508a85a6acccf1263d39cb62c28000001b2325a6ecbf023f3ce4b74c1c14bac8d9c462560dce9611bd7b08cd55ecf4b0100000001cd62e4e2142c664656f956fd0ea8d1de1027c327580398fe8912e6264801650201ccc9b305f6e65d641a4e461ea5e853354a3ac4b2acf2591849e00421bd58fb48000000013a591632f71e1fe214ab46b464112520e98f15d171da599a350f7d8dba79595d018d254447626cf40828102a60e2b433d05498a780599cdf56a14f3888c2f42008000148af2e64d92d1944a451180f1738c9f468f608525b0273967db19029b53ba16d0000000001f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "0180c286284cd52360af960fce56fe7dc339667c35580c75e9bc339483230e932701260b8984058125b5e3558fb65172d59718427111cbc06891017cc7633f74e1001f00000000000001cde58ba8e982a34499406e06a763ede11353d39ab93a5af1b34905cf268c033701226087d6a9bb28c2ed6890b989224791093cd5012a27285040025c0a72b2ce06000001ba71f8a1897b754e9fe37a29eb2c1a93ddc1678298498b4a84da732ebf056f15018073f4aff677a24eaac68c20d271ea228041f1e77710b0504d3f6c0b71d63d24000146b37a3e6167ae7f07725ab4e32247619c37e2a91c87182dd68b8feb99d5a22201e18dad85447ef2e9b8d647c9b9f1e6cef3e1d03f908975cd5e1d5c5808e443010001898b4a8f384f342a67efb3f6c4afd87310df4ff1532b86ca8d1394975aab5a1e0001c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1812500",
"hash": "00000000008f6e5ac009390e7daa092a3207581c211e2be70b60ce04b88505c3",
"time": 1663471780,
"saplingTree": "0180d672d65ad56265eeb9632dbe62473447bc7bf3e598d2e896cdcb798887b53d00190139d37c3a694b0104d15e447f51d96800d260a5a1053558cd4b7134b8c77f803400017175873eec18949e09f6df019a3d20530716d2307ba1f00fc0c09bae6656022400000198c1229f3df738745986e8aabc1d3ec92944865b43a910b8c74221f99b8ee0320000014c8a851d7fdb90d17b97621005c826c8b3ce8ee463062e324e2ad886f202b6440158720dd7a931f66eac2ff934b0765f807c9504ea318c18f606831c80feb3770401252adc68c56eae8331764a2d9d72851e7352bd33364963b6618f47d56bc21c6900017d8e001c7be91297f727edf47b650f44cbfbebb535b4f409e357b37c43a621040001ba60d34966fcefe1b8058d28b784f87089e693fde8b99902a4fc956021f7ee04017547ef39c632d9bfd0cf48e3e909dbc2e0890f2791a6929ca0d6674484f4c046000190ca77d38f0a408e33a8633f140441488cdedf7508a06d04771b53e4e4de9861019db47a02109d9232f4c4fa044613a8a0cc508865b185ee28318c86d16829895801acc2d2141fe4e5c43c11710e48bd0b12c0493b1721fad3b03470a4400157e020000001f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01cfc873591e05c184abdc8a1a78659e28df418ea5320a5bd81d2d761c1af5ed0a001f01244feb50d761d34ab22b04045ae9eface10df4b17bbded9d1da9fb71e48cf01400018a07d6cf54e4da1fb1f501850cba2a98ff5a8a8a5722896c3906c09447500f26014be19370bcb96d495a7c1e9719ccc057104a66a5ef6ea217f3fe29e26459131d000172482cc1f1fad25e86299334a24a63e6e86c0b04d319a230bd3aba2dd38279110124de7fbfe4a050ae1427d73491bdddedda1e2e7db48d2365b3cb108eb9833b34000000000000018eb08300830f838c7d212857b7e045a05458264f44f33a9f5d726631634da70201778055c0e5ef68c30c7fa81eb67f0cbf32b64dc5d6ff8138f380cb8f115418260000010fb2cb03705826ca086f605974429cfb0dc60df50a0d15d06c2cfef80017902f01c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1815000",
"hash": "0000000000ff971a219416d483ec48af6e055182ab566572fdda7fedb2d15368",
"time": 1663659973,
"saplingTree": "013010baaf80742269ab7061e012c90e4ef805cc07bb62ef71869fe0badc141f6901d003b1d99e5dfc26894e0b1f393399560374425c34f89853df55a4e32be616251901b34b07fcc9a91eb89bc409a51f452bd21327cfdf3328e9913289760e464824710122ccb843dd5775365b143f0cd1cf309c1940a7c3ba7070c92d041f35b8fc3f4601da8ae93a665218fe154683bb6c75e5ded94f1780358591ce7524ca324f22a53601962ea341367529954ac02e5479c129499aae960e8448ec5697f216b45986b3040168c82ca2a31ca7b833886ee0b3dbb60c519a917b2dfcb6590175b38a0cf17944000196c2704fda3d5e124118cc6cbb1514bbdbdff5bab3a334bda2af27b3e1c7f42a0103bdd297e11f1bbf4d438079aefdc797b012e2e90903194011f9f23cd7ed5b510198ba7189c4d8aeaa58402a655214c69bfa4d09c7818a9691629a8643cca9c91d0000016fbc427c11055083f9330cb82d07c7ce7ca37ee43c7bcc687683f6a50cfdfa3e01201e7639e97113adcb84691cca72efb6f8f44293109ccf02f1beabe46412b75c01046ace0bc25d6c3889da89753306be90ce721bc463e9a6662f3ea10141b5db2801ac5fca146985e234d3ae22d84b09f7924d8baeee82fba8235dd9db60db8f6565000001f513025ec4ad6f392fa842db7cb2f0be10e1bc3662046db7cb6d5de314abe33a01ff0944072256da35186a74026d56db14a2c80ffc4dc7a3b2b5a93885c2622b5201471bdfaa9efa8b9adce9654553a07278f7e37156c73b03394ca8288d8983403001bc76949736399cf6d26d21a468ad6c8ee3e563e0ccef01d615c45a91bc9a612e0001f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "013f012dabdfb54100613c405576e16bbc19a802910fde444e98827f568143ed0c001f010853c8f2d2423d321b9630781d679b884cde7b3160108ddc1233f955e1bd9628000001b8ef98edd02dbc2b74a63778968d66cb1361cc01f7b4434b141ec35c12a2741901c5a9a64d994fbe4c8d044c80f8f551595ff8a33b2bf98857058ab080aa2fbf31000000000116e9a922d1b143d61c100ff15dc2ffdf92e70721aed5a6c37270d7136fb38a1b013dedd8fa148f4930ad5fc0ac019753a21886453af5c94e8f8817a4e13b7ef810000001f69a0bb2a07942810983d469f50f2285c1ba6f0b3355378356cc62a17860cf170175dca061b72ede5cfcb867aa38ce3a37fc84b7586cbeac75d077c5e0eb33b3250001ccf521db485f5532163391b715519775dc86bbb7b938687551a8547a7c81b425010fb2cb03705826ca086f605974429cfb0dc60df50a0d15d06c2cfef80017902f01c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "test",
"height": "2000000",
"hash": "000bb3c6d575a155a56b1aab0e7f14a1141d25c1f36aa6f15c1cb1ef1d7d498c",
"time": 1660621966,
"saplingTree": "0133914238639e4f22ddc8ca2cab7e2d7ec676b06f12a0bf42bc757909cec4f47200100103217f60b7d9bdc625ecd5d6170e3ab57a81eb29f7beacecd07c4f3f0d61ef2801066673d6ab6b62a91f6e04a49fc0e74083cfcccdc851db681e01db333ee1cb470000018da55ded8e55006c2a6c41474da220a9a179a85a99a48df0a31a74291317720e00012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
"orchardTree": "01a682706317caa5aec999385ac580445ff4eff6347e4a3c844ac18fcb5fe9bf1c01cca6f37237f27037fa7f8fe5ec8d2cc251b791cfb9cdd08cd1215229fa9435221f0001590d3e7e3f4cd572274f79f4a95b41fa72ed9b42a7c6dbcaec9637eaf368ac0e0000018843337920418307fa7699d506bb0f47a79aea7f6fe8efc1e25b9dde8966e22f013b5a8ef020d8b30fa8beb8406dd30b2a1944755f5549713e4fe24de78ab72e12000001a46523754a6d3fbc3226d6221dafca357d930e183297a0ba1cfa2db5d0500e1f01b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "test",
"height": "2010000",
"hash": "0013159a578c874aeecddad8707b8e274a018078fd80fc3b9e2d04065abeb05d",
"time": 1661274815,
"saplingTree": "012bd51ef4da530bb488d43a0f770109df7186ef164a64f618038bb00b7861840a00100001d3a6dfc2fac7c968ce7f96efdddc9764bc320230d8ce166637f8b73b6453e8670000000197e98f0a36d7a5ccac4b4ec963223a4eaab38188eb2a658ff01da4880839996e012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
"orchardTree": "014b0ab9afb38a6c819d64806b3de5397c6469ca0f7e58274f5c779ecb75d94217018646890daed861a8f66a3966e884da1b1c8240bb47327a348fae9169eb8205051f000118681f82320257f5a5cfc5c846fd4e88641d4ed7b87fcd5ec8e65e68e3ab0636000155cd8735904f69800ba500bf8af6fc70bfb2abb3d7ca1c75a1cce183b763f30400016e57cbad21bab804109f19479a88046b5c7f8da0d21e8217ac467801235c52200001a8773a2b01e9efd1011ea7d8d1071ad0a01c6c73e2fb6df802e307df17b4ac0601a46523754a6d3fbc3226d6221dafca357d930e183297a0ba1cfa2db5d0500e1f01b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "test",
"height": "2020000",
"hash": "000777cc07627b8a0a0086f3e7cab5121372c6caad19d387a58dbd9576827a9a",
"time": 1661953684,
"saplingTree": "010620a4db0c09f044c0edfddb2e13ee69ae7385cf2ea483b9924b6af8d4d51771001001edfd2f5620d6913ff9b1e56d7e60ead3f140e9756c1351dd29f6acb98643c96901d3a6dfc2fac7c968ce7f96efdddc9764bc320230d8ce166637f8b73b6453e8670000000197e98f0a36d7a5ccac4b4ec963223a4eaab38188eb2a658ff01da4880839996e012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
"orchardTree": "01f33757a6d93a793fe0c9316ea63db47d01dd6a4a7861a046bca399b6dc4cae1101baae6b740a3589fbecc9b7bc5648d4e7bc1aaaec9dacb1f1fad48dd0d60d4b0d1f0001dc89cf3d764acdccad19c5f0fd2b4a133e132221e20c8bc42529127ac8486b3501e86d5e712791cfdc0abc80d6cf13cf2ac8b96ce89dd4d24c36a0f87dd8192b330155cd8735904f69800ba500bf8af6fc70bfb2abb3d7ca1c75a1cce183b763f30400016e57cbad21bab804109f19479a88046b5c7f8da0d21e8217ac467801235c52200001a8773a2b01e9efd1011ea7d8d1071ad0a01c6c73e2fb6df802e307df17b4ac0601a46523754a6d3fbc3226d6221dafca357d930e183297a0ba1cfa2db5d0500e1f01b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "test",
"height": "2030000",
"hash": "0020f1af6bad0db43f2c42baae19ecf784142cc06973c0054f82259096acc700",
"time": 1662635959,
"saplingTree": "01e9be53684e763faf4f718a731191ea92872c0ebe725ab22a5e4c9564d4854e6c01bcbedf283945d4a3ae20c57c9786f98be2b51c83fc14084bf81bdab4905c95291001be49bd71757b254fe215647852f1ebb4e9c6539470d1f883b260ae31f287c139011dac0abc733ee5359d9ebaf2fd1e27d97ce2dd318461e9458b21fa7460dccb4101f763bd441cf06dceca58869da07c620bf7b09cb89edf2eff0887ae3e2280d11300000197e98f0a36d7a5ccac4b4ec963223a4eaab38188eb2a658ff01da4880839996e012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
"orchardTree": "01d1cfe443088811d9043242dd694da0274ab90ec4775b8dd9b1d2177d8a1bd72b001f000001fd6d2d94e0aeb520a55825da8640517d4a8a4643ad93174d860d3ed25061580d012636bb4ba0c2e449cd2e3cb5f3e6a372d51f84879ade82345700afbcbad65128000001b51a157d0d095cc0ffbb19b8d0afd168f167c36601da2ba7c6dbd301bc0b4e2a01a8773a2b01e9efd1011ea7d8d1071ad0a01c6c73e2fb6df802e307df17b4ac0601a46523754a6d3fbc3226d6221dafca357d930e183297a0ba1cfa2db5d0500e1f01b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "test",
"height": "2040000",
"hash": "000eb52c4e328d62f1534aa382e8694e76a5a2390322f83e33e503b5dc7ae06f",
"time": 1663304586,
"saplingTree": "0156434fee1f941dbe0b18b17f3dc9dc758c012090a4abaff688497eb29b8c856800100116548b6eab46750ad5b63ecf449bcd4a17297d2188181580c64dcdbba3a8af0c000001339480cf39fd7dc1a01cacc143be0b8298ec3c94d6b191810c22e1aa1b2b0518000197e98f0a36d7a5ccac4b4ec963223a4eaab38188eb2a658ff01da4880839996e012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
"orchardTree": "01e4caaed868ac7bf736481a945a4f800587e2dc7e36004f4d65a9bcc151b9c42801cb2fab44427363a0438ae221de1e6d07ca76b67b00debb77a81f68bc67cdff1e1f0001bdabf0b09af83b73aafa98e9713ca35c48f88d034f98cf33523f567a82248e320000015a497aaf21034cbbcda9cd0805f0a550c0de19229618be23b0bc64e285265f030001bccbf993ceea1d160380db1b90b634b1a8ee1e75b6be3b4c2fa92ade249f2e39000000012e8eedc0e41ccd50e4a4fa428336f7bae0e1ddc5e80786ebb3f5ef065cc05b03016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
}

View File

@ -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<WalletBalance?>(null)
private val _transparentBalances = MutableStateFlow<WalletBalance?>(null)
// TODO [#288]: Remove Deprecated Usage of ConflatedBroadcastChannel
// TODO [#288]: https://github.com/zcash/zcash-android-wallet-sdk/issues/288
private val _status = ConflatedBroadcastChannel<Synchronizer.Status>(DISCONNECTED)
private val _status = MutableStateFlow<Synchronizer.Status>(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,

View File

@ -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)

View File

@ -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<State> = ConflatedBroadcastChannel(Initialized)
private val _progress = ConflatedBroadcastChannel(0)
private val _processorInfo =
ConflatedBroadcastChannel(ProcessorInfo(null, null, null, null, null))
private val _state: MutableStateFlow<State> = MutableStateFlow(Initialized)
private val _progress = MutableStateFlow(0)
private val _processorInfo = MutableStateFlow(ProcessorInfo(null, null, null, null, null))
private val _networkHeight = MutableStateFlow<BlockHeight?>(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
}
/**

View File

@ -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)

View File

@ -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."
)

View File

@ -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'.

View File

@ -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.
*/

View File

@ -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<SaplingParameters>,
val paramsDirectory: File,
val paramsLegacyDirectory: File
)

View File

@ -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<File>? = 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) }
)
}
}
}
}

View File

@ -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<T>(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
private val computationScope: CoroutineScope = CoroutineScope(dispatcher + SupervisorJob())
private val computationChannel: ConflatedBroadcastChannel<T> = ConflatedBroadcastChannel()
internal val flow = computationChannel.asFlow().flowOn(dispatcher).onStart {
invalidate()
}
private val computationScope: CoroutineScope = CoroutineScope(dispatcher)
private val computationFlow: MutableSharedFlow<T> = 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

View File

@ -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,

View File

@ -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
)
}

View File

@ -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,

View File

@ -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<Int>(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)
}
}

View File

@ -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