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:
commit
f2effc8dd2
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -71,6 +71,17 @@ Change Log
|
|||
- `DerivationTool.deriveUnifiedViewingKeys`
|
||||
- `DerivationTool.validateUnifiedViewingKey`
|
||||
|
||||
Version 1.9.0-beta05
|
||||
------------------------------------
|
||||
- The minimum version of Android supported is now API 21
|
||||
- Fixed R8/ProGuard consumer rule, which eliminates a runtime crash for minified apps
|
||||
|
||||
Version 1.9.0-beta04
|
||||
------------------------------------
|
||||
- The SDK now stores sapling param files in `no_backup/co.electricoin.zcash` folder instead of the `cache/params`
|
||||
folder. Besides that, `SaplingParamTool` also does validation of downloaded sapling param file hash and size.
|
||||
**No action required from client app**.
|
||||
|
||||
Version 1.9.0-beta03
|
||||
------------------------------------
|
||||
- No changes; this release is a test of a new deployment process
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<*, *, *, *>) {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -6,6 +6,8 @@ plugins {
|
|||
}
|
||||
|
||||
android {
|
||||
namespace = "cash.z.ecc.android.sdk.darkside"
|
||||
|
||||
defaultConfig {
|
||||
//targetSdk = 30 //Integer.parseInt(project.property("targetSdkVersion"))
|
||||
multiDexEnabled = true
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -211,7 +211,7 @@ class TestWallet(
|
|||
1_330_190
|
||||
),
|
||||
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
|
||||
),
|
||||
)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -46,7 +46,9 @@ class SmokeTest {
|
|||
)
|
||||
assertTrue(
|
||||
"Invalid CacheDB params dir",
|
||||
rustBackend.pathParamsDir.endsWith("cache/params")
|
||||
rustBackend.saplingParamDir.endsWith(
|
||||
"no_backup/co.electricoin.zcash"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -213,7 +213,7 @@ class TestWallet(
|
|||
1_330_190
|
||||
),
|
||||
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
|
||||
),
|
||||
)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1767500",
|
||||
"hash": "000000000062aefe96e5bdd113d850b89646e8ab600ddcbd57c2c3cca9ed9cdb",
|
||||
"time": 1660081334,
|
||||
"saplingTree": "017cc5d6752832ddd99fa1bd6e3c7f2113986ad5d5be3068bfe14243212e717257001800000157aab5fa6cc81d5a4ca70c0cb0c2bac34804dc237dc817418d45df04ac46ac3f01f5edbbffb54d8c60445812ce336ba434bb424f6ae71e1b3a84c522d345a973180001c5e144ba6cda0551733ebc2bbe098ab386b45c5a93012a3e5b756beca22c6c3e01dbe2ae2d51059351deaab0cc2bc8d56e71fefeee47491749b9cade247f576b0f01c75ccd49fcefece91dad36bf2c57808e290f33209375bd75ed26dcdfda27e1650177925c91eb349eeaa311898dae3c2832fece99426f877e73058780550acc0331000161343f9954a5082d850e2670038205296798f6080822deb44fb4c280f55f974501fc08c52c0f49521e463a91c9a3eb605b811b3916e82abfa6e2e004662cbce33000000191de3df92b233b4bc9ca3e9d5cd49e832eef5178c6ebaf3e0b1a1c5f748fa237000001f6d2aae532a8ef603f8d0e3484b9013e00f525afcc5c725aaaa531ca97514c030001a9ebb083071b5c81251c1c09b08606288769a32e79967db3fa8138075aa5825d000115c91e5fc9fd96cbf12a77777f1781cce55113f548b87f067f2d7d1af81dfa56018ed21e6b0098dd6f0902efde81f9d5da8d7068e8dcf01ab1c015f8f5936c2d5f017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
|
||||
"orchardTree": "011354cb2375c9f06e3c3a1e21549f0d582b5b24b562880c3e12d41751d0878c3a0178e9447cb89b46eedb06346450cfca4680cf6e2d5f71aaea92cfbd0c4ee8ef181f000000011a0bb2dae9ccd72fcafab242a08d3e16a31f0699d440cef290f0998c378f003a011250096a18c4e0ac40bf72bedf005b97e24096b473d6f4422335fd67e485910c01e6338965eb59f946b5bce375e43414c829116025645d4fb50b49c3fa5f65fd2f00014735a76fb3ca0190df1e1b2a64ff0dd2b875d8d972d1e49333b7720e1d80233c01725410fb3633b5a3b484f386f31ad0a8372e744b8ebad06f0b267e8181ee3d1e00000001be342cefff27be290a0c6688149ec5078a5bb559e590773823e486c6d7a10111015a78bd51e8c203d1d4960791f29412b0ebd8e918a5a34203dac112fc5a07750e010c29539431c68b879515fe688b31c4c5c8479b5bff69c50207314b769450a32e01f2fb309ae3c4de7584b0c2974bf0b92af35ff03420b8e210009227648eef393d000001a111bede6c68ba8795c99c17b615d062944718ff12e5d890a90128409860982101374feb2041bfd423c6cc3e064ee2b4705748a082836d39dd723515357fb06e300000000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1770000",
|
||||
"hash": "0000000000c72df21cd00410b6ac1eabe0df652bd1de1acc8f7de0300ad0c579",
|
||||
"time": 1660269507,
|
||||
"saplingTree": "01a8a916c0922f6255920368229c9807bd08968d344e4cac1562f61d67acfc1712013b8c478aeb325ce90904248fa79e20088787b4c2f5c8437c4a05368a8e5286131801ac59d1048b60be971ae94994637d43c9c2b5f0096e3010388c15564bacc23d0a019c21f5c2045113d187f254cac44d84eb90e6a2fd13bdeb048ded1c7f6a23a57101f84c590ef2d748f73429528a1fa8c2631153ec0c9826fbf7e9250a3afe967d4f018a2e6f62c3adc2eb82c218c405456c736c0693bc15367db9f8be0098414345190000017645afc760c2f4677e7d0e6a06ec930b414092367c1682683bd98275a57f194e00000000000000017b5ec5fcc4428da2d6818a0b1af313c60954ba660e2e3cda6f450099eb5b221f0001ef818bb1081751e68503a93549f307ffc9c315434df34fcf02a015902b46cc180001e21ed183272df1ff7b85b11dba8cdbc6a4658e6590af95813267526b37026e38000154e243e3e5358aad170a344b945fecf54c40891e8bb5b5d3bfc4630543de7b4c0115c91e5fc9fd96cbf12a77777f1781cce55113f548b87f067f2d7d1af81dfa56018ed21e6b0098dd6f0902efde81f9d5da8d7068e8dcf01ab1c015f8f5936c2d5f017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
|
||||
"orchardTree": "012b3f0b7513e55d38172c1823c882c72d905ff9202441a232abc52724539f680c017d53c8439f36df116448eadc2bc91d55a0f312a738dfc2137bde4776f9b6a4361f000000018b507a097c3386b3feccd3d9c51821906408e8b83ccbf13100783b0e837aa909000177c4e6e1b75416e4d92aab042e2725e050a5f076b11f2f4d2af70882202f33390001ba5d410df62cf48f7db09537333dc58577284c2715b8410ef15c363a33b7353700016c7e3e4e5cdb142564d4e4cf19d6daab524ec8bd50e53067c526918e906f171601b68907c6a34bc6281e7a5c7d3af957c02b21f39d6f0cf328dcc9d7a9456d8d3b0001e343584412ad061010f7a51650bbedc1158a89e38a94be46f0e5e1951caea61400000001359f584dee78fa97c62744b858526df6e936126f0abcf399e5649895e5d4160101a93fb5f55a0ed85c3dfdd84cafbe185bf7cdcc39df46f20b9e31c023f24f560a01a111bede6c68ba8795c99c17b615d062944718ff12e5d890a90128409860982101374feb2041bfd423c6cc3e064ee2b4705748a082836d39dd723515357fb06e300000000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1772500",
|
||||
"hash": "0000000001109c6049218a78d26eb16b0d5e6a46aa1918b39e8adb9c8d892320",
|
||||
"time": 1660458773,
|
||||
"saplingTree": "019ad7b4358d62778021a4080cf37a4217e7e11d2d81b38d3683cc2f8729a8aa3401d2559936f9aa3772ad890675b4c7df6c5a0a796274a172c303e74980c335bf5b1801e2b9ff9e93c5fdd152ea2402a01ee5e9fadca97a37e7843da1cca8703e5d174f01c0107854df305aec5fe7dc0fca338428d843445b7bbc93b782d44e502919530e0000000001715e39e97a1895488da1e615b3ecccfddbc1505376cb74e4a6cff974f4640e15000001c105403889f34c2a05928510ad2f88023b8823c92567ba29e274c596340f516f01bc4a6849c7428c63b06883f7d7c887f010ed34d4eec1b6ccc5cc6f1c9a5eac4401af80584abf7e1d3ce026c3c0adc9d94e9542f708050625a68f2d9dabdf4d1969012a4851fa8b991aeaa5546a9a9705b382d2045da32f86b6f95b5f9f0187b8506301b63830dd5a6d176498d96e4b6329715386e24697025a4148401541d0d745d70900000001d6815a159595ce4f0b7dabb78174abef61c557fbf5179b0b06ac629f5972c26e01b6feee0caca8f28b04cab9a5b7eda707f289719fd2b91c809f603ad23924c56401c0a384a2c6a8bdc8ec47b715670aa3bd71601872d593ad86e2f13ef6c65cbd490154e243e3e5358aad170a344b945fecf54c40891e8bb5b5d3bfc4630543de7b4c0115c91e5fc9fd96cbf12a77777f1781cce55113f548b87f067f2d7d1af81dfa56018ed21e6b0098dd6f0902efde81f9d5da8d7068e8dcf01ab1c015f8f5936c2d5f017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
|
||||
"orchardTree": "012de2bdc6851434bb9bd9356387a3ef5e2391cc04ee303c5a9c3653e60c26ee3401a6ef18049293bb3cdb59c4d461a9612811b8ae2bca828884131f37603e456b0d1f00000142caeb8a953a2a3e03630aca15b02619af2a10a70de045bbb17fed9254bf6c16000000000001591d52db18cb890feda16bf333c1dfd6e82d76cf8972330ceac6483778eb5d2f00011aa73e3b53df7e1ee91b1ef57f87ac253d680e07b63f72dd11d6922f4aae700f000147cfe08c6d5288825731fb3a99336232656f2cdc8f31bff4ee54e693440e7d3900000001015358ef9ae69af71a7602e351a1184e95854b3936834c7aebaa3774bc648d2701cd7575644cb25d5a1c7edcf4e3552520f2da1b2c98fa04bdf419175fc4a1c708000001f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1775000",
|
||||
"hash": "000000000019cdcd93e8a0704c40cd74e65424e4bfaaa2a21227db81355ccb46",
|
||||
"time": 1660646308,
|
||||
"saplingTree": "01597a94eed997b611665ea28eb0fbebf872dc4187b81baf2d7b061d9498e4be07001901289049f3d2fb297ec300241c8ca66400d8cb214272da11c7dddbf249a83b363200019448787f294385a1cf41c26b5ecc3a9f2b552d85dd959d71ad8fb79ab82f315e0000015692b62820a0affe568750d52d557ae99781acf19545f95404b3d7122e00e50d014c13e6f6cd1938716b4870921418d553fbc13ad5e174c720556e43eeedff992b01d5af984301be600c7cf840dc1fbbcdedb2d2e801813bb68a01be00c6822d5a6a017e978b347c274b38ee016285dd14132e83d59786019d1a46ce0163771dfdce0c000000017b24ec22b8e69bfebe3a24693645c8905f2c3756dd67d646d4266c5624ce29520001887d900f0f1c22d239b925d77200c12a7a637dfc7723f08aa34c0808cd1740200001e9358e668fd2780e13975fa8d46cd6ea762252a2ce849e2c82affb1c58edcb190000013eca32c92486829a47625ea4acb38897025abb7af0d1aba2d12c9e828a8d0a4f000000000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "0182a273f144dba41ed805d348a8eeae5692b737863da75ac3384af983d54fd109001f01f72799b4c4c3ec3048b5c724b0edf553fdac716a6f2e771b8e40be6f9ece050301eec827aaef0952edafa106766d905c03b29a58b8f502b579e18d6b8a36b9a2120001997dd960a2474458cc8d914bf46232ec351eb5b3ff614e455d0fe6cf1c0d961f01726cc90e9a3d35324d807825bc4eceb7c976735bf8d5072f24f916f11231aa2901e4d4b64f108c937551964335b41b8a6e43bca7b2f2c468f86bb27a938ffb912001395fc86db243265b7a2722b3a49c1ac5449d671980bfaaebd1f8b26d4324451f0001056bee64e920ba6e8604fa32b294591abb40b5fc35b9b6f257b3b465fce371270001ea6a4ce4349b49c0b9788bcf94d27cb8c95f8f5a640919110387cd66f063e113017d6446e58ba6f7f8e7f60594b91bb67739986c89c670ad3996205ca9df7eac1100000001188cb8c8ad6ec4d5a9f90689123894bc1d021fb63d53347246ef21657c04f3110000014c58c6ffa8b0fdd89c604006318b7901020edcaca498e3afd81af7579dafcd340001f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1777500",
|
||||
"hash": "000000000114eb90f65670123d685d4e1ac4208dcc39a9dc1111060789a2c418",
|
||||
"time": 1660835117,
|
||||
"saplingTree": "0179c12f2782a76e421834ecfcd800a5cbd1edc79663d3c58d25d14200c60a2b050019000000000000000001091c24ca6d7180e15a22a46f9fa2d0a90783ce8e8bf4346097b36c469f7859220113d4335546307bb7558b2bfa8a309abb341da3f88acfc3b7d0523519a993662701d99f7f7123501008b4b145d772c66240aa9bdfe1fadd8cd420a3f159126950350185b933c8fc56ff06180fc5d2dbaf7234d615c724b1dbcdd4130ddff0ac732e6e000000019fe9488e3027bf007c951a3706ac7422d242c3ece25c20d5fd8254fdc77d883c000177ac81d2104e4de33629aaf3187c8daff10899ef791b1fbb18d2f2c17296870a0000018679bff98f1311c6b1c4df01647556064d2cd34087c8f83fac1ee99babc986600000000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "01dde5b2585ed32435eba299a594f328983090c620d3e40c58e2c457c5adbc7835001f01430c6ec1ba7a552b02f897a8789639760786e29fc4ce7e133f4d8768fba4032b0001b99142688612333a2926ebf2b271c50018a4240c9522cdee66b68c4ffa8c3616000172a6fbb0176b644a4a11f6c658324fc5d8140845959cb3c924b190a8ef86e22a0153250a8de904795ee993882737ee5f13458561a0f78d6ab1432e165f0b19ed2f01b54e695a6d674420b22d8b52727140dbde2d83ed45966a2cde91393d89ea6a0101d3f0b23da54e9066a2e4621baa67d255a69f90f788e35975e5fc14ae62eb9b3d012d59e25ff1850760f04238d20341db41c77a069ce6146af0c66693c5a7e84c04014caf0366131977fe12e0ef793ad22fa3f75df7a2bca71acba67bbb206797ff3c0001ce3e5c7e4f546304954681fd0e8f0e68dc38e5c788c99c1cc2a5404c160df93a0001038ca3c0ad85cbc76b94531a2c8d7efc43af084abdc88d2d28b30bc3a8ba4d39000001aa7b1980e5656743063400012808a5a2920431959e0b721c9a915b37ca3b450801c4accd50986a74b77eddf2e5191d20e40035f38bc7c9b91a03339af16c5a7032014c58c6ffa8b0fdd89c604006318b7901020edcaca498e3afd81af7579dafcd340001f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1780000",
|
||||
"hash": "000000000092cd2f7d70451422ec93bc0c32dbb4b7ba7bfab4c6bb2f646621cf",
|
||||
"time": 1661023245,
|
||||
"saplingTree": "01eb57dc27bf63678d8d3a9d19b422a4694a9d43765bd308abd6ac1ee838dc4524018572cb62805c814962aee358456f07f5494cf3fa7b9ec1bc35dcd8f217e0920b19014241b45704d5126780eede03872b8f87501e4184dc68f977aab8e20d1e83da330001be1052772495777a8224029288e3cbce5f6528380c7dfab653661fa068e43267000000000120d655140e8a7069e4bb227ef3be10337d1fe805f6c143042f39e04047dcaa4801edee36cd129d61db744d5c70d749a066ae0903b5310f309609d014d349b7e435000001e4730f055d123a824efda2abf609b64bca407df2582fb188f15876c17a93183f01428fe4cdc82c312139518ae3cfed4fc769329bc192c724029a38dbf221557a56000176138376dab12f9063c0d4be21178499d1f912756be2dda3cb7540469376905901b261f10e8b41673b7707c93d6a38c9b3ca331d7beaf312b17dc5536bf9ac262500018fc9b4ef24688bf42b782c41232f9ec501b1c4ab6c950b0c41d34eb23b17402800017412114010d9ffb2f7387eda6e29942c0c80b6330788102467e4a0afe325382d018679bff98f1311c6b1c4df01647556064d2cd34087c8f83fac1ee99babc986600000000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "01b02222e4afcd845dc497c76d04b9f6ddb9081d01b9f9d5a2aa05fbc8d1443b1601a0331c1517205b7e943e9668d61e77e875c662dc96ef7cc96299b6a83890b41d1f0000013c6b5a330ccb2ba9442273535baa2dc7fa6dbf999b72b31afe032c168f4bd7080001e74ec029cd1c152dc8b09f65985d6c0c2c78e56da80c941538520a28587d9f160181a58cf9d3ea78a5685698258689ebe5fa4ce66d0ef9eceb6ae478750ef63139012e734c4d341726e1c02519bca7c04706db73fede3e95e9eb81208265d7b5c51e01e12417482428167d1173531eb01c4f05257c8915bd7b3c81668f7e4c0525d91901673db13a5adedf791c6b644470c5f61d202abec03de1ad93ad9b534e93925e0c01256c8aa3be887192ee742261edd0c2788d1b625442150145f4d3b8ed4905c218017fcf9eb3250652f2d0edd5af7d812909570a857529d71182f1e67fe20131b43a01c11b33655a2af90f359b1372d26da8e92bd8885107b6cc623e96243d56ec051700010d01fcaf34b9d9127ad8aade1bc05ed6c70040a7146a9fd596073bfcea1aad390137638774e0047f7be64682876e9fa29021fb428b367731ff1dc7701b58633e390000000001081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1782500",
|
||||
"hash": "0000000000c96f27e270aa37db650cf15c6b75f1fa59a947cca4f41abf004676",
|
||||
"time": 1661211894,
|
||||
"saplingTree": "01ed3ce95d71c92565819babefc0f1026beac767dd6a162ac0ae9b15f619446545001900000001ac44d394a0bac1701dd6b74ab80f53729963826696a1da89aae59e12cf4c1b5f01dacf1de9631ba5147d124a2502808ee3eabdccbcaee03b03521cf765324f52710001efe591a8f59c1b2271ff973d8204c588b21747c305a7603cac7476f62424ed0d000000000138279c0da3d1576d92933860f5de697d212036da77df1af780bebe1b0fb1df2700000001734a94d73f408cf9aa047cb69f2b67382cd2eac7ad9fd8d4dca6b9ef4706d93a01323df8cc8affced4411de770643c86329441d276e3d0af469a5387baf14b702e000196ea06c6c9b1bffd35111b29addc63a564d780556def4814bd2bdf44568add0000000190fd9fe569252b58449910aeb7a9d5bed775699b04b2b0d2373b948728ce291b00000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "0173e8d2617537311c62aa77f610f752e4553fac6d8f8cdb05248941a2afc25f0b001f00011093e632be4376c29ca004fa22ceeb426c283d9006436eba5185b63ec4adab09000001ed13450a8b52b2467727d09d721f8054d408cc007a3574f090bca255d94da90800012d81bec0d20bb2214b91f3caf8d03c66c779b67f06a9908bfb239e25b2156c1400019971eb95fe89873ce0271ed1e70b8ea4498e54a81d4db7b092fefc7b9f540212000146181485362325772638425fe945f012e9b3884886dfc0f5c689f556d40d8032000001cbc56207feb2d10de3c2dffcc93040d4ed95a1d0c653c9587739681a162a601401aad0d336606800990785d124bdfe61ffbd399167bbc344f5855d3d4de468de190001464e545a0f5a15e6c09f82335c092a66ff8f10d0abe8dd0db230ca90dbcc0d2401f11d7f967046007bf41c33b30c0dc3b52c1a4a9cc3a7a85657a2fc11a39d393a0001081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1785000",
|
||||
"hash": "000000000048a2a2cc1132d171fd58ae0483322d33a4ca4e5814d2f928358ddb",
|
||||
"time": 1661400251,
|
||||
"saplingTree": "019cdc2862d4223e86a3b2780f9cdf19b2b22431faa4c849d39f681bd01d32a0630145fdfaf100b993c09d16b34c6bb30834fbd9519be36b7cf5adc645fd2727805a190196199419320b54a8acf9a9b6883abf64a7e80fe75a17a6227bd47b8a2636b84700000001c5c6f40ccdd6c8c7c8b8257c1dbed8c9ab5b99320c0b367bd80c471f676dfe3d01324e46f8529d6becc636a9b57eb864b3cc6a19f9e1cf3da6fcd4b96456fdf1350111fe0737c21b236b783ca356947277888a785e569d1c8953d341741df944742800010e4303d3df4670374eec9b5fff8b9b7cead7c3720ccfc57bc05e9f15e005420a019a74161a408604546f401b8616c50dcd38a10efca83d1a5f0bc87f43ba3359670187130c30b7023f7d9a80fe75fcc1c7864007ae29e63c9a85dff0cc60e9055f5201d7a644ef58eeed93ab36be7ef27d90dbe43696ce2c0c2d1969e3de9f1cc9e51c013dc034d5b0eff46aa39a5461cdc285a75705eadf417e14fcb6a07feb4ed3a0530001f3a3cab7e488e34450427f914284cff92aca699a2e3bee42c42212419bb72a00000001c9c6bba2bb2a19be1e993af5f9bc35979454afa6237a44ad46ed0cd06c7eca4901e1a9472dadbe9da7956818483b066069ad0030dee06a787200e5519a5e0b96600001757bfb5957a0fa9b4fc9e8c105df1e9f0f9e156d653ba54a5fd2ebaa237afa510190fd9fe569252b58449910aeb7a9d5bed775699b04b2b0d2373b948728ce291b00000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "01fcefe6872d4fa4b8bf4cde106616dd137593b43fd761478eb4d1dd6715a4cc21001f0000000118ef33685241afb399b6d9c0697404998528096ae550dcd75365912cfb64953100019ccc0dc23d60dddcadb86ecabcfe1e5d42829a741f7e6705a368ead59ac492260158cda00196a2f05b86d770724944d4db0ff55d7442962dec634e3968822df01b019f815362606a86082ca9c3a332d4fbea2faef7c6be9cf4f248fa09672ab6012301ce1619c190c7fb2f62560a1723b9daf6ab97d03406bc4537ef4da5dbe4ea993e0000000001cbd9a766f5802ce880a9741919503a6b5b2e58104d1331b7c477d758aad8fe1400018ea310e6e8006b04172395acba6ef11cbef90cf053489a7f8b3287be3935ed3d0000019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1787500",
|
||||
"hash": "00000000013abc1b6771cf8ce5d7b385bef21fdcff3e809e96ad9ea119c0d0fa",
|
||||
"time": 1661588407,
|
||||
"saplingTree": "0103e6f1a4409bc20886cfe28eb0d0eb002050edec85b427da3164dd8e8f3d7d4500190000000153f1e09fad18b246d3acf132c9abb67fbb13b5116eeaa226b8553d3e19b25a530000015af73bf615b470dbb8f7d024c13ece622e091cce7bee801e9d54c1ad4e40e02f0149816bfc59f0deaccd1ada4c1e86b19e4413d0721784c4d052c3dc67f5c1805000000001b50664d826ff3b88aaa2ba08c69b8f21dfa710d79860c1441b5c6dfaddae483d00016378d71fb6680e282583c075d721a06b4b3b75d31df554364d4b0251def8a71001936597568d67c917899fd32efdff7afa023fbca108ebcbb402bf0ea9baacf33701f1dfff89a6bedf83c3219c1fd7261883687af993e64603b2b2be8a5fbf89754b01d31d49250f8d4970c03c939f38c3e321a8a96cdaf82a3bee23e73bd7eba7f55a01adf75581cb0b5b7aa87ebed8f1e54ac62217bf676b979298dd8b4300506dd72a01b9e02a948bb911d47d1e51ab6c7050074518cb1f6ff3856f93356d6dde2f8e0700000001f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "01e6a2858cd4376f3333f54f2033cf1211a3bfc0eb24b9b80ac2cf1446dae2f31c001f0140e2ea00e36545674bc7f09751f3391d628c78399ba803b6173e9d738a5fcc320000000179b60c3bf1b8cce227df010f5c49c22c7d777d5209738683c9b697756b31a81f016f91c16f2e3960269dac6fb301b534e027bb03374126e5dbf5e202c7422c861800011e71b797efd14accdf8a33abde065d670e6fa02a777d9e7d47be234e86b5431701deb97e143f39b0820e3421fd8c38cd340bc9cfac21262ddf86e0eafd75ff570a01b5045572a2467061c9282dea3618ba48f5b6dc05d83d1ad38d57e70735e8b514013b5384e5504f03e481ba341a2f14cd449a0a390acfeb5c56a155f6d99e188813000001794870c4958f10ee3be337bd2bd6ff6784d19662c242a40ad54a3f48bb2523230001bdd331201a0524ecdd6f812cf323d1fe0173f165c181bd74199480a6211bb91f01782a84e2fe01efa2b41d6875b0537d55ead77c75811ee929f0cf9236f976383700019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1790000",
|
||||
"hash": "000000000100dac22c6e557360ef3c4283d9f60c6f4c5a1e0076d3b1fe97d0d4",
|
||||
"time": 1661777577,
|
||||
"saplingTree": "019d3cd586fd43ea7b42b862cf823edcd874462ed3a955f1c1dfd371d86dccd5700019000000015df1e3c5a797bbbe4f4848f60f21c0bb5f7bbf3a3168cee8a1cf5689b22286590001144654b0777c00cbe78b716bacc819d39f13945657bea3ff7e0c88bf1f68894d0001164cf0832981b185067cef308aefc2cc7c93a218b7ef456b3813f58ec2162d4301ec4614750b2c957a25a24685bbd536845cebe0739d3baad2ce645105e8584d5d000168ac0ff0fb620aab894183850de738f2a79424b36e11d5daf6a77e68b771081e00012a04464db06df9410ca6b17e29f72c4b4b3c5795cee583c5422168eabb7e84210001e8e58e5db11fbfce460285fef5113a83e8b5f0ac542ce3887d722a8e2a413c2f0001fd8156cb2a632eb0265b6f49309d30a762bdbbe2e8d6148ed26ab69e6861472a01187bf80dcc94180caeacb5860ca78ef66b0977c7ef6b773428a3e19b676aeb47014e7c8a253ae2f1d791c4bb7f5026736ea71839162c1b1da41cb6a6e64a49261e0001367383639b55ca56d7008a7a64d73b875703860d1a657cc1cc0df5a9ae5d5c2b0001f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "017fed4159767f6bfb26e8136b85e4a179606f7298de17664fdcc220df21b7a02d001f01cdd388ecf7f914294552caad1d570970b47974ec73494e2f04a80e7b391afc10011f6a01e993dbacd0a5487b8a7b06275a252d0d3895fab490e2030a255a9b0c06000000012c0afb3c10723bacf85c6611273be9e7d4676128dbabec978f0fa5221223893600000001285b5cb86d8544386b6a36c49e2bdb88fbd5f59a43437cfe271d95dfb4bcf82e01b20850820c9e07dfee4fd137ebe7f4d92ff7e318f3f1581b4b53014118d6d1170000000001045adfb00ce9f9ceddcebe2658b24be1beb059888c4f93f68bde2615aca3bf0c010d67deaf001eabcb4ded2f92fd69bb00fea4118f38c2b18b9dc2ae7e8c52480701f6a82e767e738485f26942fae63d20665479e996d8404cf44a451f9585fc5b32019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1792500",
|
||||
"hash": "0000000000feda98d580a542579c369cbedb9677e2a60b45858dd3c3970aec48",
|
||||
"time": 1661965969,
|
||||
"saplingTree": "01056e79c206e1ec677117980ba8ffd3109f8df1911bf60d6a1f17194ca42d7a23018271f1b6a7b1034a1cb52e214e01fa76fe9b57b4ecc51513fdfee851a552e55c1901d12b2f51a9766ca6bb0e39e2fb8f15d3d27a620b7807b156df4d5519b115c239000166bee33838512fb2a13169dbec4a023d71eddccda1f6f364429b9012970e430f0001f498d5859edb460c87552d732c800e3cb655f87dd39edfb1123ee44f170e6b710001b8ea5420dbf802c4aa031ea066906c68e0b134ecd9f7ce1a8a9595301621b1270159b5fea41595326ec50f60bb37bc915c5525204dbb077ee6096a733e5e6be95d0001a18b7820f47bf1fe5304af022fa5c0dbe4d6d2b3c2b8f43d82e89bd29603ee3e0001ef928eac8166f4fa704d28411595a6d9e48fbce2fdb0cc1c22ff5eb65b8ea60301f62c314465ee352a914c74a495044a37f32ed19057b1e8b1139c24ae5fe1eb6d000001a54b39b5536ee32eaccd615c97b5894e59ca339fa58b170542e565957973c2640001abc74ef752650de527e13ef419675a18fa8abc0ea648bb79c9bad4ddc3a3c33300000001707d2f9501c9e36a368499bad696bc5aaa721b6d08f9a40f4ec8851e9deb373401f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "01d12a9e0d3c96789f73acf81fbbd59e1352c9510eddd29d7b3e79fdb6c34e873801ac79591488cf371014ca4577af93bf3b81be2312c23315f0d30007fee8d5a22e1f0115bacc61ac12c6c35f4601d1fa17909700158b676b305700615c8b22c50c0f11012075bbb910e3877bea5dfd4594729900eef32aa66effa3aaa2354a455a507f1801f5a06cb7bb2283e8e1bb8e5eb22ac57741f666f1c5ef2db8e5f644957c86481e0001f4924dc1afdcef8b935fda84922dab90e523b0100e329d24295e4ab809f38d19012ce7342f56d5319d6c54a63a326e33b8dced10d6cd696abf270e8305d8b6101c01e05e25ecc131faff939ad965861a12eda859ef69fd6928c65c978ae0d827592301d646e453f3ee10362226de45b8e9377ac12191351816efdf5d3254668345422e01b2642250d10f8ec40181ee561a18b60870aa700977107805b91d2f52b872f122012eb7bc0fff59658e28f1bf9b79fa184d21ff2679637745b2e6c51657993e001b01a50a617a7ffe94d3b52041db8dd9a0830c2fe42e394ce95ad4b6265bae2c7112000001f901961738a6f66400aee2908298ed5b83c53adea294db77b57d10eedbfcb5370187ae41d92d3780530a81c2db1a35768479f5843a62c64846320039681551062e00000000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1795000",
|
||||
"hash": "0000000000080087f9812e87732355691e4bdd43fdbbf517618959c81cccdda1",
|
||||
"time": 1662152700,
|
||||
"saplingTree": "01b68aa765ae73bf3eddef03a2a0127126964d9a543859d462540467a4772425610019000001aa8687526bc08a818db298cf940aa78c30792a927a58ee1b6d11715b2507d9280000013788d7a49d2a299ce893eeb4175e702b4380f070ccd32d4b4beebdaae9d2dc3d00012653cc481a3bd5901dd5728d9754f9555932c00647c15ebd5a13cb1db579383301764ef57a84dee173a7d8b135065b75be0b012d5931af754bd339f6572ad84a5a01642fd0ec665cb43eff91032c290ee9cccf50b234af0f5ea94798973b3efc392f000190a2aa5d8963fe49141ced07138011754b00493211523505d488a2cf3ba24a320168569b74e5b9370152695d797cbe7174538b8e33304539f72efef6b58a9a4f04010b05111ee7d748441446ecc08f652247197356bbf83d73d7d07448ed4754192b017e137de7b7b21bbf9c3f9720196427864e7fe2fd8070aba9237b24fbec95914a0113c1af933a4048a7b09cb0e6ccd27691f4649385596d8111fe602bf4b255db29010470b81e369a600bdd7d6bb96c409554913044eb1c66dd7659a6e68129136165014d7bd573014ae0f6d462262ec28df786ef65aa9bcd5e2d6c54f26b4ee3633c2b0127bd15f9da068d7c81f1396a0ee87b700755451b9f4cebfa11919f3a4285713c016bce4a3d7e7ae87037b3ba0b9789dd1a1737e355cb862aba330cec03aec244710001707d2f9501c9e36a368499bad696bc5aaa721b6d08f9a40f4ec8851e9deb373401f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "012d8880e630c07430af4529f928e8d5d6d7f19f69a4449e7b6ce739f73a2e2b2f014fc16e1b120f140566bfd6a4e40797c72c87af205508ae98259bcdba377d3a0d1f01caa4adc8466eb027a6c6095ab1a20fecbe058ef63db381fd1d1f08628bb5a41801235d173241458aebb1b6d757489969e24a1a09720606ba56b1eae566bfbdb01300000126b2135c0942f13da6de1470ee2e5bc26c98f8d6d808ee71f2e7d7683ada763400000171071f52e8986c28e68db191de9cf8b88e39af84909289207ca4f56d346e85160000018f9f481c4fb6151f6868afef177dbd28f208ddb31bc4951185f31f4b6996420d014e57bc25f0a5322059b756e0b4bea83442a5e418d3486b57c617757d9098552501e0e364cb2abe4d1967bca145c4af1774eb2d84bca3af756581870661685cfb160001eb3af18b241fa811548c29205f42601bee7b58ba55fc529542836f1cf477bd080001f63a616ca09e1106337cee385439f0bdb7e14ebf6d2b3eaafc4c69ed04f147300000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1797500",
|
||||
"hash": "000000000062762b9e9de093b9375fe1bfc451ff05a2c11a0339c90ec3e879b0",
|
||||
"time": 1662341718,
|
||||
"saplingTree": "0125d05417c5144dfe8aec67961d71a700c93b51d82486803f8c4719be2708274b01c675445684630272ec881b8283397137140ef8089bac18b3f07b0a7f3f8f6c54190192d55e6fef209eede1572a74f52fb42e347bb17221b179f28443e8ac190e480701489b0714407a588eaa9b6be421f77049a98bb005b79c3df1f8d592201360f94a000000000163669ec32a05e1726bfd63268924daf5bb3f6794a05a7824f87492708cbc3b2a0001e4bfc91695ef7bce9e29f48595b8cc278af7b1857039f2fdcf75bd58b579bc6201b6a313b5daf1ebb237d55661ce5d6f6a93018b407f2386635b727bdfe8ea6019000001dfe1241acfb16d56a4163ad86a0bb7b763b4f5da28b17b4413a3e6e8dbb7d728019a4fe2a045f95d86fe1a5fa9d413c4f36e42e1f03fbb2b5bed842d96d3f71e1b018e123a755b1d549c6f87ac048fa15664bbc3b10a0ef155b046eb4bc7e5b7061d000000000000000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "01a23430438ea5dafb4b7b4db2111f6280226cf69c172a8a763d1218f92ed6c23a001f01e4aeacad6fe90d4122dffbad65537ce856480aadcca1866d9682b2b968cc5e2f000001d142373141d5e6a48be5d097631fec923007c886d34e2b1ff5fbb55fa4f33810010f5906e6e3c4bf7d8015ff88c532795ea4b49f35dbb41a6bdf8592d09439a92c01c6580c00bd5c6da40f2026554c86788192aa5e8cd5f7486d8d51cc258cb312200001e41507a73031aa566ffb009506cd5b30992b0ebd34fc3fc78449a886a4317a0d0000015b224fa4142c9508f0b2dfd1e64fff63e996b697a4dd9502f9a3fd917cbfc42200010fbd0f1d4f428961e0b32b28994bfdd80e1e68b330883bd0889f9ca8f948b6190136b0d3765a45a227aedb1925c5670bc6364856ffcbaf19b52ab06f179ed897350132fe04f3088f035c4ca4c818b3e720ac26ac172f30b27dcd3e3bbcbb8192c11001fe9e4b5837bc7acc9356055fda92475bd0593e36a180e93549bb1ce2ab52e20e01f63a616ca09e1106337cee385439f0bdb7e14ebf6d2b3eaafc4c69ed04f147300000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1800000",
|
||||
"hash": "0000000000f908c93f3a7f4288b1f73a5c5621397ae94e4a894ef44ecceaa961",
|
||||
"time": 1662529994,
|
||||
"saplingTree": "01394fa4810bafa4d2231cb3dcc560e43e9f02418e82635d6a3619276e76407118014a496ba29efc52eaf66430ebe6be9d9b8dc10ab790cf624bd9b16d404e32e52619017ce5fbd2d63aee2d62820d40951f1efdb1ea9d4b7b3c40470d44b61b3537303c01b4c88e366d13efe090502c5e1c147a70f9be10226d97a16f726aef466245a9400001ecaf654b7bb55264015e8e5cf8ca20a01f79dafea9ea174a216727160a3596180128dfcd78a6488832dedf7d4bc7af36360a3ced5ac10f26a49451d2543096672b0001867d8b2f5148a10776a109b8cc6c0fbe006256eddd007ecdc8ac703ae03db126000001836b32ac862719c718b58435f87bba6d66a476711854121212299657ab973d440120d4f1b55c4e9e43442c08a201d4518369927b976a61c12add79cfdcd62c515000000109545546d941eb229dba133374ec57484d65febeffed0eaf9101e039db7eb06d01f98dd99c797f8ff594d93854a1f42f17bcf85d22e75fa9a06ce53b395ae1e671000148f810d1aaaa173f8c744fca6efe293f1b7be578def47bd9e45e647a35df5e3001d224af2c322521ed4297213b51e327cfa001cb6b7c04fd647a8c60d5ecff6717010bfec213bc4e3bfb6039ad8e0d01f1b6662367b4e38a2b185f227d77591b3b2001cf2ef2a7179af6eb4d2552757c05466c345a169d15c5da6dc13ba8fd34e3495700000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "01e0e4bfc911716e9548d6d4cb84cf8b66f62df3647a176f9aa3c66453013ff206010b0e83345c27139830a0fff89f018987eaf3b6ed776ea138ab846e064c7685041f0001821067594b408dbac72a9967d0d135053f174f0adc604af6887867ba1a1f0735017f46cc72632f201b8854c67456f91fe83418040e8be1bf983231da9d4e8c1a0000013a6f5f208f42b3640fe9a182cc4653f0b46cf1c62f7ca31ffcd14a542d8f7917016560e4ea4138ea5bcae07924ce84454871d1da8efb6ce4a59acb78f3287a30140001cb756a9be049d541b1cb9d4e58344c3cc6a34863345ac0c122744c026f29f7200000000000000001c5633b986425941f3be7634113839a2de1d40a43529148a16fc979cfd2245d310001c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1802500",
|
||||
"hash": "00000000017fbac093c9dd6d349b88685f369cbbe2c12b9a0c9ed6830bfc8e91",
|
||||
"time": 1662718546,
|
||||
"saplingTree": "018672213d179722fdc5221d5199441fc70a068db8c20a02ebf8bec70dac9f4e08017a0d68f3d30e1f5e3690c627ead792ae7ce1d08761b095ed717db285e60a0166190001c44e548845f6a825e9fdcdcec964674a84c48cff34c112d9828c1d5f21a5502a00000000014aea74e6cc2dbae2ff085618b475820a68a5969215246e00353f68129994a72900011489a9121bb1593a210f489a44b3fb8e2fe7b0b1143d89cf338b1da874438149012ea4d7a0b0e68673080d9f47f4c92728839fe0991626ae337f9ad6bfa598872b000001629e5d548ad857baaa6716719e67708b8b696b77b23ef900d49820d028ac604101cf6d58fea133cccc9307a56ef90ca4f8b32af3fb8b494b583724275da946a1370000000001cddcf07098de80d473262d9e1594738168a73a2e27bb986b9ce8a43635ccd03d00013f6c68973c08f026717c32a6be0d4083febb8c2d63f2e895a6d07021afa3a51c000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "011618cffe419b3ae6d6f94ed2138925766f616262a81b11220dd64310db9a602201b50f80ddf6be2c181b9e4795a48aa30def25318410e19071bbcae266517237061f01000ec86443fda797bdee68d22ad1c8e3159ce016343aed4bdc4044b30808ff0d000192dd02c90b3e87a42dc8a5a1bc59b832e21937614d71aec0ca0dcb4293c8802e000000015226ac6b1c91f0b90680b4cf4175a38450727aeaec887792adb9ef13c411de0b0001bc359c17ecd3d5fb2896bfcf890ebc51de0eda3c55734dc24523a41b24267f1a0001c8b40d0cea90b7642884cdca72286ca44b278ae6e9ad2e2bbee1b30680b1a60d00010deddae26bf93da080b26610dae9ddccb9243015487b922f3c05198476c4ca2400019902d40b5aab956a3001b26499f3f689c08ffc9b5989d14840ea01f443dfe61c01c5633b986425941f3be7634113839a2de1d40a43529148a16fc979cfd2245d310001c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1805000",
|
||||
"hash": "00000000011cfeaec0ee1d6230aa8d3642ad4c22db6d541ff43520ed2035f7eb",
|
||||
"time": 1662907506,
|
||||
"saplingTree": "016d31b4c64ae5c17308b9d90572e6de5993b3755e861e040949fe4438caf1000e00190000016c0be2c033839aec91d32becbaf583282f918ad917865f323a5c88d687f1bb1501ba7e5ccb08c9579b4fa81fa555c3fa94e6872eee2bf8f01641f6103e9a975b4b019c049c4abf367e07e168ba327e86a7cb04d11d9ad446e2356f2870cfe9e958340001f29ca1e8f612cc422bfeb2713a89062d8695f44436cb8ac0b57ff5f2f1d8da5401e4195653dc559c9590e0248719443c666c82d6e7521d2688d32a914070eb7a540001f8d79dde2f627bfd60d0c1131cd3ad6b779c9619f6dbb0ab0515778a9a9fe24d0000000001235b463d441fae8a4ee172bb40b1a3853ba4a07ea44377b06295db356608ea3a013c2d33c9c5afba6b720ffd9ea7c69af1635c2f98d291e2eaf358f2c87232ec2a0000013ec9722bf3aeef59c292db71e3de87d2a3868cdd2902b867328e24c30c8dbe1b0000018c2d6adea2ad4faf20eccfc2c2a2c59192fb53d3204b3a2757f1c247dadec16b0001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "01a1eac07fe90382f2003de69239f281a9798b7f984bc20a3559d5f886a9e07035001f019f6fb5bad161ee9f26dbe23c485e0f25d083e17c1e68b91525f33d659d35ac1d00013957a17ebe53945197d684304f29a77486a0f081c6a4a3f4c73b33b88a834a1101e151d0e8b30866bf9779bb3d1105c3124db863690458c855537e967679bd903c000000000001bad8cfde5bec689cc645ed5c9e37f6e6f1753fa9a95f40ccfe25ac91bcc2481e019927318c01f928c75112b5d0c116e9a7244d58684396512e5e24569acb94581a0000010ca90449f94a0ea6eb64ff88b438e14649280613827022401d54a8635e63943100011759fa02e2fae57b40cd62bd3829225c15bad6430caf25a526d51f329091293e014660573ed7dbd889063d06f1d814a4f28a5ba4da76c2c5c8e11ad980f070353601c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1807500",
|
||||
"hash": "0000000000b92234ffe6efd360a2fb1528e2f5cf891a54e58a9b83cd6c513ad9",
|
||||
"time": 1663095814,
|
||||
"saplingTree": "0146b9cd1dc80b44c19108d53ecf72b5bacd405e8ed5c8038c413ad2d7bf9f486201fb61737c80f0c3816ddc487ff9ba360c60b1663151cbd07c08dd8c09c648760d190177aa27c08a1ff4754710984f2f94c5a101a10f57eb6ad49f7b1df47f7e8a5730014c0fb4e36c1b7968082d0355f318a67e265e5a5533ffcc01b939e4bf1779841b000000019315fe44f132446949cd9ea1f34ec20f32c0dd0ba4f732e563c040c692fd5d49010e6b6ae8956bd722b0f5e3f355adfc7c9b6a39542c07fb0bcd317d0eff8d99080001382db97fabb726a88e7a7baab050a1c9169d2142a9ce0b7f3022349acf74981a0175d44cdd04e213c2d95fb281f96fc7d734c65be70a77b7aa4fef8a4b6fb2475b015fe654f11132fc9c133b46dbbf19b0113cc45715f52dfd3282ede76f6880740c01d14f83f0fd7d09f4f52c8ae9d39c63b57c59a37666d211b9a2deb290808c02720001701df279d9a2270a82379486df546fafeaeb831993cde1cd9e5c9cb17be5191f01cc6ae86e9147b0b1c3f5fb32fb7acc012b4cb4384a1a1331ae3c2324a804483e00017fb2e2890b05355ba797af2f77e38cab3e8ec1623d29a912bdd0dc4a78cf554a0001de17a599d0c6d73eaf1a5939e95af4427f75b89f703749e00d853cce3d6af84f00014f6313837ed19d2b480ec531529fb6b425006f2c1d981077640be21627659410018c2d6adea2ad4faf20eccfc2c2a2c59192fb53d3204b3a2757f1c247dadec16b0001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "013de3a4ad28b5df77a979fc925643e69b7dd26ba787a3122fcd6a445c47d9280e01b68287983d323c90e6a1ed5980ab5b1a846d49340ee6d40d2349795c132a382c1f0001c2db91c5b9baa1c09623429cb4005ae12e521a018eb8df2d051d6793a307eb3e01c6b2f8d93635310d470d4a6d011ea77f59e28bee6ffca3df88f5f2a98980331a01777860ffe739b8047045e2dff8ba77070666075214a0f7702568205410351b39019ed7c5c3e958cb8b9d5324c290ff384dc3ca6cbc870950002f64398478ff1904000114b5ad56c8f210854a1688f47116b5d272fea09559646cee33ad3e6958306a15000001110e689714d772b170f63bfbf4b144c6a48e0a57c4a2780513633d5c799a0d1300010cf94eaf4d5268d9e0878064a458eaa3363dfcfcf2602681c443baf989d5de20000000019776dec2ea06cc5ecd2d212d37023972f526cb2ffa7ce1e8cf8eb4ef04700b01000001c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1810000",
|
||||
"hash": "0000000000bdf01be068ff1f0b21b8b266f839b31ed066b72888b67f672c9800",
|
||||
"time": 1663283618,
|
||||
"saplingTree": "019dbc466ad114f2b4a6d0d91198c0a2c5c20c1dec7c2e7f932c9f9197d76b80020019015b272118101cac0ee6b9b8cf26d5104ab42913d2f5253388bac28e7998f2c41b0001868d5008c587f0fa3d26fc42097d34df49a70508a85a6acccf1263d39cb62c28000001b2325a6ecbf023f3ce4b74c1c14bac8d9c462560dce9611bd7b08cd55ecf4b0100000001cd62e4e2142c664656f956fd0ea8d1de1027c327580398fe8912e6264801650201ccc9b305f6e65d641a4e461ea5e853354a3ac4b2acf2591849e00421bd58fb48000000013a591632f71e1fe214ab46b464112520e98f15d171da599a350f7d8dba79595d018d254447626cf40828102a60e2b433d05498a780599cdf56a14f3888c2f42008000148af2e64d92d1944a451180f1738c9f468f608525b0273967db19029b53ba16d0000000001f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "0180c286284cd52360af960fce56fe7dc339667c35580c75e9bc339483230e932701260b8984058125b5e3558fb65172d59718427111cbc06891017cc7633f74e1001f00000000000001cde58ba8e982a34499406e06a763ede11353d39ab93a5af1b34905cf268c033701226087d6a9bb28c2ed6890b989224791093cd5012a27285040025c0a72b2ce06000001ba71f8a1897b754e9fe37a29eb2c1a93ddc1678298498b4a84da732ebf056f15018073f4aff677a24eaac68c20d271ea228041f1e77710b0504d3f6c0b71d63d24000146b37a3e6167ae7f07725ab4e32247619c37e2a91c87182dd68b8feb99d5a22201e18dad85447ef2e9b8d647c9b9f1e6cef3e1d03f908975cd5e1d5c5808e443010001898b4a8f384f342a67efb3f6c4afd87310df4ff1532b86ca8d1394975aab5a1e0001c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1812500",
|
||||
"hash": "00000000008f6e5ac009390e7daa092a3207581c211e2be70b60ce04b88505c3",
|
||||
"time": 1663471780,
|
||||
"saplingTree": "0180d672d65ad56265eeb9632dbe62473447bc7bf3e598d2e896cdcb798887b53d00190139d37c3a694b0104d15e447f51d96800d260a5a1053558cd4b7134b8c77f803400017175873eec18949e09f6df019a3d20530716d2307ba1f00fc0c09bae6656022400000198c1229f3df738745986e8aabc1d3ec92944865b43a910b8c74221f99b8ee0320000014c8a851d7fdb90d17b97621005c826c8b3ce8ee463062e324e2ad886f202b6440158720dd7a931f66eac2ff934b0765f807c9504ea318c18f606831c80feb3770401252adc68c56eae8331764a2d9d72851e7352bd33364963b6618f47d56bc21c6900017d8e001c7be91297f727edf47b650f44cbfbebb535b4f409e357b37c43a621040001ba60d34966fcefe1b8058d28b784f87089e693fde8b99902a4fc956021f7ee04017547ef39c632d9bfd0cf48e3e909dbc2e0890f2791a6929ca0d6674484f4c046000190ca77d38f0a408e33a8633f140441488cdedf7508a06d04771b53e4e4de9861019db47a02109d9232f4c4fa044613a8a0cc508865b185ee28318c86d16829895801acc2d2141fe4e5c43c11710e48bd0b12c0493b1721fad3b03470a4400157e020000001f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "01cfc873591e05c184abdc8a1a78659e28df418ea5320a5bd81d2d761c1af5ed0a001f01244feb50d761d34ab22b04045ae9eface10df4b17bbded9d1da9fb71e48cf01400018a07d6cf54e4da1fb1f501850cba2a98ff5a8a8a5722896c3906c09447500f26014be19370bcb96d495a7c1e9719ccc057104a66a5ef6ea217f3fe29e26459131d000172482cc1f1fad25e86299334a24a63e6e86c0b04d319a230bd3aba2dd38279110124de7fbfe4a050ae1427d73491bdddedda1e2e7db48d2365b3cb108eb9833b34000000000000018eb08300830f838c7d212857b7e045a05458264f44f33a9f5d726631634da70201778055c0e5ef68c30c7fa81eb67f0cbf32b64dc5d6ff8138f380cb8f115418260000010fb2cb03705826ca086f605974429cfb0dc60df50a0d15d06c2cfef80017902f01c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "main",
|
||||
"height": "1815000",
|
||||
"hash": "0000000000ff971a219416d483ec48af6e055182ab566572fdda7fedb2d15368",
|
||||
"time": 1663659973,
|
||||
"saplingTree": "013010baaf80742269ab7061e012c90e4ef805cc07bb62ef71869fe0badc141f6901d003b1d99e5dfc26894e0b1f393399560374425c34f89853df55a4e32be616251901b34b07fcc9a91eb89bc409a51f452bd21327cfdf3328e9913289760e464824710122ccb843dd5775365b143f0cd1cf309c1940a7c3ba7070c92d041f35b8fc3f4601da8ae93a665218fe154683bb6c75e5ded94f1780358591ce7524ca324f22a53601962ea341367529954ac02e5479c129499aae960e8448ec5697f216b45986b3040168c82ca2a31ca7b833886ee0b3dbb60c519a917b2dfcb6590175b38a0cf17944000196c2704fda3d5e124118cc6cbb1514bbdbdff5bab3a334bda2af27b3e1c7f42a0103bdd297e11f1bbf4d438079aefdc797b012e2e90903194011f9f23cd7ed5b510198ba7189c4d8aeaa58402a655214c69bfa4d09c7818a9691629a8643cca9c91d0000016fbc427c11055083f9330cb82d07c7ce7ca37ee43c7bcc687683f6a50cfdfa3e01201e7639e97113adcb84691cca72efb6f8f44293109ccf02f1beabe46412b75c01046ace0bc25d6c3889da89753306be90ce721bc463e9a6662f3ea10141b5db2801ac5fca146985e234d3ae22d84b09f7924d8baeee82fba8235dd9db60db8f6565000001f513025ec4ad6f392fa842db7cb2f0be10e1bc3662046db7cb6d5de314abe33a01ff0944072256da35186a74026d56db14a2c80ffc4dc7a3b2b5a93885c2622b5201471bdfaa9efa8b9adce9654553a07278f7e37156c73b03394ca8288d8983403001bc76949736399cf6d26d21a468ad6c8ee3e563e0ccef01d615c45a91bc9a612e0001f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
|
||||
"orchardTree": "013f012dabdfb54100613c405576e16bbc19a802910fde444e98827f568143ed0c001f010853c8f2d2423d321b9630781d679b884cde7b3160108ddc1233f955e1bd9628000001b8ef98edd02dbc2b74a63778968d66cb1361cc01f7b4434b141ec35c12a2741901c5a9a64d994fbe4c8d044c80f8f551595ff8a33b2bf98857058ab080aa2fbf31000000000116e9a922d1b143d61c100ff15dc2ffdf92e70721aed5a6c37270d7136fb38a1b013dedd8fa148f4930ad5fc0ac019753a21886453af5c94e8f8817a4e13b7ef810000001f69a0bb2a07942810983d469f50f2285c1ba6f0b3355378356cc62a17860cf170175dca061b72ede5cfcb867aa38ce3a37fc84b7586cbeac75d077c5e0eb33b3250001ccf521db485f5532163391b715519775dc86bbb7b938687551a8547a7c81b425010fb2cb03705826ca086f605974429cfb0dc60df50a0d15d06c2cfef80017902f01c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "test",
|
||||
"height": "2000000",
|
||||
"hash": "000bb3c6d575a155a56b1aab0e7f14a1141d25c1f36aa6f15c1cb1ef1d7d498c",
|
||||
"time": 1660621966,
|
||||
"saplingTree": "0133914238639e4f22ddc8ca2cab7e2d7ec676b06f12a0bf42bc757909cec4f47200100103217f60b7d9bdc625ecd5d6170e3ab57a81eb29f7beacecd07c4f3f0d61ef2801066673d6ab6b62a91f6e04a49fc0e74083cfcccdc851db681e01db333ee1cb470000018da55ded8e55006c2a6c41474da220a9a179a85a99a48df0a31a74291317720e00012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
|
||||
"orchardTree": "01a682706317caa5aec999385ac580445ff4eff6347e4a3c844ac18fcb5fe9bf1c01cca6f37237f27037fa7f8fe5ec8d2cc251b791cfb9cdd08cd1215229fa9435221f0001590d3e7e3f4cd572274f79f4a95b41fa72ed9b42a7c6dbcaec9637eaf368ac0e0000018843337920418307fa7699d506bb0f47a79aea7f6fe8efc1e25b9dde8966e22f013b5a8ef020d8b30fa8beb8406dd30b2a1944755f5549713e4fe24de78ab72e12000001a46523754a6d3fbc3226d6221dafca357d930e183297a0ba1cfa2db5d0500e1f01b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "test",
|
||||
"height": "2010000",
|
||||
"hash": "0013159a578c874aeecddad8707b8e274a018078fd80fc3b9e2d04065abeb05d",
|
||||
"time": 1661274815,
|
||||
"saplingTree": "012bd51ef4da530bb488d43a0f770109df7186ef164a64f618038bb00b7861840a00100001d3a6dfc2fac7c968ce7f96efdddc9764bc320230d8ce166637f8b73b6453e8670000000197e98f0a36d7a5ccac4b4ec963223a4eaab38188eb2a658ff01da4880839996e012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
|
||||
"orchardTree": "014b0ab9afb38a6c819d64806b3de5397c6469ca0f7e58274f5c779ecb75d94217018646890daed861a8f66a3966e884da1b1c8240bb47327a348fae9169eb8205051f000118681f82320257f5a5cfc5c846fd4e88641d4ed7b87fcd5ec8e65e68e3ab0636000155cd8735904f69800ba500bf8af6fc70bfb2abb3d7ca1c75a1cce183b763f30400016e57cbad21bab804109f19479a88046b5c7f8da0d21e8217ac467801235c52200001a8773a2b01e9efd1011ea7d8d1071ad0a01c6c73e2fb6df802e307df17b4ac0601a46523754a6d3fbc3226d6221dafca357d930e183297a0ba1cfa2db5d0500e1f01b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "test",
|
||||
"height": "2020000",
|
||||
"hash": "000777cc07627b8a0a0086f3e7cab5121372c6caad19d387a58dbd9576827a9a",
|
||||
"time": 1661953684,
|
||||
"saplingTree": "010620a4db0c09f044c0edfddb2e13ee69ae7385cf2ea483b9924b6af8d4d51771001001edfd2f5620d6913ff9b1e56d7e60ead3f140e9756c1351dd29f6acb98643c96901d3a6dfc2fac7c968ce7f96efdddc9764bc320230d8ce166637f8b73b6453e8670000000197e98f0a36d7a5ccac4b4ec963223a4eaab38188eb2a658ff01da4880839996e012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
|
||||
"orchardTree": "01f33757a6d93a793fe0c9316ea63db47d01dd6a4a7861a046bca399b6dc4cae1101baae6b740a3589fbecc9b7bc5648d4e7bc1aaaec9dacb1f1fad48dd0d60d4b0d1f0001dc89cf3d764acdccad19c5f0fd2b4a133e132221e20c8bc42529127ac8486b3501e86d5e712791cfdc0abc80d6cf13cf2ac8b96ce89dd4d24c36a0f87dd8192b330155cd8735904f69800ba500bf8af6fc70bfb2abb3d7ca1c75a1cce183b763f30400016e57cbad21bab804109f19479a88046b5c7f8da0d21e8217ac467801235c52200001a8773a2b01e9efd1011ea7d8d1071ad0a01c6c73e2fb6df802e307df17b4ac0601a46523754a6d3fbc3226d6221dafca357d930e183297a0ba1cfa2db5d0500e1f01b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "test",
|
||||
"height": "2030000",
|
||||
"hash": "0020f1af6bad0db43f2c42baae19ecf784142cc06973c0054f82259096acc700",
|
||||
"time": 1662635959,
|
||||
"saplingTree": "01e9be53684e763faf4f718a731191ea92872c0ebe725ab22a5e4c9564d4854e6c01bcbedf283945d4a3ae20c57c9786f98be2b51c83fc14084bf81bdab4905c95291001be49bd71757b254fe215647852f1ebb4e9c6539470d1f883b260ae31f287c139011dac0abc733ee5359d9ebaf2fd1e27d97ce2dd318461e9458b21fa7460dccb4101f763bd441cf06dceca58869da07c620bf7b09cb89edf2eff0887ae3e2280d11300000197e98f0a36d7a5ccac4b4ec963223a4eaab38188eb2a658ff01da4880839996e012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
|
||||
"orchardTree": "01d1cfe443088811d9043242dd694da0274ab90ec4775b8dd9b1d2177d8a1bd72b001f000001fd6d2d94e0aeb520a55825da8640517d4a8a4643ad93174d860d3ed25061580d012636bb4ba0c2e449cd2e3cb5f3e6a372d51f84879ade82345700afbcbad65128000001b51a157d0d095cc0ffbb19b8d0afd168f167c36601da2ba7c6dbd301bc0b4e2a01a8773a2b01e9efd1011ea7d8d1071ad0a01c6c73e2fb6df802e307df17b4ac0601a46523754a6d3fbc3226d6221dafca357d930e183297a0ba1cfa2db5d0500e1f01b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"network": "test",
|
||||
"height": "2040000",
|
||||
"hash": "000eb52c4e328d62f1534aa382e8694e76a5a2390322f83e33e503b5dc7ae06f",
|
||||
"time": 1663304586,
|
||||
"saplingTree": "0156434fee1f941dbe0b18b17f3dc9dc758c012090a4abaff688497eb29b8c856800100116548b6eab46750ad5b63ecf449bcd4a17297d2188181580c64dcdbba3a8af0c000001339480cf39fd7dc1a01cacc143be0b8298ec3c94d6b191810c22e1aa1b2b0518000197e98f0a36d7a5ccac4b4ec963223a4eaab38188eb2a658ff01da4880839996e012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
|
||||
"orchardTree": "01e4caaed868ac7bf736481a945a4f800587e2dc7e36004f4d65a9bcc151b9c42801cb2fab44427363a0438ae221de1e6d07ca76b67b00debb77a81f68bc67cdff1e1f0001bdabf0b09af83b73aafa98e9713ca35c48f88d034f98cf33523f567a82248e320000015a497aaf21034cbbcda9cd0805f0a550c0de19229618be23b0bc64e285265f030001bccbf993ceea1d160380db1b90b634b1a8ee1e75b6be3b4c2fa92ade249f2e39000000012e8eedc0e41ccd50e4a4fa428336f7bae0e1ddc5e80786ebb3f5ef065cc05b03016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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."
|
||||
)
|
||||
|
|
|
@ -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'.
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue