Merge tag 'v1.9.0-beta03' into merge-1.9.0-beta03

This commit is contained in:
Jack Grigg 2022-08-22 21:23:43 +01:00
commit 609cc861e9
64 changed files with 2078 additions and 876 deletions

View File

@ -31,13 +31,13 @@ runs:
echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties
- name: Gradle Wrapper Cache
id: gradle-wrapper-cache
uses: actions/cache@0865c47f36e68161719c5b124609996bb5c40129
uses: actions/cache@a7c34adf76222e77931dedbf4a45b2e4648ced19
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@0865c47f36e68161719c5b124609996bb5c40129
uses: actions/cache@a7c34adf76222e77931dedbf4a45b2e4648ced19
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@0865c47f36e68161719c5b124609996bb5c40129
uses: actions/cache@a7c34adf76222e77931dedbf4a45b2e4648ced19
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@0865c47f36e68161719c5b124609996bb5c40129
uses: actions/cache@a7c34adf76222e77931dedbf4a45b2e4648ced19
with:
path: |
sdk-lib/target

View File

@ -1,9 +1,7 @@
# Expected secrets
# MAVEN_CENTRAL_USERNAME - Username for Maven Central.
# MAVEN_CENTRAL_PASSWORD - Password for Maven Central.
# MAVEN_SIGNING_KEYRING_FILE_BASE64 - Base64 encoded GPG keyring file.
# MAVEN_SIGNING_KEY_ID - ID for the key in the GPG keyring file.
# MAVEN_SIGNING_PASSWORD - Password for the key in the GPG keyring file.
# MAVEN_SIGNING_KEY_ASCII - GPG key without a password which has ASCII-armored and then BASE64-encoded.
name: Deploy Release
@ -27,9 +25,26 @@ jobs:
timeout-minutes: 1
uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b
check_secrets:
environment: deployment
permissions:
contents: read
runs-on: ubuntu-latest
outputs:
has-secrets: ${{ steps.check_secrets.outputs.defined }}
steps:
- id: check_secrets
env:
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
MAVEN_SIGNING_KEY: ${{ secrets.MAVEN_SIGNING_KEY_ASCII }}
if: "${{ env.MAVEN_CENTRAL_USERNAME != '' && env.MAVEN_CENTRAL_PASSWORD != '' && env.MAVEN_SIGNING_KEY != '' }}"
run: echo "::set-output name=defined::true"
deploy_release:
environment: deployment
needs: validate_gradle_wrapper
needs: [validate_gradle_wrapper, check_secrets]
if: needs.check_secrets.outputs.has-secrets == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
@ -41,36 +56,24 @@ jobs:
id: setup
timeout-minutes: 30
uses: ./.github/actions/setup
- name: Export Maven Signing Key
env:
MAVEN_SIGNING_KEYRING_FILE_BASE64: ${{ secrets.MAVEN_SIGNING_KEYRING_FILE_BASE64 }}
GPG_KEY_PATH: ${{ format('{0}/keyring.gpg', env.home) }}
shell: bash
run: |
echo ${MAVEN_SIGNING_KEYRING_FILE_BASE64} | base64 --decode > ${GPG_KEY_PATH}
# While not strictly necessary, this sanity checks the build before attempting to upload.
# This adds minimal additional build time, since most of the work is cached and re-used
# in the next step.
- name: Deploy to Maven Local
timeout-minutes: 25
env:
ORG_GRADLE_PROJECT_IS_SNAPSHOT: true
ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED: false
ORG_GRADLE_PROJECT_IS_SNAPSHOT: false
ORG_GRADLE_PROJECT_ZCASH_ASCII_GPG_KEY: ${{ secrets.MAVEN_SIGNING_KEY_ASCII }}
run: |
./gradlew publishToMavenLocal --no-parallel
./gradlew publishReleasePublicationToMavenLocalRepository --no-parallel
# Note that GitHub Actions appears to have issues with environment variables that contain periods,
# so the GPG variables are done as command line arguments instead.
- name: Deploy to Maven Central
timeout-minutes: 8
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_IS_SNAPSHOT: false
ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED: true
GPG_KEY_PATH: ${{ format('{0}/keyring.gpg', env.home) }}
GPG_KEY_ID: ${{ secrets.MAVEN_SIGNING_KEY_ID }}
GPG_PASSWORD: ${{ secrets.MAVEN_SIGNING_PASSWORD }}
ORG_GRADLE_PROJECT_ZCASH_MAVEN_PUBLISH_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_ZCASH_MAVEN_PUBLISH_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_ZCASH_ASCII_GPG_KEY: ${{ secrets.MAVEN_SIGNING_KEY_ASCII }}
run: |
./gradlew publish -Psigning.secretKeyRingFile=$GPG_KEY_PATH -Psigning.keyId=$GPG_KEY_ID -Psigning.password=$GPG_PASSWORD --no-parallel
./gradlew closeAndReleaseRepository --no-parallel
./gradlew publishReleasePublicationToMavenCentralRepository --no-parallel
- name: Collect Artifacts
timeout-minutes: 1
if: ${{ always() }}

View File

@ -35,9 +35,24 @@ jobs:
timeout-minutes: 1
uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b
check_secrets:
environment: deployment
permissions:
contents: read
runs-on: ubuntu-latest
outputs:
has-secrets: ${{ steps.check_secrets.outputs.defined }}
steps:
- id: check_secrets
env:
MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
if: "${{ env.MAVEN_CENTRAL_USERNAME != '' && env.MAVEN_CENTRAL_PASSWORD != '' }}"
run: echo "::set-output name=defined::true"
deploy_snapshot:
environment: deployment
needs: validate_gradle_wrapper
needs: [validate_gradle_wrapper, check_secrets]
runs-on: ubuntu-latest
permissions:
contents: read
@ -56,18 +71,16 @@ jobs:
timeout-minutes: 25
env:
ORG_GRADLE_PROJECT_IS_SNAPSHOT: true
ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED: false
run: |
./gradlew publishToMavenLocal --no-parallel
./gradlew publishReleasePublicationToMavenLocalRepository --no-parallel
- name: Deploy to Maven Central
timeout-minutes: 8
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_ZCASH_MAVEN_PUBLISH_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_ZCASH_MAVEN_PUBLISH_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_IS_SNAPSHOT: true
ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED: false
run: |
./gradlew publish --no-parallel
./gradlew publishReleasePublicationToMavenCentralRepository --no-parallel
- name: Collect Artifacts
timeout-minutes: 1
if: ${{ always() }}

View File

@ -1,56 +0,0 @@
# Although our CI builds should automatically close and release repositories after publishing, sometimes
# this process can fail. This GitHub Action allows a team member to manually unwedge this stuck deployment.
# Expected secrets
# MAVEN_CENTRAL_USERNAME - Username for Maven Central
# MAVEN_CENTRAL_PASSWORD - Password for Maven Central
name: Close and release repository
on:
workflow_dispatch:
inputs:
mavenCentralRepository:
description: 'Repository name to close'
required: true
concurrency: deploy_release
jobs:
validate_gradle_wrapper:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
# Gradle Wrapper validation can be flaky
# https://github.com/gradle/wrapper-validation-action/issues/40
- name: Gradle Wrapper Validation
timeout-minutes: 1
uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b
unwedge:
environment: deployment
needs: validate_gradle_wrapper
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
timeout-minutes: 1
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
- name: Setup
id: setup
timeout-minutes: 30
uses: ./.github/actions/setup
- name: Close and release repository
timeout-minutes: 8
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_IS_SNAPSHOT: true
ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED: false
run: |
./gradlew closeAndReleaseRepository --repository="${{ github.event.inputs.mavenCentralRepository }}" --no-parallel

View File

@ -47,12 +47,23 @@ Change Log
- `DerivationTool.deriveUnifiedViewingKeys`
- `DerivationTool.validateUnifiedViewingKey`
1.9.0-beta01
Version 1.9.0-beta03
------------------------------------
- No changes; this release is a test of a new deployment process
Version 1.9.0-beta02
------------------------------------
- The SDK now stores database files in `no_backup/co.electricoin.zcash` folder instead of the `database` folder. **No action required from client app**.
Version 1.9.0-beta01
------------------------------------
- Split `ZcashNetwork` into `ZcashNetwork` and `LightWalletEndpoint` to decouple network and server configuration
- Gradle 7.5.1
- Updated checkpoints
Version 1.8.0-beta01
------------------------------------
- Enabled automated unit tests run on the CI server
- Added `BlockHeight` typesafe object to represent block heights
- Significantly reduced memory usage, fixing potential OutOfMemoryError during block download
- Kotlin 1.7.10

218
README.md
View File

@ -16,212 +16,42 @@ This is a beta build and is currently under active development. Please be advise
---
# Zcash Android SDK
This lightweight SDK connects Android to Zcash, allowing third-party Android apps to send and receive shielded transactions easily, securely and privately.
This lightweight SDK connects Android to Zcash. It welds together Rust and Kotlin in a minimal way, allowing third-party Android apps to send and receive shielded transactions easily, securely and privately.
Different sections of this repository documentation are oriented to different roles, specifically Consumers (you want to use the SDK) and Maintainers (you want to modify the SDK).
## Contents
Note: This SDK is designed to work with [lightwalletd](https://github.com/zcash-hackworks/lightwalletd). As either a consumer of the SDK or developer, you'll need a lightwalletd instance to connect to. These servers are maintained by the Zcash community.
- [Requirements](#requirements)
- [Structure](#structure)
- [Overview](#overview)
- [Components](#components)
- [Quickstart](#quickstart)
- [Examples](#examples)
- [Compiling Sources](#compiling-sources)
- [Versioning](#versioning)
- [Examples](#examples)
Note: Because we have not deployed a non-beta release of the SDK yet, version numbers currently follow a variation of [semantic versioning](https://semver.org/). Generally a non-breaking change will increment the beta number while a breaking change will increment the minor number. 1.0.0-beta01 -> 1.0.0-beta02 is non-breaking, while 1.0.0-beta01 -> 1.1.0-beta01 is breaking. This is subject to change.
## Requirements
# Zcash Networks
"mainnet" (main network) and "testnet" (test network) are terms used in the blockchain ecosystem to describe different blockchain networks. Mainnet is responsible for executing actual transactions within the network and storing them on the blockchain. In contrast, the testnet provides an alternative environment that mimics the mainnet's functionality to allow developers to build and test projects without needing to facilitate live transactions or the use of cryptocurrencies, for example.
This SDK is designed to work with [lightwalletd](https://github.com/zcash-hackworks/lightwalletd)
The Zcash testnet is an alternative blockchain that attempts to mimic the mainnet (main Zcash network) for testing purposes. Testnet coins are distinct from actual ZEC and do not have value. Developers and users can experiment with the testnet without having to use valuable currency. The testnet is also used to test network upgrades and their activation before committing to the upgrade on the main Zcash network. For more information on how to add testnet funds visit [Testnet Guide](https://zcash.readthedocs.io/en/latest/rtd_pages/testnet_guide.html) or go right to the [Testnet Faucet](https://faucet.zecpages.com/).
## Structure
This SDK supports both mainnet and testnet. Further details on switching networks are covered in the remaining documentation.
From an app developer's perspective, this SDK will encapsulate the most complex aspects of using Zcash, freeing the developer to focus on UI and UX, rather than scanning blockchains and building commitment trees! Internally, the SDK is structured as follows:
![SDK Diagram](assets/sdk_diagram_final.png?raw=true "SDK Diagram")
Thankfully, the only thing an app developer has to be concerned with is the following:
![SDK Diagram Developer Perspective](assets/sdk_dev_pov_final.png?raw=true "SDK Diagram Dev PoV")
[Back to contents](#contents)
## Overview
At a high level, this SDK simply helps native Android codebases connect to Zcash's Rust crypto libraries without needing to know Rust or be a Cryptographer. Think of it as welding. The SDK takes separate things and tightly bonds them together such that each can remain as idiomatic as possible. Its goal is to make it easy for an app to incorporate shielded transactions while remaining a good citizen on mobile devices.
Given all the moving parts, making things easy requires coordination. The [Synchronizer](docs/-synchronizer/README.md) provides that layer of abstraction so that the primary steps to make use of this SDK are simply:
1. Start the [Synchronizer](docs/-synchronizer/README.md)
2. Subscribe to wallet data
The [Synchronizer](docs/-synchronizer/README.md) takes care of
- Connecting to the light wallet server
- Downloading the latest compact blocks in a privacy-sensitive way
- Scanning and trial decrypting those blocks for shielded transactions related to the wallet
- Processing those related transactions into useful data for the UI
- Sending payments to a full node through [lightwalletd](https://github.com/zcash/lightwalletd)
- Monitoring sent payments for status updates
To accomplish this, these responsibilities of the SDK are divided into separate components. Each component is coordinated by the [Synchronizer](docs/-synchronizer/README.md), which is the thread that ties it all together.
#### Components
| Component | Summary |
| :----------------------------- | :---------------------------------------------------------------------------------------- |
| **LightWalletService** | Service used for requesting compact blocks |
| **CompactBlockStore** | Stores compact blocks that have been downloaded from the `LightWalletService` |
| **CompactBlockProcessor** | Validates and scans the compact blocks in the `CompactBlockStore` for transaction details |
| **OutboundTransactionManager** | Creates, Submits and manages transactions for spending funds |
| **Initializer** | Responsible for all setup that must happen before synchronization can begin. Loads the rust library and helps initialize databases. |
| **DerivationTool**, **BirthdayTool** | Utilities for deriving keys, addresses and loading wallet checkpoints, called "birthdays." |
| **RustBackend** | Wraps and simplifies the rust library and exposes its functionality to the Kotlin SDK |
[Back to contents](#contents)
## Quickstart
Add flavors for testnet v mainnet. Since `productFlavors` cannot start with the word 'test' we recommend:
build.gradle:
```groovy
flavorDimensions 'network'
productFlavors {
// would rather name them "testnet" and "mainnet" but product flavor names cannot start with the word "test"
zcashtestnet {
dimension 'network'
matchingFallbacks = ['zcashtestnet', 'debug']
}
zcashmainnet {
dimension 'network'
matchingFallbacks = ['zcashmainnet', 'release']
}
}
```
build.gradle.kts
```kotlin
flavorDimensions.add("network")
productFlavors {
// would rather name them "testnet" and "mainnet" but product flavor names cannot start with the word "test"
create("zcashtestnet") {
dimension = "network"
matchingFallbacks.addAll(listOf("zcashtestnet", "debug"))
}
create("zcashmainnet") {
dimension = "network"
matchingFallbacks.addAll(listOf("zcashmainnet", "release"))
}
}
```
Add the SDK dependency:
```kotlin
implementation("cash.z.ecc.android:zcash-android-sdk:1.4.0-beta01")
```
Start the [Synchronizer](docs/-synchronizer/README.md)
```kotlin
synchronizer.start(this)
```
Get the wallet's address
```kotlin
synchronizer.getAddress()
// or alternatively
DerivationTool.deriveShieldedAddress(viewingKey)
```
Send funds to another address
```kotlin
synchronizer.sendToAddress(spendingKey, zatoshi, address, memo)
```
[Back to contents](#contents)
## Examples
Full working examples can be found in the [demo app](demo-app), covering all major functionality of the SDK. Each demo strives to be self-contained so that a developer can understand everything required for it to work. Testnet builds of the demo app will soon be available to [download as github releases](https://github.com/zcash/zcash-android-wallet-sdk/releases).
### Demos
Menu Item|Related Code|Description
:-----|:-----|:-----
Get Private Key|[GetPrivateKeyFragment.kt](demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt)|Given a seed, display its viewing key and spending key
Get Address|[GetAddressFragment.kt](demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt)|Given a seed, display its z-addr
Get Balance|[GetBalanceFragment.kt](demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt)|Display the balance
Get Latest Height|[GetLatestHeightFragment.kt](demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getlatestheight/GetLatestHeightFragment.kt)|Given a lightwalletd server, retrieve the latest block height
Get Block|[GetBlockFragment.kt](demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getblock/GetBlockFragment.kt)|Given a lightwalletd server, retrieve a compact block
Get Block Range|[GetBlockRangeFragment.kt](demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getblockrange/GetBlockRangeFragment.kt)|Given a lightwalletd server, retrieve a range of compact blocks
List Transactions|[ListTransactionsFragment.kt](demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt)|Given a seed, list all related shielded transactions
Send|[SendFragment.kt](demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt)|Send and monitor a transaction, the most complex demo
[Back to contents](#contents)
## Compiling Sources
:warning: Compilation is not required unless you plan to submit a patch or fork the code. Instead, it is recommended to simply add the SDK dependencies via Gradle.
In the event that you *do* want to compile the SDK from sources, please see [Setup.md](docs/Setup.md).
[Back to contents](#contents)
## Versioning
This project follows [semantic versioning](https://semver.org/) with pre-release versions. An example of a valid version number is `1.0.4-alpha11` denoting the `11th` iteration of the `alpha` pre-release of version `1.0.4`. Stable releases, such as `1.0.4` will not contain any pre-release identifiers. Pre-releases include the following, in order of stability: `alpha`, `beta`, `rc`. Version codes offer a numeric representation of the build name that always increases. The first six significant digits represent the major, minor and patch number (two digits each) and the last 3 significant digits represent the pre-release identifier. The first digit of the identifier signals the build type. Lastly, each new build has a higher version code than all previous builds. The following table breaks this down:
#### Build Types
| Type | Purpose | Stability | Audience | Identifier | Example Version |
| :---- | :--------- | :---------- | :-------- | :------- | :--- |
| **alpha** | **Sandbox.** For developers to verify behavior and try features. Things seen here might never go to production. Most bugs here can be ignored.| Unstable: Expect bugs | Internal developers | 0XX | 1.2.3-alpha04 (10203004) |
| **beta** | **Hand-off.** For developers to present finished features. Bugs found here should be reported and immediately addressed, if they relate to recent changes. | Unstable: Report bugs | Internal stakeholders | 2XX | 1.2.3-beta04 (10203204) |
| **release candidate** | **Hardening.** Final testing for an app release that we believe is ready to go live. The focus here is regression testing to ensure that new changes have not introduced instability in areas that were previously working. | Stable: Hunt for bugs | External testers | 4XX | 1.2.3-rc04 (10203404) |
| **production** | **Delivery.** Deliver new features to end-users. Any bugs found here need to be prioritized. Some will require immediate attention but most can be worked into a future release. | Stable: Prioritize bugs | Public | 8XX | 1.2.3 (10203800) |
[Back to contents](#contents)
## Examples
# Consumers
If you're a developer consuming this SDK in your own app, see [Consumers.md](docs/Consumers.md) for a discussion of setting up your app to consume the SDK and leverage the public APIs.
A primitive example to exercise the SDK exists in this repo, under [Demo App](demo-app).
There's also a more comprehensive [Sample Wallet](https://github.com/zcash/zcash-android-wallet).
There are also more comprehensive sample walletes:
* [ECC Sample Wallet](https://github.com/zcash/zcash-android-wallet) — A basic sample application.
* [Secant Sample Wallet](https://github.com/zcash/secant-android-wallet) — A more modern codebase written in Compose. This repository is a work-in-progress and is not fully functional yet as of August 2022, although it will be our primary sample application in the future.
[Back to contents](#contents)
# Maintainers and Contributors
If you're building the SDK from source or modifying the SDK:
* [Setup.md](docs/Setup.md) to configure building from source
* [Architecture.md](docs/Architecture.md) to understand the high level architecture of the code
* [CI.md](docs/CI.md) to understand the Continuous Integration build scripts
* [PUBLISHING.md](docs/PUBLISHING.md) to understand our deployment process
## Checkpoints
To improve the speed of syncing with the Zcash network, the SDK contains a series of embedded checkpoints. These should be updated periodically, as new transactions are added to the network. Checkpoints are stored under the [assets](sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint) directory as JSON files. Checkpoints for both mainnet and testnet are bundled into the SDK.
Note that we aim for the main branch of this repository to be stable and releasable. We continuously deploy snapshot builds after a merge to the main branch, then manually deploy release builds. Our continuous deployment of snapshots implies two things:
* A pull request containing API changes should also bump the version
* Each pull request should be stable and ready to be consumed, to the best of your knowledge. Gating unstable functionality behind a flag is perfectly acceptable
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.
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.
* Sapling activation
* Mainnet: 419200
* Testnet: 280000
* Orchard activation
* Mainnet: 1687104
* Testnet: 1842420
## Publishing
Publishing instructions for maintainers of this repository can be found in [PUBLISHING.md](PUBLISHING.md)
[Back to contents](#contents)
# Known Issues
1. Intel-based machines may have trouble building in Android Studio. The workaround is to add the following line to `~/.gradle/gradle.properties` `ZCASH_IS_DEPENDENCY_LOCKING_ENABLED=false`
## Known Issues
1. Intel-based machines may have trouble building in Android Studio. The workaround is to add the following line to `~/.gradle/gradle.properties`: `ZCASH_IS_DEPENDENCY_LOCKING_ENABLED=false`
1. During builds, a warning will be printed that says "Unable to detect AGP versions for included builds. All projects in the build should use the same AGP version." This can be safely ignored. The version under build-conventions is the same as the version used elsewhere in the application.
1. Android Studio will warn about the Gradle checksum. This is a [known issue](https://github.com/gradle/gradle/issues/9361) and can be safely ignored.

View File

@ -1,45 +1,45 @@
# 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.1=runtimeClasspath
androidx.databinding:databinding-compiler-common:7.2.1=runtimeClasspath
com.android.databinding:baseLibrary:7.2.1=runtimeClasspath
com.android.tools.analytics-library:crash:30.2.1=runtimeClasspath
com.android.tools.analytics-library:protos:30.2.1=runtimeClasspath
com.android.tools.analytics-library:shared:30.2.1=runtimeClasspath
com.android.tools.analytics-library:tracker:30.2.1=runtimeClasspath
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.1-7984345=runtimeClasspath
com.android.tools.build:aaptcompiler:7.2.1=runtimeClasspath
com.android.tools.build:apksig:7.2.1=compileClasspath,runtimeClasspath
com.android.tools.build:apkzlib:7.2.1=compileClasspath,runtimeClasspath
com.android.tools.build:builder-model:7.2.1=compileClasspath,runtimeClasspath
com.android.tools.build:builder-test-api:7.2.1=runtimeClasspath
com.android.tools.build:builder:7.2.1=compileClasspath,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.1=compileClasspath,runtimeClasspath
com.android.tools.build:gradle:7.2.1=compileClasspath,runtimeClasspath
com.android.tools.build:manifest-merger:30.2.1=compileClasspath,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
com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=runtimeClasspath
com.android.tools.ddms:ddmlib:30.2.1=runtimeClasspath
com.android.tools.layoutlib:layoutlib-api:30.2.1=runtimeClasspath
com.android.tools.lint:lint-model:30.2.1=runtimeClasspath
com.android.tools.lint:lint-typedef-remover:30.2.1=runtimeClasspath
com.android.tools.utp:android-device-provider-ddmlib-proto:30.2.1=runtimeClasspath
com.android.tools.utp:android-device-provider-gradle-proto:30.2.1=runtimeClasspath
com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.2.1=runtimeClasspath
com.android.tools.utp:android-test-plugin-host-coverage-proto:30.2.1=runtimeClasspath
com.android.tools.utp:android-test-plugin-host-retention-proto:30.2.1=runtimeClasspath
com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.2.1=runtimeClasspath
com.android.tools:annotations:30.2.1=runtimeClasspath
com.android.tools:common:30.2.1=runtimeClasspath
com.android.tools:dvlib:30.2.1=runtimeClasspath
com.android.tools:repository:30.2.1=runtimeClasspath
com.android.tools:sdk-common:30.2.1=runtimeClasspath
com.android.tools:sdklib:30.2.1=runtimeClasspath
com.android:signflinger:7.2.1=runtimeClasspath
com.android:zipflinger:7.2.1=compileClasspath,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

View File

@ -10,9 +10,11 @@ plugins {
android {
defaultConfig {
applicationId = "cash.z.ecc.android.sdk.demoapp"
minSdk = 21 // Different from the SDK min
minSdk = 19
versionCode = 1
versionName = "1.0"
multiDexEnabled = true
vectorDrawables.useSupportLibrary = true
}
buildFeatures {
viewBinding = true
@ -63,6 +65,7 @@ dependencies {
// Android
implementation(libs.androidx.core)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.multidex)
implementation(libs.androidx.navigation.fragment)
implementation(libs.androidx.navigation.ui)
implementation(libs.material)

View File

@ -1,20 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="5" by="lint 4.2.1" client="gradle" variant="all" version="4.2.1">
<issue
id="OldTargetApi"
message="Not targeting the latest versions of Android; compatibility modes apply. Consider testing and updating this version. Consult the android.os.Build.VERSION_CODES javadoc for details."
errorLine1=" targetSdkVersion 29"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="13"
column="9"/>
</issue>
<issues format="6" by="lint 7.2.2" type="baseline" client="gradle" dependencies="false" name="AGP (7.2.2)" variant="all" version="7.2.2">
<issue
id="UnusedAttribute"
message="Attribute `drawableTint` is only used in API level 23 and higher (current min is 21)"
message="Attribute `drawableTint` is only used in API level 23 and higher (current min is 19)"
errorLine1=" android:drawableTint=&quot;@color/colorPrimary&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
@ -23,6 +12,17 @@
column="9"/>
</issue>
<issue
id="UnusedAttribute"
message="Attribute `elevation` is only used in API level 21 and higher (current min is 19)"
errorLine1=" android:elevation=&quot;1dp&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/layout/item_transaction.xml"
line="9"
column="5"/>
</issue>
<issue
id="FragmentTagUsage"
message="Replace the &lt;fragment> tag with FragmentContainerView."
@ -35,102 +35,146 @@
</issue>
<issue
id="GradleDependency"
message="A newer version of cash.z.ecc.android:kotlin-bip39 than 1.0.1 is available: 1.0.2"
errorLine1=" implementation &apos;cash.z.ecc.android:kotlin-bip39:1.0.1&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
id="RedundantLabel"
message="Redundant label can be removed"
errorLine1=" android:label=&quot;@string/app_name&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="61"
column="20"/>
file="src/main/AndroidManifest.xml"
line="16"
column="13"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.core:core-ktx than 1.3.2 is available: 1.6.0"
errorLine1=" implementation &apos;androidx.core:core-ktx:1.3.2&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
id="VectorDrawableCompat"
message="To use VectorDrawableCompat, you need to set `android.defaultConfig.vectorDrawables.useSupportLibrary = true` in `:demo-app/build.gradle`"
errorLine1=" app:srcCompat=&quot;@drawable/ic_floating_action&quot; />"
errorLine2=" ~~~~~~~~~~~~~">
<location
file="build.gradle"
line="64"
column="20"/>
file="src/main/res/layout/app_bar_main.xml"
line="32"
column="9"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.constraintlayout:constraintlayout than 2.0.4 is available: 2.1.0"
errorLine1=" implementation &apos;androidx.constraintlayout:constraintlayout:2.0.4&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
id="VectorDrawableCompat"
message="To use VectorDrawableCompat, you need to set `android.defaultConfig.vectorDrawables.useSupportLibrary = true` in `:demo-app/build.gradle`"
errorLine1=" app:srcCompat=&quot;@drawable/ic_receive&quot;"
errorLine2=" ~~~~~~~~~~~~~">
<location
file="build.gradle"
line="65"
column="20"/>
file="src/main/res/layout/item_transaction.xml"
line="21"
column="9"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.navigation:navigation-fragment-ktx than 2.3.1 is available: 2.3.5"
errorLine1=" implementation &apos;androidx.navigation:navigation-fragment-ktx:2.3.1&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
id="VectorRaster"
message="Resource references will not work correctly in images generated for this vector icon for API &lt; 21; check generated icon to make sure it looks acceptable"
errorLine1=" android:tint=&quot;?attr/colorControlNormal&quot;>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="66"
column="20"/>
file="src/main/res/drawable/ic_baseline_check_24.xml"
line="6"
column="19"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.navigation:navigation-ui-ktx than 2.3.1 is available: 2.3.5"
errorLine1=" implementation &apos;androidx.navigation:navigation-ui-ktx:2.3.1&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
id="VectorRaster"
message="Resource references will not work correctly in images generated for this vector icon for API &lt; 21; check generated icon to make sure it looks acceptable"
errorLine1=" android:fillColor=&quot;@android:color/white&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="67"
column="20"/>
file="src/main/res/drawable/ic_baseline_check_24.xml"
line="8"
column="26"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of com.google.android.material:material than 1.3.0-alpha03 is available: 1.5.0-alpha02"
errorLine1=" implementation &quot;com.google.android.material:material:1.3.0-alpha03&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
id="VectorRaster"
message="Resource references will not work correctly in images generated for this vector icon for API &lt; 21; check generated icon to make sure it looks acceptable"
errorLine1=" android:tint=&quot;?attr/colorControlNormal&quot;>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="68"
column="20"/>
file="src/main/res/drawable/ic_baseline_close_24.xml"
line="6"
column="19"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of junit:junit than 4.13 is available: 4.13.2"
errorLine1=" testImplementation &apos;junit:junit:4.13&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~">
id="VectorRaster"
message="Resource references will not work correctly in images generated for this vector icon for API &lt; 21; check generated icon to make sure it looks acceptable"
errorLine1=" android:fillColor=&quot;@android:color/white&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="69"
column="24"/>
file="src/main/res/drawable/ic_baseline_close_24.xml"
line="8"
column="26"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.test.ext:junit than 1.1.2 is available: 1.1.3"
errorLine1=" androidTestImplementation &apos;androidx.test.ext:junit:1.1.2&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
id="VectorRaster"
message="Resource references will not work correctly in images generated for this vector icon for API &lt; 21; check generated icon to make sure it looks acceptable"
errorLine1=" android:tint=&quot;?attr/colorControlNormal&quot;>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="70"
column="31"/>
file="src/main/res/drawable/ic_baseline_edit_24.xml"
line="6"
column="19"/>
</issue>
<issue
id="GradleDependency"
message="A newer version of androidx.test.espresso:espresso-core than 3.3.0 is available: 3.4.0"
errorLine1=" androidTestImplementation &apos;androidx.test.espresso:espresso-core:3.3.0&apos;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
id="VectorRaster"
message="Resource references will not work correctly in images generated for this vector icon for API &lt; 21; check generated icon to make sure it looks acceptable"
errorLine1=" android:fillColor=&quot;@android:color/white&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle"
line="71"
column="31"/>
file="src/main/res/drawable/ic_baseline_edit_24.xml"
line="8"
column="26"/>
</issue>
<issue
id="VectorRaster"
message="Resource references will not work correctly in images generated for this vector icon for API &lt; 21; check generated icon to make sure it looks acceptable"
errorLine1=" android:tint=&quot;?attr/colorControlNormal&quot;>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/drawable/ic_baseline_move_to_inbox_24.xml"
line="6"
column="19"/>
</issue>
<issue
id="VectorRaster"
message="Resource references will not work correctly in images generated for this vector icon for API &lt; 21; check generated icon to make sure it looks acceptable"
errorLine1=" android:fillColor=&quot;@android:color/white&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/drawable/ic_baseline_move_to_inbox_24.xml"
line="8"
column="26"/>
</issue>
<issue
id="VectorRaster"
message="Resource references will not work correctly in images generated for this vector icon for API &lt; 21; check generated icon to make sure it looks acceptable"
errorLine1=" android:tint=&quot;?attr/colorControlNormal&quot;>"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/drawable/ic_menu_balance.xml"
line="6"
column="19"/>
</issue>
<issue
id="VectorRaster"
message="Resource references will not work correctly in images generated for this vector icon for API &lt; 21; check generated icon to make sure it looks acceptable"
errorLine1=" android:fillColor=&quot;@android:color/black&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/drawable/ic_menu_balance.xml"
line="8"
column="26"/>
</issue>
<issue
@ -156,10 +200,14 @@
</issue>
<issue
id="ObsoleteSdkInt"
message="This folder configuration (`v21`) is unnecessary; `minSdkVersion` is 21. Merge all the resources in this folder into `values`.">
id="DataExtractionRules"
message="The attribute `android:allowBackup` is deprecated from Android 12 and higher and may be removed in future versions. Consider adding the attribute `android:dataExtractionRules` specifying an `@xml` resource which configures cloud backups and device transfers on Android 12 and higher."
errorLine1=" android:allowBackup=&quot;false&quot;"
errorLine2=" ~~~~~">
<location
file="src/main/res/values-v21"/>
file="src/main/AndroidManifest.xml"
line="8"
column="30"/>
</issue>
<issue
@ -363,66 +411,66 @@
<issue
id="SetTextI18n"
message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
errorLine1=" binding.textInfo.text = &quot;z-addr:\n$zaddress\n\n\nt-addr:\n$taddress&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
errorLine1=" binding.textInfo.text = &quot;z-addr:\n$zaddress\n\n\nt-addr:\n$taddress&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt"
line="41"
column="33"/>
line="48"
column="37"/>
</issue>
<issue
id="SetTextI18n"
message="String literal in `setText` can not be translated. Use Android resources instead."
errorLine1=" binding.textInfo.text = &quot;z-addr:\n$zaddress\n\n\nt-addr:\n$taddress&quot;"
errorLine2=" ~~~~~~~">
errorLine1=" binding.textInfo.text = &quot;z-addr:\n$zaddress\n\n\nt-addr:\n$taddress&quot;"
errorLine2=" ~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt"
line="41"
column="34"/>
line="48"
column="38"/>
</issue>
<issue
id="SetTextI18n"
message="String literal in `setText` can not be translated. Use Android resources instead."
errorLine1=" binding.textInfo.text = &quot;z-addr:\n$zaddress\n\n\nt-addr:\n$taddress&quot;"
errorLine2=" ~~~~~~~">
errorLine1=" binding.textInfo.text = &quot;z-addr:\n$zaddress\n\n\nt-addr:\n$taddress&quot;"
errorLine2=" ~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getaddress/GetAddressFragment.kt"
line="41"
column="58"/>
line="48"
column="62"/>
</issue>
<issue
id="SetTextI18n"
message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
errorLine1=" binding.textBalance.text = &quot;&quot;&quot;"
errorLine2=" ^">
errorLine1=" binding.textBalance.text = &quot;&quot;&quot;"
errorLine2=" ^">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt"
line="72"
column="40"/>
line="82"
column="36"/>
</issue>
<issue
id="SetTextI18n"
message="String literal in `setText` can not be translated. Use Android resources instead."
errorLine1=" Available balance: ${balance.availableZatoshi.convertZatoshiToZecString(12)}"
errorLine1=" Available balance: ${balance.available.convertZatoshiToZecString(12)}"
errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt"
line="73"
line="83"
column="1"/>
</issue>
<issue
id="SetTextI18n"
message="String literal in `setText` can not be translated. Use Android resources instead."
errorLine1=" Total balance: ${balance.totalZatoshi.convertZatoshiToZecString(12)}"
errorLine1=" Total balance: ${balance.total.convertZatoshiToZecString(12)}"
errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt"
line="74"
line="84"
column="1"/>
</issue>
@ -433,7 +481,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt"
line="81"
line="89"
column="35"/>
</issue>
@ -444,7 +492,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt"
line="81"
line="89"
column="36"/>
</issue>
@ -455,7 +503,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt"
line="83"
line="92"
column="41"/>
</issue>
@ -466,7 +514,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt"
line="91"
line="100"
column="39"/>
</issue>
@ -477,7 +525,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt"
line="91"
line="100"
column="40"/>
</issue>
@ -510,7 +558,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getblockrange/GetBlockRangeFragment.kt"
line="92"
line="102"
column="33"/>
</issue>
@ -521,41 +569,41 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getblockrange/GetBlockRangeFragment.kt"
line="92"
line="102"
column="34"/>
</issue>
<issue
id="SetTextI18n"
message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
errorLine1=" binding.textInfo.setText(&quot;Spending Key:\n$spendingKey\n\nViewing Key:\n$viewingKey&quot;)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
errorLine1=" binding.textInfo.setText(&quot;Spending Key:\n$spendingKey\n\nViewing Key:\n$viewingKey&quot;)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt"
line="43"
column="34"/>
line="56"
column="38"/>
</issue>
<issue
id="SetTextI18n"
message="String literal in `setText` can not be translated. Use Android resources instead."
errorLine1=" binding.textInfo.setText(&quot;Spending Key:\n$spendingKey\n\nViewing Key:\n$viewingKey&quot;)"
errorLine2=" ~~~~~~~~~~~~~">
errorLine1=" binding.textInfo.setText(&quot;Spending Key:\n$spendingKey\n\nViewing Key:\n$viewingKey&quot;)"
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt"
line="43"
column="35"/>
line="56"
column="39"/>
</issue>
<issue
id="SetTextI18n"
message="String literal in `setText` can not be translated. Use Android resources instead."
errorLine1=" binding.textInfo.setText(&quot;Spending Key:\n$spendingKey\n\nViewing Key:\n$viewingKey&quot;)"
errorLine2=" ~~~~~~~~~~~~">
errorLine1=" binding.textInfo.setText(&quot;Spending Key:\n$spendingKey\n\nViewing Key:\n$viewingKey&quot;)"
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getprivatekey/GetPrivateKeyFragment.kt"
line="43"
column="66"/>
line="56"
column="70"/>
</issue>
<issue
@ -565,7 +613,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/home/HomeFragment.kt"
line="48"
line="42"
column="47"/>
</issue>
@ -576,7 +624,7 @@
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/home/HomeFragment.kt"
line="48"
line="42"
column="48"/>
</issue>
@ -587,7 +635,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt"
line="82"
line="96"
column="54"/>
</issue>
@ -598,7 +646,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt"
line="82"
line="96"
column="55"/>
</issue>
@ -609,7 +657,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt"
line="86"
line="100"
column="46"/>
</issue>
@ -620,7 +668,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt"
line="86"
line="100"
column="47"/>
</issue>
@ -631,7 +679,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt"
line="91"
line="105"
column="35"/>
</issue>
@ -642,7 +690,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt"
line="91"
line="105"
column="36"/>
</issue>
@ -653,7 +701,7 @@
errorLine2=" ^">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt"
line="109"
line="123"
column="25"/>
</issue>
@ -664,30 +712,30 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt"
line="109"
line="123"
column="26"/>
</issue>
<issue
id="SetTextI18n"
message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
errorLine1=" &quot;or send funds to this address (tap the FAB to copy it):\n\n $address&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
errorLine1=" &quot;or send funds to this address (tap the FAB to copy it):\n\n $address&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt"
line="110"
column="33"/>
line="124"
column="25"/>
</issue>
<issue
id="SetTextI18n"
message="String literal in `setText` can not be translated. Use Android resources instead."
errorLine1=" &quot;or send funds to this address (tap the FAB to copy it):\n\n $address&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
errorLine1=" &quot;or send funds to this address (tap the FAB to copy it):\n\n $address&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listtransactions/ListTransactionsFragment.kt"
line="110"
column="34"/>
line="124"
column="26"/>
</issue>
<issue
@ -697,7 +745,7 @@
errorLine2=" ~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt"
line="96"
line="103"
column="36"/>
</issue>
@ -708,7 +756,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt"
line="145"
line="165"
column="39"/>
</issue>
@ -719,7 +767,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt"
line="218"
line="244"
column="56"/>
</issue>
@ -730,7 +778,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt"
line="218"
line="244"
column="57"/>
</issue>
@ -741,7 +789,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt"
line="222"
line="248"
column="48"/>
</issue>
@ -752,7 +800,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt"
line="222"
line="248"
column="49"/>
</issue>
@ -763,7 +811,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt"
line="227"
line="253"
column="35"/>
</issue>
@ -774,7 +822,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/listutxos/ListUtxosFragment.kt"
line="227"
line="253"
column="36"/>
</issue>
@ -785,7 +833,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="114"
line="133"
column="35"/>
</issue>
@ -796,7 +844,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="114"
line="133"
column="36"/>
</issue>
@ -807,7 +855,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="117"
line="136"
column="41"/>
</issue>
@ -818,7 +866,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="125"
line="144"
column="39"/>
</issue>
@ -829,7 +877,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="125"
line="144"
column="40"/>
</issue>
@ -840,7 +888,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="133"
line="152"
column="56"/>
</issue>
@ -851,7 +899,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="133"
line="152"
column="57"/>
</issue>
@ -862,29 +910,29 @@
errorLine2=" ^">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="139"
line="158"
column="40"/>
</issue>
<issue
id="SetTextI18n"
message="String literal in `setText` can not be translated. Use Android resources instead."
errorLine1=" Available balance: ${balance.availableZatoshi.convertZatoshiToZecString(12)}"
errorLine1=" Available balance: ${balance?.available.convertZatoshiToZecString(12)}"
errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="140"
line="159"
column="1"/>
</issue>
<issue
id="SetTextI18n"
message="String literal in `setText` can not be translated. Use Android resources instead."
errorLine1=" Total balance: ${balance.totalZatoshi.convertZatoshiToZecString(12)}"
errorLine1=" Total balance: ${balance?.total.convertZatoshiToZecString(12)}"
errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="141"
line="160"
column="1"/>
</issue>
@ -895,7 +943,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="173"
line="192"
column="20"/>
</issue>
@ -906,7 +954,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="181"
line="200"
column="29"/>
</issue>
@ -917,7 +965,7 @@
errorLine2=" ~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="185"
line="204"
column="29"/>
</issue>
@ -928,7 +976,7 @@
errorLine2=" ~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="190"
line="209"
column="29"/>
</issue>
@ -939,7 +987,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt"
line="198"
line="217"
column="34"/>
</issue>

View File

@ -1,10 +1,10 @@
package cash.z.ecc.android.sdk.demoapp
import android.app.Application
import androidx.multidex.MultiDexApplication
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
import cash.z.ecc.android.sdk.internal.Twig
class App : Application() {
class App : MultiDexApplication() {
override fun onCreate() {
super.onCreate()

View File

@ -1 +1,35 @@
TODO
# Overview
From an app developer's perspective, this SDK will encapsulate the most complex aspects of using Zcash, freeing the developer to focus on UI and UX, rather than scanning blockchains and building commitment trees! Internally, the SDK is structured as follows:
![SDK Diagram](assets/sdk_diagram_final.png?raw=true "SDK Diagram")
Thankfully, the only thing an app developer has to be concerned with is the following:
![SDK Diagram Developer Perspective](assets/sdk_dev_pov_final.png?raw=true "SDK Diagram Dev PoV")
# Components
| Component | Summary |
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| **LightWalletService** | Service used for requesting compact blocks |
| **CompactBlockStore** | Stores compact blocks that have been downloaded from the `LightWalletService` |
| **CompactBlockProcessor** | Validates and scans the compact blocks in the `CompactBlockStore` for transaction details |
| **OutboundTransactionManager** | Creates, Submits and manages transactions for spending funds |
| **Initializer** | Responsible for all setup that must happen before synchronization can begin. Loads the rust library and helps initialize databases. |
| **DerivationTool**, **BirthdayTool** | Utilities for deriving keys, addresses and loading wallet checkpoints, called "birthdays." |
| **RustBackend** | Wraps and simplifies the rust library and exposes its functionality to the Kotlin SDK |
# Checkpoints
To improve the speed of syncing with the Zcash network, the SDK contains a series of embedded checkpoints. These should be updated periodically, as new transactions are added to the network. Checkpoints are stored under the [sdk-lib's assets](../sdk-lib/src/main/assets/co.electriccoin.zcash/checkpoint) directory as JSON files. Checkpoints for both mainnet and testnet are bundled into the SDK.
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.
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.
* Sapling activation
* Mainnet: 419200
* Testnet: 280000
* Orchard activation
* Mainnet: 1687104
* Testnet: 1842420

110
docs/Consumers.md Normal file
View File

@ -0,0 +1,110 @@
# Configuring your build
Add flavors for testnet and mainnet. Since `productFlavors` cannot start with the word 'test' we recommend:
build.gradle
```groovy
flavorDimensions 'network'
productFlavors {
// would rather name them "testnet" and "mainnet" but product flavor names cannot start with the word "test"
zcashtestnet {
dimension 'network'
matchingFallbacks = ['zcashtestnet', 'debug']
}
zcashmainnet {
dimension 'network'
matchingFallbacks = ['zcashmainnet', 'release']
}
}
```
build.gradle.kts
```kotlin
flavorDimensions.add("network")
productFlavors {
// would rather name them "testnet" and "mainnet" but product flavor names cannot start with the word "test"
create("zcashtestnet") {
dimension = "network"
matchingFallbacks.addAll(listOf("zcashtestnet", "debug"))
}
create("zcashmainnet") {
dimension = "network"
matchingFallbacks.addAll(listOf("zcashmainnet", "release"))
}
}
```
Resources
/src/main/res/values/bools.xml
```
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="zcash_is_testnet">false</bool>
</resources>
```
/src/zcashtestnet/res/values/bools.xml
```
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="zcash_is_testnet">true</bool>
</resources>
```
ZcashNetworkExt.kt
```
/**
* @return Zcash network determined from resources.
*/
fun ZcashNetwork.Companion.fromResources(context: Context) =
if (context.resources.getBoolean(R.bool.zcash_is_testnet)) {
ZcashNetwork.Testnet
} else {
ZcashNetwork.Mainnet
}
```
Add the SDK dependency:
```kotlin
implementation("cash.z.ecc.android:zcash-android-sdk:$LATEST_VERSION")
```
# Using the SDK
Start the [Synchronizer](-synchronizer/README.md)
```kotlin
synchronizer.start(this)
```
Get the wallet's address
```kotlin
synchronizer.getAddress()
// or alternatively
DerivationTool.deriveShieldedAddress(viewingKey)
```
Send funds to another address
```kotlin
synchronizer.sendToAddress(spendingKey, zatoshi, address, memo)
```
The [Synchronizer](-synchronizer/README.md) is the primary entrypoint for the SDK.
1. Start the [Synchronizer](-synchronizer/README.md)
2. Subscribe to wallet data
The [Synchronizer](-synchronizer/README.md) takes care of:
- Connecting to the light wallet server
- Downloading the latest compact blocks in a privacy-sensitive way
- Scanning and trial decrypting those blocks for shielded transactions related to the wallet
- Processing those related transactions into useful data for the UI
- Sending payments to a full node through [lightwalletd](https://github.com/zcash/lightwalletd)
- Monitoring sent payments for status updates

View File

@ -46,10 +46,14 @@ Because Gradle caches dependencies and because multiple snapshots can be deploye
## Releases
Production releases can be consumed using the instructions in the [README.MD](../README.md). Note that production releases can include alpha or beta designations.
Automated production releases still require a manual trigger. To do a production release:
1. Update the CHANGELOG and MIGRATIONS.md for any new changes since the last production release.
Automated production releases require a manual trigger of the GitHub action and a manual step inside the Sonatype dashboard. To do a production release:
1. Update the [CHANGELOG](../CHANGELOG.md) and [MIGRATIONS](../MIGRATIONS.md) for any new changes since the last production release.
1. Run the [release deployment](https://github.com/zcash/zcash-android-wallet-sdk/actions/workflows/deploy-release.yml).
1. Due to #535, release deployments are not fully automated. See the workaround steps in that issue and complete those steps.
1. Log into Maven Central and release the deployment.
1. Check the contents of the staging repository, to verify it looks correct
1. Close the staging repository
1. Wait a few minutes and refresh the page
1. Release the staging repository
1. Confirm deployment succeeded by modifying the [ECC Wallet](https://github.com/zcash/zcash-android-wallet) to consume the new SDK version.
1. Create a new Git tag for the new release in this repository.
1. Create a new pull request bumping the version to the next version (this ensures that the next merge to the main branch creates a snapshot under the next version number).
@ -59,33 +63,25 @@ See [ci.md](ci.md), which describes the continuous integration workflow for depl
## One time only
* Set up environment to [compile the SDK](https://github.com/zcash/zcash-android-wallet-sdk/#compiling-sources)
* Copy the GPG key to a directory with proper permissions (chmod 600). Note: If you'd like to quickly publish locally without subsequently publishing to Maven Central, configure a Gradle property `RELEASE_SIGNING_ENABLED=false`
* Create file `~/.gradle/gradle.properties` per the [instructions in this guide](https://proandroiddev.com/publishing-a-maven-artifact-3-3-step-by-step-instructions-to-mavencentral-publishing-bd661081645d)
* Create file `~/.gradle/gradle.properties`
* add your sonotype credentials with these properties
* `mavenCentralUsername`
* `mavenCentralPassword`
* point it to the GPG key with these properties
* `signing.keyId`
* `signing.password`
* `signing.secretKeyRingFile`
* `ZCASH_MAVEN_PUBLISH_USERNAME`
* `ZCASH_MAVEN_PUBLISH_PASSWORD`
* Point it to a passwordless GPG key that has been ASCII armored, then base64 encoded. Note this is only required for release builds. Snapshots do not require signing.
* `ZCASH_ASCII_GPG_KEY`
## Every time
1. Update the [build number](https://github.com/zcash/zcash-android-wallet-sdk/blob/main/gradle.properties) and the [CHANGELOG](https://github.com/zcash/zcash-android-wallet-sdk/blob/main/CHANGELOG.md). For release builds, suffix the Gradle invocations below with `-PIS_SNAPSHOT=false`.
1. Update the [build number](https://github.com/zcash/zcash-android-wallet-sdk/blob/main/gradle.properties) and the [CHANGELOG](../CHANGELOG.md). For release builds, suffix the Gradle invocations below with `-PIS_SNAPSHOT=false`.
3. Build locally
* This will install the files in your local maven repo at `~/.m2/repository/cash/z/ecc/android/`
```zsh
./gradlew publishToMavenLocal
```
4. Publish via the following command:
```zsh
# This uploads the file to sonotypes staging area
./gradlew publish --no-daemon --no-parallel
```
5. Deploy to maven central:
```zsh
# This closes the staging repository and releases it to the world
./gradlew closeAndReleaseRepository
./gradlew publishReleasePublicationToMavenLocalRepository
```
3. Publish via the following command:
1. Snapshot: `./gradlew publishReleasePublicationToMavenCentralRepository -PIS_SNAPSHOT=true`
2. Release
1. `./gradlew publishReleasePublicationToMavenCentralRepository -PIS_SNAPSHOT=false`
2. Log into the Sonatype portal to complete the process of closing and releasing the repository.
Note:
Our existing artifacts can be found here and here:

View File

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 237 KiB

After

Width:  |  Height:  |  Size: 237 KiB

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

View File

Before

Width:  |  Height:  |  Size: 445 KiB

After

Width:  |  Height:  |  Size: 445 KiB

View File

@ -1,5 +1,5 @@
# Build
There are a variety of aspects to building the SDK and demo app. Although much of this ends up being tested automatically by the CI server, understanding these step can help when troubleshooting build issues. These test cases provide sanity checks that the build is not broken.
There are a variety of aspects to building the SDK and demo app. Although much of this ends up being tested automatically by the CI server, understanding these step can help when troubleshooting build issues. These test cases provide sanity checks that the build is not broken.
# Build
1. Run the assemble Gradle task, e.g. `./gradlew assemble`

View File

@ -0,0 +1,37 @@
# 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.
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
- Ideally two emulators with min and max supported API level
- A working git client
- Cloned Zcash Android SDK repository
# 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:
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.
# 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/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
# 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.

View File

@ -9,19 +9,20 @@ android.useAndroidX=true
android.builder.sdkDownload=true
# Publishing
## Configure these with command line arguments (`-PmavenCentralUsername=`), environment variables (`ORG_GRADLE_PROJECT_mavenCentralUsername`), or global ~/.gradle/gradle.properties
## Don't rename these; although we're consuming them directly for snapshot releases, they have special meaning in the com.vanniktech.maven.publish plugin and are used implicitly by it
mavenCentralUsername=
mavenCentralPassword=
ZCASH_MAVEN_PUBLISH_SNAPSHOT_URL=https://oss.sonatype.org/content/repositories/snapshots/
ZCASH_MAVEN_PUBLISH_RELEASE_URL=https://oss.sonatype.org/service/local/staging/deploy/maven2/
# Configures whether release is an unstable snapshot.
## Configure these with command line arguments (`-PZCASH_MAVEN_PUBLISH_USERNAME=`), environment variables (`ORG_GRADLE_PROJECT_ZCASH_MAVEN_PUBLISH_USERNAME`), or global ~/.gradle/gradle.properties
ZCASH_MAVEN_PUBLISH_USERNAME=
ZCASH_MAVEN_PUBLISH_PASSWORD=
# GPG key is only required if IS_SNAPSHOT is false
# GPG key is ASCII armored without a password, then Base64 encoded to escape the newlines
ZCASH_ASCII_GPG_KEY=
# Configures whether release is an unstable snapshot, therefore published to the snapshot repository.
IS_SNAPSHOT=true
RELEASE_SIGNING_ENABLED=false
# Required by the maven publishing plugin
SONATYPE_HOST=DEFAULT
LIBRARY_VERSION=1.9.0-beta01
LIBRARY_VERSION=1.9.0-beta03
# Kotlin compiler warnings can be considered errors, failing the build.
# Currently set to false, because this project has a lot of warnings to fix first.
@ -63,8 +64,8 @@ ANDROID_COMPILE_SDK_VERSION=33
# When changing this, be sure to update .github/actions/setup/action.yml
ANDROID_NDK_VERSION=22.1.7171670
ANDROID_GRADLE_PLUGIN_VERSION=7.2.1
DETEKT_VERSION=1.20.0
ANDROID_GRADLE_PLUGIN_VERSION=7.2.2
DETEKT_VERSION=1.21.0
DOKKA_VERSION=1.7.10
EMULATOR_WTF_GRADLE_PLUGIN_VERSION=0.0.10
FLANK_VERSION=22.03.0
@ -72,7 +73,6 @@ FULLADLE_VERSION=0.17.4
GRADLE_VERSIONS_PLUGIN_VERSION=0.42.0
KTLINT_VERSION=0.46.1
KSP_VERSION=1.7.10-1.0.6
MAVEN_PUBLISH_GRADLE_PLUGIN=0.20.0
PROTOBUF_GRADLE_PLUGIN_VERSION=0.8.19
RUST_GRADLE_PLUGIN_VERSION=0.9.3
@ -90,10 +90,10 @@ ANDROIDX_TEST_JUNIT_VERSION=1.1.3
ANDROIDX_TEST_ORCHESTRATOR_VERSION=1.1.0-alpha1
ANDROIDX_TEST_VERSION=1.4.0
ANDROIDX_UI_AUTOMATOR_VERSION=2.2.0-alpha1
BIP39_VERSION=1.0.2
BIP39_VERSION=1.0.4
COROUTINES_OKHTTP=1.0
GOOGLE_MATERIAL_VERSION=1.6.1
GRPC_VERSION=1.48.0
GRPC_VERSION=1.48.1
GSON_VERSION=2.9.0
GUAVA_VERSION=31.1-android
JACOCO_VERSION=0.8.8

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionSha256Sum=cb87f222c5585bd46838ad4db78463a5c5f3d336e5e2b98dc7c0c586527351c2
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -4,6 +4,7 @@ import com.google.protobuf.gradle.plugins
import com.google.protobuf.gradle.proto
import com.google.protobuf.gradle.protobuf
import com.google.protobuf.gradle.protoc
import java.util.Base64
plugins {
id("com.android.library")
@ -15,29 +16,106 @@ plugins {
id("org.jetbrains.dokka")
id("com.google.protobuf")
id("org.mozilla.rust-android-gradle.rust-android")
id("com.vanniktech.maven.publish.base")
id("wtf.emulator.gradle")
id("zcash-sdk.emulator-wtf-conventions")
id("maven-publish")
id("signing")
}
// Publishing information
val publicationVariant = "release"
val myVersion = project.property("LIBRARY_VERSION").toString()
val myArtifactId = "zcash-android-sdk"
val isSnapshot = project.property("IS_SNAPSHOT").toString().toBoolean()
val version = project.property("LIBRARY_VERSION").toString()
project.group = "cash.z.ecc.android"
project.version = if (isSnapshot) {
"$version-SNAPSHOT"
} else {
version
}
val myArtifactId = "zcash-android-sdk"
publishing {
publications {
publications.withType<MavenPublication>().all {
register<MavenPublication>("release") {
artifactId = myArtifactId
groupId = "cash.z.ecc.android"
version = if (isSnapshot) {
"$myVersion-SNAPSHOT"
} else {
myVersion
}
afterEvaluate {
from(components[publicationVariant])
}
pom {
name.set("Zcash Android Wallet SDK")
description.set("This lightweight SDK connects Android to Zcash, allowing third-party " +
"Android apps to send and receive shielded transactions easily, securely and privately.")
url.set("https://github.com/zcash/zcash-android-wallet-sdk/")
inceptionYear.set("2018")
scm {
url.set("https://github.com/zcash/zcash-android-wallet-sdk/")
connection.set("scm:git:git://github.com/zcash/zcash-android-wallet-sdk.git")
developerConnection.set("scm:git:ssh://git@github.com/zcash/zcash-android-wallet-sdk.git")
}
developers {
developer {
id.set("zcash")
name.set("Zcash")
url.set("https://github.com/zcash/")
}
}
licenses {
license {
name.set("The MIT License")
url.set("http://opensource.org/licenses/MIT")
distribution.set("repo")
}
}
}
}
}
repositories {
val mavenUrl = if (isSnapshot) {
project.property("ZCASH_MAVEN_PUBLISH_SNAPSHOT_URL").toString()
} else {
project.property("ZCASH_MAVEN_PUBLISH_RELEASE_URL").toString()
}
val mavenPublishUsername = project.property("ZCASH_MAVEN_PUBLISH_USERNAME").toString()
val mavenPublishPassword = project.property("ZCASH_MAVEN_PUBLISH_PASSWORD").toString()
mavenLocal {
name = "MavenLocal"
}
maven(mavenUrl) {
name = "MavenCentral"
credentials {
username = mavenPublishUsername
password = mavenPublishPassword
}
}
}
}
signing {
// Maven Central requires signing for non-snapshots
isRequired = !isSnapshot
val signingKey = run {
val base64EncodedKey = project.property("ZCASH_ASCII_GPG_KEY").toString()
if (base64EncodedKey.isNotEmpty()) {
val keyBytes = Base64.getDecoder().decode(base64EncodedKey)
String(keyBytes)
} else {
""
}
}
if (signingKey.isNotEmpty()) {
useInMemoryPgpKeys(signingKey, "")
}
sign(publishing.publications)
}
android {
@ -78,7 +156,7 @@ android {
kotlinOptions {
// Tricky: fix: By default, the kotlin_module name will not include the version (in classes.jar/META-INF). Instead it has a colon, which breaks compilation on Windows. This is one way to set it explicitly to the proper value. See https://github.com/zcash/zcash-android-wallet-sdk/issues/222 for more info.
freeCompilerArgs += listOf("-module-name", "$myArtifactId-${project.version}_release")
freeCompilerArgs += listOf("-module-name", "$myArtifactId-${myVersion}_release")
}
packagingOptions {
@ -102,53 +180,12 @@ android {
baseline = File("lint-baseline.xml")
}
// Handled by com.vanniktech.maven.publish.AndroidSingleVariantLibrary below
// publishing {
// singleVariant("release") {
// withSourcesJar()
// withJavadocJar()
// }
// }
}
mavenPublishing {
publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.DEFAULT)
signAllPublications()
pom {
name.set("Zcash Android Wallet SDK")
description.set("This lightweight SDK connects Android to Zcash, allowing third-party Android " +
"apps to send and receive shielded transactions easily, securely and privately.")
url.set("https://github.com/zcash/zcash-android-wallet-sdk/")
inceptionYear.set("2018")
scm {
url.set("https://github.com/zcash/zcash-android-wallet-sdk/")
connection.set("scm:git:git://github.com/zcash/zcash-android-wallet-sdk.git")
developerConnection.set("scm:git:ssh://git@github.com/zcash/zcash-android-wallet-sdk.git")
}
developers {
developer {
id.set("zcash")
name.set("Zcash")
url.set("https://github.com/zcash/")
}
}
licenses {
license {
name.set("The MIT License")
url.set("http://opensource.org/licenses/MIT")
distribution.set("repo")
}
publishing {
singleVariant(publicationVariant) {
withSourcesJar()
withJavadocJar()
}
}
configure(
com.vanniktech.maven.publish.AndroidSingleVariantLibrary(
"release",
sourcesJar = true,
publishJavadocJar = true
)
)
}
allOpen {
@ -264,6 +301,7 @@ dependencies {
androidTestImplementation(libs.androidx.test.junit)
androidTestImplementation(libs.androidx.test.core)
androidTestImplementation(libs.coroutines.okhttp)
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)

View File

@ -83,8 +83,8 @@ class AssetTest {
assertEquals(
"File: ${it.filename}",
CheckpointTool.checkpointHeightFromFilename(network, it.filename),
jsonObject.getInt("height")
CheckpointTool.checkpointHeightFromFilename(network, it.filename).value,
jsonObject.getLong("height")
)
// In the future, additional validation of the JSON can be added
@ -94,9 +94,9 @@ class AssetTest {
private data class JsonFile(val jsonObject: JSONObject, val filename: String)
companion object {
fun listAssets(network: ZcashNetwork) = runBlocking {
fun listAssets(network: ZcashNetwork): Array<String>? = runBlocking {
CheckpointTool.listCheckpointDirectoryContents(
ApplicationProvider.getApplicationContext<Context>(),
ApplicationProvider.getApplicationContext(),
CheckpointTool.checkpointDirectory(network)
)
}

View File

@ -0,0 +1,39 @@
package cash.z.ecc.android.sdk.db
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.internal.AndroidApiVersion
import cash.z.ecc.android.sdk.internal.db.PendingTransactionDb
import cash.z.ecc.android.sdk.test.getAppContext
import cash.z.ecc.fixture.DatabaseNameFixture
import cash.z.ecc.fixture.DatabasePathFixture
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Test
import java.io.File
class CommonDatabaseBuilderTest {
@Test
@SmallTest
fun proper_database_name_used_test() {
val dbDirectory = File(DatabasePathFixture.new())
val dbFileName = DatabaseNameFixture.newDb(name = DatabaseCoordinator.DB_PENDING_TRANSACTIONS_NAME)
val dbFile = File(dbDirectory, dbFileName)
val db = commonDatabaseBuilder(
getAppContext(),
PendingTransactionDb::class.java,
dbFile
).build()
assertNotNull(db)
val expectedDbName = if (AndroidApiVersion.isAtLeastO_MR1) {
dbFileName
} else {
dbFile.absolutePath
}
assertEquals(expectedDbName, db.openHelper.databaseName)
}
}

View File

@ -0,0 +1,239 @@
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.model.ZcashNetwork
import cash.z.ecc.android.sdk.test.getAppContext
import cash.z.ecc.fixture.DatabaseNameFixture
import cash.z.ecc.fixture.DatabasePathFixture
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import java.io.File
class DatabaseCoordinatorTest {
private val dbCoordinator = DatabaseCoordinator.getInstance(getAppContext())
@Before
fun clear_test_files() {
val databaseDir = DatabasePathFixture.new(baseFolderPath = DatabasePathFixture.DATABASE_DIR_PATH)
val noBackupDir = DatabasePathFixture.new(baseFolderPath = DatabasePathFixture.NO_BACKUP_DIR_PATH)
File(databaseDir).deleteRecursively()
File(noBackupDir).deleteRecursively()
}
// Sanity check of the database coordinator instance and its thread-safe implementation. Our aim
// here is to run two jobs in parallel (the second one runs immediately after the first was started)
// to test mutex implementation and correct DatabaseCoordinator function call result.
@Test
@SmallTest
fun mutex_test() = runTest {
var testResult: File? = null
launch {
delay(1000)
testResult = dbCoordinator.cacheDbFile(
DatabaseNameFixture.TEST_DB_NETWORK,
DatabaseNameFixture.TEST_DB_ALIAS
)
}
val job2 = launch {
delay(1001)
testResult = dbCoordinator.cacheDbFile(
ZcashNetwork.Mainnet,
"TestZcashSdk"
)
}
advanceTimeBy(1002)
job2.join().also {
assertTrue(testResult != null)
assertTrue(testResult!!.absolutePath.isNotEmpty())
assertTrue(testResult!!.absolutePath.contains(ZcashNetwork.Mainnet.networkName))
assertTrue(testResult!!.absolutePath.contains("TestZcashSdk"))
}
}
@FlakyTest
@Test
@MediumTest
fun mutex_stress_test() {
// We run the mutex test multiple times sequentially to catch a possible problem.
for (x in 0..9) {
mutex_test()
}
}
@Test
@SmallTest
fun database_cache_file_creation_test() = runTest {
val directory = File(DatabasePathFixture.new())
val fileName = DatabaseNameFixture.newDb(name = DatabaseCoordinator.DB_CACHE_NAME)
val expectedFilePath = File(directory, fileName).path
dbCoordinator.cacheDbFile(
DatabaseNameFixture.TEST_DB_NETWORK,
DatabaseNameFixture.TEST_DB_ALIAS
).also { resultFile ->
assertEquals(expectedFilePath, resultFile.absolutePath)
}
}
@Test
@SmallTest
fun database_data_file_creation_test() = runTest {
val directory = File(DatabasePathFixture.new())
val fileName = DatabaseNameFixture.newDb(name = DatabaseCoordinator.DB_DATA_NAME)
val expectedFilePath = File(directory, fileName).path
dbCoordinator.dataDbFile(
DatabaseNameFixture.TEST_DB_NETWORK,
DatabaseNameFixture.TEST_DB_ALIAS
).also { resultFile ->
assertEquals(expectedFilePath, resultFile.absolutePath)
}
}
@Test
@SmallTest
fun database_transactions_file_creation_test() = runTest {
val directory = File(DatabasePathFixture.new())
val fileName = DatabaseNameFixture.newDb(name = DatabaseCoordinator.DB_PENDING_TRANSACTIONS_NAME)
val expectedFilePath = File(directory, fileName).path
dbCoordinator.pendingTransactionsDbFile(
DatabaseNameFixture.TEST_DB_NETWORK,
DatabaseNameFixture.TEST_DB_ALIAS
).also { resultFile ->
assertEquals(expectedFilePath, resultFile.absolutePath)
}
}
@Test
@SmallTest
fun database_files_move_test() = runTest {
val parentFile = File(
DatabasePathFixture.new(
baseFolderPath = DatabasePathFixture.DATABASE_DIR_PATH,
internalPath = ""
)
)
val originalDbFile = getEmptyFile(
parent = parentFile,
fileName = DatabaseNameFixture.newDb(
name = DatabaseCoordinator.DB_CACHE_NAME_LEGACY,
alias = DatabaseCoordinator.ALIAS_LEGACY
)
)
val originalDbJournalFile = getEmptyFile(
parent = parentFile,
fileName = DatabaseNameFixture.newDbJournal(
name = DatabaseCoordinator.DB_CACHE_NAME_LEGACY,
alias = DatabaseCoordinator.ALIAS_LEGACY
)
)
val originalDbWalFile = getEmptyFile(
parent = parentFile,
fileName = DatabaseNameFixture.newDbWal(
name = DatabaseCoordinator.DB_CACHE_NAME_LEGACY,
alias = DatabaseCoordinator.ALIAS_LEGACY
)
)
val expectedDbFile = File(
DatabasePathFixture.new(),
DatabaseNameFixture.newDb(name = DatabaseCoordinator.DB_CACHE_NAME)
)
val expectedDbJournalFile = File(
DatabasePathFixture.new(),
DatabaseNameFixture.newDbJournal(name = DatabaseCoordinator.DB_CACHE_NAME)
)
val expectedDbWalFile = File(
DatabasePathFixture.new(),
DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_CACHE_NAME)
)
assertTrue(originalDbFile.exists())
assertTrue(originalDbJournalFile.exists())
assertTrue(originalDbWalFile.exists())
assertFalse(expectedDbFile.exists())
assertFalse(expectedDbJournalFile.exists())
assertFalse(expectedDbWalFile.exists())
dbCoordinator.cacheDbFile(
DatabaseNameFixture.TEST_DB_NETWORK,
DatabaseNameFixture.TEST_DB_ALIAS
).also { resultFile ->
assertTrue(resultFile.exists())
assertEquals(expectedDbFile.absolutePath, resultFile.absolutePath)
assertTrue(expectedDbFile.exists())
assertTrue(expectedDbJournalFile.exists())
assertTrue(expectedDbWalFile.exists())
assertFalse(originalDbFile.exists())
assertFalse(originalDbJournalFile.exists())
assertFalse(originalDbWalFile.exists())
}
}
private fun getEmptyFile(parent: File, fileName: String): File {
return File(parent, fileName).apply {
assertTrue(parentFile != null)
parentFile!!.mkdirs()
assertTrue(parentFile!!.exists())
createNewFile()
assertTrue(exists())
}
}
@Test
@SmallTest
fun delete_database_files_test() = runTest {
val parentFile = File(
DatabasePathFixture.new(
baseFolderPath = DatabasePathFixture.NO_BACKUP_DIR_PATH,
internalPath = DatabasePathFixture.INTERNAL_DATABASE_PATH
)
)
val dbFile = getEmptyFile(
parent = parentFile,
fileName = DatabaseNameFixture.newDb(name = DatabaseCoordinator.DB_CACHE_NAME)
)
val dbJournalFile = getEmptyFile(
parent = parentFile,
fileName = DatabaseNameFixture.newDbJournal(name = DatabaseCoordinator.DB_CACHE_NAME)
)
val dbWalFile = getEmptyFile(
parent = parentFile,
fileName = DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_CACHE_NAME)
)
assertTrue(dbFile.exists())
assertTrue(dbJournalFile.exists())
assertTrue(dbWalFile.exists())
dbCoordinator.deleteDatabases(DatabaseNameFixture.TEST_DB_NETWORK, DatabaseNameFixture.TEST_DB_ALIAS).also {
assertFalse(dbFile.exists())
assertFalse(dbJournalFile.exists())
assertFalse(dbWalFile.exists())
}
}
}

View File

@ -0,0 +1,40 @@
package cash.z.ecc.android.sdk.db
import android.os.Build
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.internal.NoBackupContextWrapper
import cash.z.ecc.android.sdk.test.getAppContext
import cash.z.ecc.fixture.DatabaseNameFixture
import cash.z.ecc.fixture.DatabasePathFixture
import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.File
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O_MR1)
class NoBackupContextWrapperTest {
private val databaseParentDir = File(DatabasePathFixture.new())
private val noBackupContextWrapper = NoBackupContextWrapper(getAppContext(), databaseParentDir)
@Test
@SmallTest
fun get_context_test() {
assertTrue(noBackupContextWrapper.applicationContext is NoBackupContextWrapper)
assertTrue(noBackupContextWrapper.baseContext is NoBackupContextWrapper)
}
@Test
@SmallTest
fun get_database_path_test() {
val testDbPath = File(DatabasePathFixture.new(), DatabaseNameFixture.newDb()).absolutePath
val testDbFile = noBackupContextWrapper.getDatabasePath(testDbPath)
testDbFile.absolutePath.also {
assertTrue(it.isNotEmpty())
assertTrue(it.contains(DatabaseNameFixture.TEST_DB_NAME))
assertTrue(it.contains(DatabasePathFixture.NO_BACKUP_DIR_PATH))
assertTrue(it.contains(DatabasePathFixture.INTERNAL_DATABASE_PATH))
}
}
}

View File

@ -2,7 +2,9 @@ package cash.z.ecc.android.sdk.integration
import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.ext.BlockExplorer
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.util.TestWallet
@ -13,6 +15,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
// TODO [#650]: https://github.com/zcash/zcash-android-wallet-sdk/issues/650
/**
* This test is intended to run to make sure that basic things are functional and pinpoint what is
* not working. It was originally developed after a major refactor to find what broke.
@ -22,7 +26,7 @@ import org.junit.runners.Parameterized
class SanityTest(
private val wallet: TestWallet,
private val encoding: String,
private val birthday: Int
private val birthday: BlockHeight
) {
@ -31,20 +35,23 @@ class SanityTest(
@Test
fun testFilePaths() {
assertEquals(
assertTrue(
"$name has invalid DataDB file",
"/data/user/0/cash.z.ecc.android.sdk.test/databases/TestWallet_${networkName}_Data.db",
wallet.initializer.rustBackend.pathDataDb
wallet.initializer.rustBackend.dataDbFile.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_${networkName}_${DatabaseCoordinator.DB_DATA_NAME}"
)
)
assertEquals(
assertTrue(
"$name has invalid CacheDB file",
"/data/user/0/cash.z.ecc.android.sdk.test/databases/TestWallet_${networkName}_Cache.db",
wallet.initializer.rustBackend.pathCacheDb
wallet.initializer.rustBackend.cacheDbFile.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_${networkName}_${DatabaseCoordinator.DB_CACHE_NAME}"
)
)
assertEquals(
assertTrue(
"$name has invalid CacheDB params dir",
"/data/user/0/cash.z.ecc.android.sdk.test/cache/params",
wallet.initializer.rustBackend.pathParamsDir
wallet.initializer.rustBackend.pathParamsDir.endsWith(
"cache/params"
)
)
}
@ -70,8 +77,15 @@ class SanityTest(
fun testLatestHeight() = runBlocking {
if (wallet.networkName == "mainnet") {
val expectedHeight = BlockExplorer.fetchLatestHeight()
// fetch height directly because the synchronizer hasn't started, yet
val downloaderHeight = wallet.service.getLatestBlockHeight()
// Fetch height directly because the synchronizer hasn't started, yet. Then we test the
// result, only if there is no server communication problem.
val downloaderHeight = runCatching {
return@runCatching wallet.service.getLatestBlockHeight()
}.onFailure {
twig(it)
}.getOrElse { return@runBlocking }
assertTrue(
"${wallet.endpoint} ${wallet.networkName} Lightwalletd is too far behind. Downloader height $downloaderHeight is more than 10 blocks behind block explorer height $expectedHeight",
expectedHeight - 10 < downloaderHeight.value
@ -81,9 +95,18 @@ class SanityTest(
@Test
fun testSingleBlockDownload() = runBlocking {
// fetch block directly because the synchronizer hasn't started, yet
// Fetch height directly because the synchronizer hasn't started, yet. Then we test the
// result, only if there is no server communication problem.
val height = BlockHeight.new(wallet.network, 1_000_000)
val block = wallet.service.getBlockRange(height..height).first()
val block = runCatching {
return@runCatching wallet.service.getBlockRange(height..height).first()
}.onFailure {
twig(it)
}.getOrElse { return@runBlocking }
val downloaderHeight = runCatching {
wallet.service.getLatestBlockHeight()
}.getOrNull() ?: return@runBlocking
assertTrue("$networkName failed to return a proper block. Height was ${block.height} but we expected $height", block.height == height.value)
}
@ -95,13 +118,13 @@ class SanityTest(
arrayOf(
TestWallet(TestWallet.Backups.SAMPLE_WALLET),
"zxviewtestsapling1qv0ue89kqqqqpqqyt4cl5wvssx4wqq30e5m948p07dnwl9x3u75vvnzvjwwpjkrf8yk2gva0kkxk9p8suj4xawlzw9pajuxgap83wykvsuyzfrm33a2p2m4jz2205kgzx0l2lj2kyegtnuph6crkyvyjqmfxut84nu00wxgrstu5fy3eu49nzl8jzr4chmql4ysgg2t8htn9dtvxy8c7wx9rvcerqsjqm6lqln9syk3g8rr3xpy3l4nj0kawenzpcdtnv9qmy98vdhqzaf063",
1320000
BlockHeight.new(ZcashNetwork.Testnet, 1330000)
),
// Mainnet wallet
arrayOf(
TestWallet(TestWallet.Backups.SAMPLE_WALLET, ZcashNetwork.Mainnet),
"zxviews1q0hxkupsqqqqpqzsffgrk2smjuccedua7zswf5e3rgtv3ga9nhvhjug670egshd6me53r5n083s2m9mf4va4z7t39ltd3wr7hawnjcw09eu85q0ammsg0tsgx24p4ma0uvr4p8ltx5laum2slh2whc23ctwlnxme9w4dw92kalwk5u4wyem8dynknvvqvs68ktvm8qh7nx9zg22xfc77acv8hk3qqll9k3x4v2fa26puu2939ea7hy4hh60ywma69xtqhcy4037ne8g2sg8sq",
1000000
BlockHeight.new(ZcashNetwork.Mainnet, 1000000)
)
)
}

View File

@ -4,9 +4,11 @@ import androidx.test.filters.LargeTest
import androidx.test.filters.MediumTest
import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.util.TestWallet
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Ignore
import org.junit.Test
@ -20,19 +22,36 @@ class SmokeTest {
@Test
fun testFilePaths() {
Assert.assertEquals("Invalid DataDB file", "/data/user/0/cash.z.ecc.android.sdk.test/databases/TestWallet_testnet_Data.db", wallet.initializer.rustBackend.pathDataDb)
Assert.assertEquals("Invalid CacheDB file", "/data/user/0/cash.z.ecc.android.sdk.test/databases/TestWallet_testnet_Cache.db", wallet.initializer.rustBackend.pathCacheDb)
Assert.assertEquals("Invalid CacheDB params dir", "/data/user/0/cash.z.ecc.android.sdk.test/cache/params", wallet.initializer.rustBackend.pathParamsDir)
assertTrue(
"Invalid DataDB file",
wallet.initializer.rustBackend.dataDbFile.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_DATA_NAME}"
)
)
assertTrue(
"Invalid CacheDB file",
wallet.initializer.rustBackend.cacheDbFile.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_CACHE_NAME}"
)
)
assertTrue(
"Invalid CacheDB params dir",
wallet.initializer.rustBackend.pathParamsDir.endsWith("cache/params")
)
}
@Test
fun testBirthday() {
Assert.assertEquals("Invalid birthday height", 1_320_000, wallet.initializer.checkpoint.height)
assertEquals(
"Invalid birthday height",
1_330_000,
wallet.initializer.checkpoint.height.value
)
}
@Test
fun testViewingKeys() {
Assert.assertEquals("Invalid encoding", "zxviewtestsapling1qv0ue89kqqqqpqqyt4cl5wvssx4wqq30e5m948p07dnwl9x3u75vvnzvjwwpjkrf8yk2gva0kkxk9p8suj4xawlzw9pajuxgap83wykvsuyzfrm33a2p2m4jz2205kgzx0l2lj2kyegtnuph6crkyvyjqmfxut84nu00wxgrstu5fy3eu49nzl8jzr4chmql4ysgg2t8htn9dtvxy8c7wx9rvcerqsjqm6lqln9syk3g8rr3xpy3l4nj0kawenzpcdtnv9qmy98vdhqzaf063", wallet.initializer.viewingKeys[0].encoding)
assertEquals("Invalid encoding", "zxviewtestsapling1qv0ue89kqqqqpqqyt4cl5wvssx4wqq30e5m948p07dnwl9x3u75vvnzvjwwpjkrf8yk2gva0kkxk9p8suj4xawlzw9pajuxgap83wykvsuyzfrm33a2p2m4jz2205kgzx0l2lj2kyegtnuph6crkyvyjqmfxut84nu00wxgrstu5fy3eu49nzl8jzr4chmql4ysgg2t8htn9dtvxy8c7wx9rvcerqsjqm6lqln9syk3g8rr3xpy3l4nj0kawenzpcdtnv9qmy98vdhqzaf063", wallet.initializer.viewingKeys[0].encoding)
}
// This test takes an extremely long time

View File

@ -1,6 +1,8 @@
package cash.z.ecc.android.sdk.integration.service
import android.os.Build
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose
@ -32,6 +34,7 @@ import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.Spy
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
@MaintainedTest(TestPurpose.REGRESSION)
@RunWith(AndroidJUnit4::class)
@SmallTest
@ -69,14 +72,28 @@ class ChangeServiceTest : ScopedTest() {
@Test
fun testSanityCheck() {
val result = service.getLatestBlockHeight()
// Test the result, only if there is no server communication problem.
val result = runCatching {
return@runCatching service.getLatestBlockHeight()
}.onFailure {
twig(it)
}.getOrElse { return }
assertTrue(result > network.saplingActivationHeight)
}
@Test
fun testCleanSwitch() = runBlocking {
downloader.changeService(otherService)
val result = downloader.downloadBlockRange(BlockHeight.new(network, 900_000)..BlockHeight.new(network, 901_000))
// Test the result, only if there is no server communication problem.
val result = runCatching {
downloader.changeService(otherService)
return@runCatching downloader.downloadBlockRange(
BlockHeight.new(network, 900_000)..BlockHeight.new(network, 901_000)
)
}.onFailure {
twig(it)
}.getOrElse { return@runBlocking }
assertEquals(1_001, result)
}
@ -111,9 +128,17 @@ class ChangeServiceTest : ScopedTest() {
@Test
fun testSwitchToInvalidServer() = runBlocking {
var caughtException: Throwable? = null
downloader.changeService(LightWalletGrpcService.new(context, LightWalletEndpoint("invalid.lightwalletd", 9087, true))) {
caughtException = it
}
// the test can continue only if there is no server communication problem
if (caughtException is StatusException) {
twig("Server communication problem while testing.")
return@runBlocking
}
assertNotNull("Using an invalid host should generate an exception.", caughtException)
assertTrue(
"Exception was of the wrong type.",
@ -124,9 +149,17 @@ class ChangeServiceTest : ScopedTest() {
@Test
fun testSwitchToTestnetFails() = runBlocking {
var caughtException: Throwable? = null
downloader.changeService(LightWalletGrpcService.new(context, LightWalletEndpoint.Testnet)) {
caughtException = it
}
// the test can continue only if there is no server communication problem
if (caughtException is StatusException) {
twig("Server communication problem while testing.")
return@runBlocking
}
assertNotNull("Using an invalid host should generate an exception.", caughtException)
assertTrue(
"Exception was of the wrong type. Expected ${ChainInfoNotMatching::class.simpleName} but was ${caughtException!!::class.simpleName}",

View File

@ -39,10 +39,10 @@ class SaplingParamToolTest {
}
@Test
fun testOnlySpendFileExits() = runBlocking {
fun output_file_exists() = runBlocking {
// Given
SaplingParamTool.fetchParams(cacheDir)
File("$cacheDir/${ZcashSdk.OUTPUT_PARAM_FILE_NAME}").delete()
File(cacheDir, ZcashSdk.OUTPUT_PARAM_FILE_NAME).delete()
// When
val result = SaplingParamTool.validate(cacheDir)
@ -52,10 +52,10 @@ class SaplingParamToolTest {
}
@Test
fun testOnlyOutputOFileExits() = runBlocking {
fun param_file_exists() = runBlocking {
// Given
SaplingParamTool.fetchParams(cacheDir)
File("$cacheDir/${ZcashSdk.SPEND_PARAM_FILE_NAME}").delete()
File(cacheDir, ZcashSdk.SPEND_PARAM_FILE_NAME).delete()
// When
val result = SaplingParamTool.validate(cacheDir)

View File

@ -13,6 +13,8 @@ import cash.z.ecc.android.sdk.internal.service.LightWalletService
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.test.ScopedTest
import cash.z.ecc.fixture.DatabaseNameFixture
import cash.z.ecc.fixture.DatabasePathFixture
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.stub
import kotlinx.coroutines.cancel
@ -32,6 +34,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import java.io.File
@MaintainedTest(TestPurpose.REGRESSION)
@RunWith(AndroidJUnit4::class)
@ -42,19 +45,27 @@ class PersistentTransactionManagerTest : ScopedTest() {
@Mock lateinit var mockService: LightWalletService
val pendingDbName = "PersistentTxMgrTest_Pending.db"
val dataDbName = "PersistentTxMgrTest_Data.db"
private val pendingDbFile = File(
DatabasePathFixture.new(),
DatabaseNameFixture.newDb(name = "PersistentTxMgrTest_Pending.db")
).apply {
assertTrue(parentFile != null)
parentFile!!.mkdirs()
assertTrue(parentFile!!.exists())
createNewFile()
assertTrue(exists())
}
private lateinit var manager: OutboundTransactionManager
@Before
fun setup() {
initMocks()
deleteDb()
manager = PersistentTransactionManager(context, mockEncoder, mockService, pendingDbName)
manager = PersistentTransactionManager(context, mockEncoder, mockService, pendingDbFile)
}
private fun deleteDb() {
context.getDatabasePath(pendingDbName).delete()
pendingDbFile.deleteRecursively()
}
private fun initMocks() {

View File

@ -49,16 +49,16 @@ class BranchIdTest internal constructor(
val mainnetBackend = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Mainnet, ZcashNetwork.Mainnet.saplingActivationHeight) }
return listOf(
// Mainnet Cases
arrayOf("Sapling", 419_200, 1991772603L, "76b809bb", mainnetBackend),
arrayOf("Blossom", 653_600, 733220448L, "2bb40e60", mainnetBackend),
arrayOf("Heartwood", 903_000, 4122551051L, "f5b9230b", mainnetBackend),
arrayOf("Canopy", 1_046_400, 3925833126L, "e9ff75a6", mainnetBackend),
arrayOf("Sapling", BlockHeight.new(ZcashNetwork.Mainnet, 419_200), 1991772603L, "76b809bb", mainnetBackend),
arrayOf("Blossom", BlockHeight.new(ZcashNetwork.Mainnet, 653_600), 733220448L, "2bb40e60", mainnetBackend),
arrayOf("Heartwood", BlockHeight.new(ZcashNetwork.Mainnet, 903_000), 4122551051L, "f5b9230b", mainnetBackend),
arrayOf("Canopy", BlockHeight.new(ZcashNetwork.Mainnet, 1_046_400), 3925833126L, "e9ff75a6", mainnetBackend),
// Testnet Cases
arrayOf("Sapling", 280_000, 1991772603L, "76b809bb", testnetBackend),
arrayOf("Blossom", 584_000, 733220448L, "2bb40e60", testnetBackend),
arrayOf("Heartwood", 903_800, 4122551051L, "f5b9230b", testnetBackend),
arrayOf("Canopy", 1_028_500, 3925833126L, "e9ff75a6", testnetBackend)
arrayOf("Sapling", BlockHeight.new(ZcashNetwork.Testnet, 280_000), 1991772603L, "76b809bb", testnetBackend),
arrayOf("Blossom", BlockHeight.new(ZcashNetwork.Testnet, 584_000), 733220448L, "2bb40e60", testnetBackend),
arrayOf("Heartwood", BlockHeight.new(ZcashNetwork.Testnet, 903_800), 4122551051L, "f5b9230b", testnetBackend),
arrayOf("Canopy", BlockHeight.new(ZcashNetwork.Testnet, 1_028_500), 3925833126L, "e9ff75a6", testnetBackend)
)
}
}

View File

@ -0,0 +1,11 @@
package cash.z.ecc.android.sdk.test
import android.content.Context
import androidx.annotation.StringRes
import androidx.test.core.app.ApplicationProvider
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)

View File

@ -4,6 +4,7 @@ import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.CheckpointTool.IS_FALLBACK_ON_FAILURE
import kotlinx.coroutines.runBlocking
@ -16,7 +17,10 @@ class CheckpointToolTest {
@Test
@SmallTest
fun birthday_height_from_filename() {
assertEquals(123, CheckpointTool.checkpointHeightFromFilename(ZcashNetwork.Mainnet, "123.json"))
assertEquals(
BlockHeight.new(ZcashNetwork.Mainnet, 1_230_000),
CheckpointTool.checkpointHeightFromFilename(ZcashNetwork.Mainnet, "1230000.json")
)
}
@Test

View File

@ -0,0 +1,31 @@
package cash.z.ecc.fixture
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.model.ZcashNetwork
object DatabaseNameFixture {
const val TEST_DB_NAME = "empty.db"
const val TEST_DB_JOURNAL_NAME_SUFFIX = DatabaseCoordinator.DATABASE_FILE_JOURNAL_SUFFIX
const val TEST_DB_WAL_NAME_SUFFIX = DatabaseCoordinator.DATABASE_FILE_WAL_SUFFIX
const val TEST_DB_ALIAS = "zcash_sdk"
val TEST_DB_NETWORK = ZcashNetwork.Testnet
internal fun newDb(
name: String = TEST_DB_NAME,
alias: String = TEST_DB_ALIAS,
network: String = TEST_DB_NETWORK.networkName
) = "${alias}_${network}_$name"
internal fun newDbJournal(
name: String = TEST_DB_NAME,
alias: String = TEST_DB_ALIAS,
network: String = TEST_DB_NETWORK.networkName
) = "${alias}_${network}_$name-$TEST_DB_JOURNAL_NAME_SUFFIX"
internal fun newDbWal(
name: String = TEST_DB_NAME,
alias: String = TEST_DB_ALIAS,
network: String = TEST_DB_NETWORK.networkName
) = "${alias}_${network}_$name-$TEST_DB_WAL_NAME_SUFFIX"
}

View File

@ -0,0 +1,29 @@
package cash.z.ecc.fixture
import cash.z.ecc.android.sdk.internal.Files
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
import kotlinx.coroutines.runBlocking
import java.io.File
object DatabasePathFixture {
val NO_BACKUP_DIR_PATH: String = runBlocking {
getAppContext().getNoBackupFilesDirCompat().absolutePath
}
val DATABASE_DIR_PATH: String = runBlocking {
getAppContext().getDatabasePathSuspend("temporary.db").parentFile.let { parentFile ->
assert(parentFile != null) { "Failed to create database folder." }
parentFile!!.mkdirs()
assert(parentFile.exists()) { "Failed to check database folder." }
parentFile.absolutePath
}
}
const val INTERNAL_DATABASE_PATH = Files.NO_BACKUP_SUBDIRECTORY
internal fun new(
baseFolderPath: String = NO_BACKUP_DIR_PATH,
internalPath: String = INTERNAL_DATABASE_PATH
) = File(baseFolderPath, internalPath).absolutePath
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1752500",
"hash": "00000000002721ca5e87699661dc2c259ae14e5ccea0252dbfbd3ecd4d972fa7",
"time": 1658950269,
"saplingTree": "01a3ef62b2119b06263f4ddf3bb35429b91b0c2d5432c2e82381d56ea5e4c8d227001801f1165a0d96afd18c88b0a3198c9b12b3b388e2d0cc3915e7743a7c783510552f00000000000001996cb567e9a871f88f38fbdf9e027da6d9e45c2830a3014b960d684cdc592553000000000001f1275a28ffa603047a39513672a52253bbde6e453fba60b742f790959614bc560162ea4359404b3b43fb50798b3758be7eda6a32fcdbbed2def4c269cbbc362010010629c8231ea9ed1f21dff7b13fddd79400605d1a71dad22765a11dab4514950200019687efd54c9ee6d74165788e1120e8095ff48f7b5cc0a9e67954cf83b04de07001c97f6223a9bc66019e50af01f00e4768212c1ae1c42dc3d82944e2348de15137012acafd61cb88a77c098a05f8a6eec00930b6a28e1b4748c0fde540cd39edfa47000000017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
"orchardTree": "01e4954081f6ae3df79b22ec63ae6b6a8af9b9d8dbbd1c4ac894187e54ff18e820001f01520234b92baf9ba6ceb5636e8e694deaf34fcdc2b9a6c61c67889ea8ffd58605000000000000000001f8be16a220acf8ba291b9b9bf14c1bbb3541c08d5a8a89f6205bf890a18ac93401130cfb41380fdd7836985e2c4c488fdc3d1d1bd4390f350f0e1b8a448f47ac1c012bcbdd308beca04006b18928c4418aad2b3650677289b1b45ea5a21095c5310301100ed4d0a8a440b03f1254ce1efb2d82a94cf001cffa0e7fd6ada813a2688b240130a69de998b87aebcd4c556503a45e559a422ecfbdf2f0e6318a8427e41a7b09017676cfe97afff13797f82f8d631bd29edde424854139c64ab8241e2a2331551401da371f0d3294843fd8f645019a04c07607342c70cf4cc793068355eaccdd671601bc79f0119f97113775379bf58f3f5d9d122766909b797947127b28248ff0720501c1eb3aa1717c2a696ce0aba6c5b5bda44c4eda5cf69ae376cc8b334a2c23fb2b0001374feb2041bfd423c6cc3e064ee2b4705748a082836d39dd723515357fb06e300000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1755000",
"hash": "00000000000aa431fa339ff48bd7ac55f31346f14407362884d5a04674e50e74",
"time": 1659139285,
"saplingTree": "01487aeb1b6ed4701c648e9ef247028ba2e94cd8d236c9bec59e5fb7022ef2b3660185ffc2003a288dde5dbfeb6fcedd3715386cb0b2ce71a10a546fdd25a3d4055d18000141a3a594e42cedd19855d8dbedd7ded0928962a40af5a504592a2bcbb648c100016871d23a7420a11788d2c0608f6d987b73771ff1d85bb4866343b5875f079b6600015979ac055368ed68e7f26aaf7c688d24af4f96274e2f455855f13c1678c2a93001774ce40898fe539ac97e91980ebe1a5085be10239eda6d0104738c819fad864801f1f47fa4eae37e6e1fd478673c43c8adde64dc9cf2281ad8a9c1624805b9385901a0951dffa15b626e4899f376c899245882f88c4ffede8cd7a242de51edfffa6600016bc4ff27516858b8cf13b7675fd90d56bde42f8571ed52e0731c7037dcea013b0001a5f05d22e67265d52a04314b17e58f7fc50933aea0ed5e430a020353a5ebe12401b311bd6baa175de69b09ad3ba0b03ef75c9eb74461c4f4f52120df856e15051f014ee0e948878a90bcb7278e4037bf44ef0325fea828c937ffa613ab946433301b0159a7b450b3e2fa85c1de2385f6e3b77298cda110ad590836972b26fc6ef2ed5a01aff9dd23237bcabbedfdc9a4241ee3c471e0b4d8a860658d5aa35e8a0594a9210001352446c36ff71e79bbed92275d7572cf1445e90155e7dbee94d37aab1858cf280134867611fb40f23a94fa1517e620cb3c4cab5b6d88fff002009e4e9c86dc5c3d01f5b866a0e1a8aa712824cef26261a7ee5a3f98747d1da413f86c681e319c423a01570f01e74ac0b816bdfb55ddec96081fe3da2557abf406ed30ccf93a20ae5f680000017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
"orchardTree": "01f7f982f1e5bb2607adb498641aad65b0b886285c9d0354216fa9f0f0d97f442c001f01dc1c327185e61b049ff03531f9c8cb3818f7924a15b8cf325967801f0117442500000134e73998cd29120070cef7d8e8235a075528725ad9210a4e3d0565b40789561d000000000001f8be16a220acf8ba291b9b9bf14c1bbb3541c08d5a8a89f6205bf890a18ac93401130cfb41380fdd7836985e2c4c488fdc3d1d1bd4390f350f0e1b8a448f47ac1c012bcbdd308beca04006b18928c4418aad2b3650677289b1b45ea5a21095c5310301100ed4d0a8a440b03f1254ce1efb2d82a94cf001cffa0e7fd6ada813a2688b240130a69de998b87aebcd4c556503a45e559a422ecfbdf2f0e6318a8427e41a7b09017676cfe97afff13797f82f8d631bd29edde424854139c64ab8241e2a2331551401da371f0d3294843fd8f645019a04c07607342c70cf4cc793068355eaccdd671601bc79f0119f97113775379bf58f3f5d9d122766909b797947127b28248ff0720501c1eb3aa1717c2a696ce0aba6c5b5bda44c4eda5cf69ae376cc8b334a2c23fb2b0001374feb2041bfd423c6cc3e064ee2b4705748a082836d39dd723515357fb06e300000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1757500",
"hash": "0000000000d151ac883834ea09e1817a48b6efa7f1950dd1fb1a381ab0e739a0",
"time": 1659327728,
"saplingTree": "016fa42ca011082d89168ca57bfb93c2caea59b8004a525fe1f09654c207f83d1d001800000147ed8dad3a86c353e902744e326f89701ee5c8282d97bb4f45b92a1c2a4c1c0e000001f6322d8bcb120e73f0138c460dd361b3949163645769df3ec842600e7fc5b60200000001ff715e9ec1bb54c821000b7ed62b9271375ca9d8ca11b16bc6662de4e9fe865400000001a91d7f61ad8158e6da534285fe0ff445ed2779adf2d12be5b256a14170f7a70101e25cf57ced229ea089d5732d8356bb8ca19d14a4ada376d543fa977f5a7513190001954941e529339317ce9598695b6acf0a01d3d35be8f578363c4de5c8ee68c95300016562c6b48c77ce7f673740755ebabf8c607b30cb926f9c314e616ba79cd7675901ce54921528804b9297bef103d267fdc4038c64380fcb740287e56f71731178440001a330b2cb242c3abf253345e76375cfc2c71c61e474cb97c547336577050cbf5c00017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
"orchardTree": "013e8aeee6cce0c450b9a21f54977033f617f5d14c7b66023f8e1f0bbf2877212f001f0001a7a78f2cf30e0c514b85b53ce2a799f28f263a36f4ee8b7e48fc0fb5b987183a0162a250fa9f3b9f0d743805986b31ebd7d762c38a72433fef358a057f49b67e370001d589f1512354126e95efad28fa7da3b7f62dfe1967910d80114c6410bbe795190000000001f8be16a220acf8ba291b9b9bf14c1bbb3541c08d5a8a89f6205bf890a18ac93401130cfb41380fdd7836985e2c4c488fdc3d1d1bd4390f350f0e1b8a448f47ac1c012bcbdd308beca04006b18928c4418aad2b3650677289b1b45ea5a21095c5310301100ed4d0a8a440b03f1254ce1efb2d82a94cf001cffa0e7fd6ada813a2688b240130a69de998b87aebcd4c556503a45e559a422ecfbdf2f0e6318a8427e41a7b09017676cfe97afff13797f82f8d631bd29edde424854139c64ab8241e2a2331551401da371f0d3294843fd8f645019a04c07607342c70cf4cc793068355eaccdd671601bc79f0119f97113775379bf58f3f5d9d122766909b797947127b28248ff0720501c1eb3aa1717c2a696ce0aba6c5b5bda44c4eda5cf69ae376cc8b334a2c23fb2b0001374feb2041bfd423c6cc3e064ee2b4705748a082836d39dd723515357fb06e300000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1760000",
"hash": "0000000001283529e3acf4f17948e87044a1dee9f5324a2be4bfdb1f9965adff",
"time": 1659516733,
"saplingTree": "01f51fb3642308c649edea46c6e0ed7084c3626f6ff7bf7fde8afeffe1528d0c3e0018000001827d8ce3486ac3843ea6cfd4e836176eadb7ef3e9bb10958f2a4c978dfb959560001663b4da07a3508de750b1eb1d2c1e41ee27117ea4229064fdc9320a4d1bfd63501dd8c2c119c75c7f0897c2805ccb54f33d9b46967e7d67f2ee62ad4134b25512f000001cd26ccdbef4a6edcb9ebf6af3b1f8fba4de56dfc472fc8ba4b4fa1f56aa6ef21011ac156b267e3700c8d6ecf50f2015ffa6b0deca83ef568e150818b2901b54a310001c85edfba92c0f619db0a88d2879275c3dc99890e289a0487fde46329cb40a67201bb05c3fac5068ecf7c879d05e0e2870fef6db2429925fd187e2fd38c7bccc15f0000019abbee9b4817684fc2c5272627af712e1f2fe971bfd4bc143a26b22b623d996b010f1004c6b806e1764c6b08b0b8433a7f339e7217611e9ce5421d17e21f1d350701b3e97c3870c26ce324e2290569cc520b46fbf78e8e44994f55fd4f7d0697804d0001703f0e4cb0cd39846b1f0fb99e58ef8079bf6199626f9585eff0d01fb4d18155011a455e02dd258c6a97a9b81756269fce9ea96b661896561192f4537cc3d10a4901a330b2cb242c3abf253345e76375cfc2c71c61e474cb97c547336577050cbf5c00017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
"orchardTree": "01e256b39f1f747be0c9a8d5961583805db49080e05e379565db98e9b9648f890001c96aebc9cded8c7bd0f66f0f5c92c1aa077f871d4efb45c8662913afd514c02f1f01f3261c161e1594f19cc5c8dda4a3374ae4a66a25392a3dc0318c64aff72a7b32016a94ff2145b4a9250d7eac080ba7423d272e3bcbdef26cb96df78169e7c56c340000000001b1d090970cbc6cd3cc3dacd48b84185d0f97b2db04b7d270074935d1e30ba628000001f8be16a220acf8ba291b9b9bf14c1bbb3541c08d5a8a89f6205bf890a18ac93401130cfb41380fdd7836985e2c4c488fdc3d1d1bd4390f350f0e1b8a448f47ac1c012bcbdd308beca04006b18928c4418aad2b3650677289b1b45ea5a21095c5310301100ed4d0a8a440b03f1254ce1efb2d82a94cf001cffa0e7fd6ada813a2688b240130a69de998b87aebcd4c556503a45e559a422ecfbdf2f0e6318a8427e41a7b09017676cfe97afff13797f82f8d631bd29edde424854139c64ab8241e2a2331551401da371f0d3294843fd8f645019a04c07607342c70cf4cc793068355eaccdd671601bc79f0119f97113775379bf58f3f5d9d122766909b797947127b28248ff0720501c1eb3aa1717c2a696ce0aba6c5b5bda44c4eda5cf69ae376cc8b334a2c23fb2b0001374feb2041bfd423c6cc3e064ee2b4705748a082836d39dd723515357fb06e300000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1762500",
"hash": "00000000017a7c3843f21669e8e4bd8bdaf0fad5f5dcf629ab04987b42e1aa17",
"time": 1659704759,
"saplingTree": "01fbcd6aa2451a2992dc3ebebbc7e342bf170e2d7ccfcf47f4123536c2f6b1564000180001b31b22d8e3fa25cbcda26117e9c49a9ac0be890ae2d96259c40d170209cd4f69000000000109bc787aaa3cb1dd53b9a6312a5319f65375b57bff779305ca4fecf22ed73b19019cc4f6765546053057cb46c04b13c3491a2845463c8e1c0b459861c3d041ae1201e5ded1df5bb1dfd04c1fa03339f2e860029d0524d4e877f578dcfe3ab33fcf230001c23df56312273b93b9e894e52dd21e6f4355e1b501431202b777a23e1784f06401440d1e33e0eb0ef54b953de0083cc94a21e119edc07f73912f33955a5ce94c1800000109f62a95f9c37785745abe209233d080c1e9d258e9b828f3afc85e698797275c00016835e0b829511b303ad2fdb06f00e2cbc90d0a9a0a7bfecb12d6d794c716f70f00000186384c1f717045cd48e98de8b5d9da6047b0821f6c9c6c485a92a520dbacfb100000018ed21e6b0098dd6f0902efde81f9d5da8d7068e8dcf01ab1c015f8f5936c2d5f017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
"orchardTree": "018d8550d51fce8201a8ba611925d247a6434ec4051d754e97761ee2ebb177361b0120227a75d363c060d205fddb48b693b9de061dc224b7291210532295f623950c1f01ecbd7de037092436013cfe17b59249c719e72fea3e0e24082bcf46837048d02d000195f8d67fe58751cb04828837810d6442023a55ed677b7ac6d2572dc443e5d51d0001cd69a36b954858a9cde0629c6ec31bb735aa80d20947bcf25dd182db7492b92d0001b1d090970cbc6cd3cc3dacd48b84185d0f97b2db04b7d270074935d1e30ba628000001f8be16a220acf8ba291b9b9bf14c1bbb3541c08d5a8a89f6205bf890a18ac93401130cfb41380fdd7836985e2c4c488fdc3d1d1bd4390f350f0e1b8a448f47ac1c012bcbdd308beca04006b18928c4418aad2b3650677289b1b45ea5a21095c5310301100ed4d0a8a440b03f1254ce1efb2d82a94cf001cffa0e7fd6ada813a2688b240130a69de998b87aebcd4c556503a45e559a422ecfbdf2f0e6318a8427e41a7b09017676cfe97afff13797f82f8d631bd29edde424854139c64ab8241e2a2331551401da371f0d3294843fd8f645019a04c07607342c70cf4cc793068355eaccdd671601bc79f0119f97113775379bf58f3f5d9d122766909b797947127b28248ff0720501c1eb3aa1717c2a696ce0aba6c5b5bda44c4eda5cf69ae376cc8b334a2c23fb2b0001374feb2041bfd423c6cc3e064ee2b4705748a082836d39dd723515357fb06e300000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1765000",
"hash": "00000000010881061abd953e3cae22cef9ac0d59d52a7fc41fa84d6c7656763e",
"time": 1659893089,
"saplingTree": "01c82bfd8fb955801a6c73bf26c999c7588eb33b362a68d898013f827e9ae0fb6a015e4715bab32e13c2bd88b32f9397dedbb115f3b4b9f056aeab068bcacb599659180167619283e7272dd833c45bb2f9ccc486c388a203e0a8a53213a84de27d66854501a0557fe4b8693ca7db4d1de647c987873f3e231bc8aa75892d3cd3d6c6755c0e01f7cc73cb43aa1d6a367f41eef0211173dd062da372e977ed0c692e59fbcbc93e000109a80a49995974f38fa8f73acdad1f7e6bd4919bc126309ce437d2ea4f3ab70c00000001ec676047263b15efba4d8e63b4f37ddbda791cbc588957e0671341aa3720084301dbfc2451fe34384638a3432143865f5d7b8b2a7e2e7a66cd53a5f7c299fb0a1d01cc9f79025fa31f9f5ed10802ea7c4544e23fa10013eea892329c2675b0eca20c01c3a0c40c83e182112e7a2ab8327b9898acbd3aefa31da43b47a39a36db6ef74f0000018a81193c32003895922db0e63e73c50cd71cac65107ee32d6232ecdd332f585b00017685c6cb28cae3ebd2b5f1dd5722b53718dda65c8e7abb38f55b180ef1ffdb37015fff3290c15bc580315673e71ce9ebc18ea8562083db38dd61f2d2bd37ff99550001e9254e1c420d982ba56e67ecebf5e4b1921f588b3a165ba65f9d8241d26bb32301d85f6214920d267663a855587995121330148f79681d1d0e1dd9cd3cd0fefa4500018ed21e6b0098dd6f0902efde81f9d5da8d7068e8dcf01ab1c015f8f5936c2d5f017e16ac72afb29d36b6ba9cead439f560ec9c25e60a178552a69a4748e7edf51e",
"orchardTree": "01366cfbdde62aae1022f45c2e7ab97a13dbc62e349292b5fe66af232d7852200e001f0000012542127304cf2a12e2786e2c48bfac68bb1e1832f30748fd5faae64d4009382e000001ea4f1bf4431dc71f69560969ed1b1896331139f6c2abb30d31f96d699c182a0101b1d090970cbc6cd3cc3dacd48b84185d0f97b2db04b7d270074935d1e30ba628000001f8be16a220acf8ba291b9b9bf14c1bbb3541c08d5a8a89f6205bf890a18ac93401130cfb41380fdd7836985e2c4c488fdc3d1d1bd4390f350f0e1b8a448f47ac1c012bcbdd308beca04006b18928c4418aad2b3650677289b1b45ea5a21095c5310301100ed4d0a8a440b03f1254ce1efb2d82a94cf001cffa0e7fd6ada813a2688b240130a69de998b87aebcd4c556503a45e559a422ecfbdf2f0e6318a8427e41a7b09017676cfe97afff13797f82f8d631bd29edde424854139c64ab8241e2a2331551401da371f0d3294843fd8f645019a04c07607342c70cf4cc793068355eaccdd671601bc79f0119f97113775379bf58f3f5d9d122766909b797947127b28248ff0720501c1eb3aa1717c2a696ce0aba6c5b5bda44c4eda5cf69ae376cc8b334a2c23fb2b0001374feb2041bfd423c6cc3e064ee2b4705748a082836d39dd723515357fb06e300000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "test",
"height": "1980000",
"hash": "002ed4cb2b1ff42fb3c0a2cc937fa7c6593c73c0c8688f3ce6c398419a4c0af3",
"time": 1659290352,
"saplingTree": "0119c3970a5842d338e1d00d789ddabaab0b128b5485779674a6f2ff4350c2382d00100115573ba41e1eba171f1f752a0c87798a426f5f7ba329f7cab8929c37c5f77d270000000001a75a362c331628986b65647791374e7db0bbdbb2b871673fb1e6c0f11d7b791a000160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
"orchardTree": "01e9e5bbb3887195a57aae9be6a4fe8c6c3f43fe800c8650eb9937aa5a14e24219011c3fa06971b6adc1b915ec0f83c43cd6869f3026ca45ac68c2665d9f8654c4301f01f7cf6110e572e1946cf525c03905cb3129e477ba772ab5052be8a5ac97f2a911000001c8c190d39cf8b3d2e1728e9597f2c0124f94c7d29c10d9a25d094bee38a8150f016ae9f45036c0ce6ac3f523e0887a3195a02cacef78a2e49ac150adb7dc7b701d00016460d41653df12bbe8c6584d2aa56ae658f02f8ee202ed9238dcfd48bd89e51401bfae99e1684aa4131806b36d697f2c07df94fc5d4a85a1b740ce5b174f9eb12b01a89b33f0d7233cc0e1e80799d98de525c05caf198398a352f8a3bbcfd3c61f3c0000016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "test",
"height": "1990000",
"hash": "004167f8881a1e0d6407ddebaa77dd2ddb9d5d979f5a489cbb31f6797ac83e35",
"time": 1659943879,
"saplingTree": "013fd1d8c97e8c58705795887d6b2b95f241bc7b7f5dc7465ec7efcee9c481e45a0010015735fc78e731a7806d90cbe18cc8329954998f8e6ce0518c1237647b25c3c8540001d894fa58e452130e147ad6cdee0bb5435723fd90f405e76ade170723606aa43a01cf26d83db78c952aa29219071d11711b55efa4253bb1040987967a2922da684b0000012233753a4db3b97f44b3bb4d77034fd77c8e5c7938092724cb4a3bf3491bc1180160a5822bf1407657b82e959214634ec4c36a092bc1ab7615286058d8bc06152c01d878f6e29835fd77030d6c7395e85231d74c3a4d72659c8f301d65dfc2f3c51f018bd9ca6b4e00aac24d384b8d85b32481e0b4335ab3de5a0dea5c61aa32366153019aa3b71d9ce27ab88add19fda2e50caf313de20a04c6b72f5fbe1783980431730122c55fdffb446e39b73f1606907b2889d18b01ac818a0cbd4b2c661ad6a5a170000117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39",
"orchardTree": "01c86718d75ab663fe7a0b73f1fbaeb623205e632b50232b3e4e0aa642a2ee432f01b7ae647c07586477c57919f3d8511e5637d0be5bd79682abeefb4e2f4301cc141f000001c829fce51dc638a0b5027c8693ea0699cd65aa80b9e7fa36e61600a1a5de68300001e9507f2da9342ede27e7a548c176167e823b5447f7b1ffd13ffd3881724e2a22000156e5e2a74ac07ec0441aa4a435babf8536b4cf072a88b8f6e3be2f9c97c9fd0901dbcd13b6969c227ffb50565a2fa3659fd6691718d96b691e5d0aae952b8d4e1c0001b6fd291e9d6068bc24e99aefe49f8f29836ed1223deabc23871f1a1288f9240300016fc552915a0d5bc5c0c0cdf29453edf081d9a2de396535e6084770c38dcff838019518d88883e466a41ca67d6b986739fb2f601d77bb957398ed899de70b2a9f0801cd4871c1f545e7f5d844cc65fb00b8a162e316c3d1a435b00c435032b732c4280000000000000000000000000000000000"
}

View File

@ -1,11 +1,10 @@
package cash.z.ecc.android.sdk
import android.content.Context
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.exception.InitializerException
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.internal.SdkDispatchers
import cash.z.ecc.android.sdk.internal.ext.getCacheDirSuspend
import cash.z.ecc.android.sdk.internal.ext.getDatabasePathSuspend
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.jni.RustBackend
@ -16,7 +15,6 @@ import cash.z.ecc.android.sdk.tool.CheckpointTool
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.UnifiedFullViewingKey
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.File
/**
@ -380,9 +378,11 @@ class Initializer private constructor(
alias: String,
blockHeight: BlockHeight
): RustBackend {
val coordinator = DatabaseCoordinator.getInstance(context)
return RustBackend.init(
cacheDbPath(context, network, alias),
dataDbPath(context, network, alias),
coordinator.cacheDbFile(network, alias).absolutePath,
coordinator.dataDbFile(network, alias).absolutePath,
File(context.getCacheDirSuspend(), "params").absolutePath,
network,
blockHeight
@ -407,88 +407,7 @@ class Initializer private constructor(
appContext: Context,
network: ZcashNetwork,
alias: String
): Boolean {
val cacheDeleted = deleteDb(cacheDbPath(appContext, network, alias))
val dataDeleted = deleteDb(dataDbPath(appContext, network, alias))
return dataDeleted || cacheDeleted
}
//
// Path Helpers
//
/**
* Returns the path to the cache database that would correspond to the given alias.
*
* @param appContext the application context
* @param network the network associated with the data in the cache database.
* @param alias the alias to convert into a database path
*/
private suspend fun cacheDbPath(
appContext: Context,
network: ZcashNetwork,
alias: String
): String =
aliasToPath(appContext, network, alias, ZcashSdk.DB_CACHE_NAME)
/**
* Returns the path to the data database that would correspond to the given alias.
* @param appContext the application context
* * @param network the network associated with the data in the database.
* @param alias the alias to convert into a database path
*/
private suspend fun dataDbPath(
appContext: Context,
network: ZcashNetwork,
alias: String
): String =
aliasToPath(appContext, network, alias, ZcashSdk.DB_DATA_NAME)
private suspend fun aliasToPath(
appContext: Context,
network: ZcashNetwork,
alias: String,
dbFileName: String
): String {
val parentDir: String =
appContext.getDatabasePathSuspend("unused.db").parentFile?.absolutePath
?: throw InitializerException.DatabasePathException
val prefix = if (alias.endsWith('_')) alias else "${alias}_"
return File(parentDir, "$prefix${network.networkName}_$dbFileName").absolutePath
}
/**
* Delete a database and it's potential journal file at the given path.
*
* @param filePath the path of the db to erase.
* @return true when a file exists at the given path and was deleted.
*/
private suspend fun deleteDb(filePath: String): Boolean {
// just try the journal file. Doesn't matter if it's not there.
delete("$filePath-journal")
return delete(filePath)
}
/**
* Delete the file at the given path.
*
* @param filePath the path of the file to erase.
* @return true when a file exists at the given path and was deleted.
*/
private suspend fun delete(filePath: String): Boolean {
return File(filePath).let {
withContext(SdkDispatchers.DATABASE_IO) {
if (it.exists()) {
twig("Deleting ${it.name}!")
it.delete()
true
} else {
false
}
}
}
}
): Boolean = DatabaseCoordinator.getInstance(appContext).deleteDatabases(network, alias)
}
}

View File

@ -17,6 +17,7 @@ import cash.z.ecc.android.sdk.block.CompactBlockProcessor.State.Scanned
import cash.z.ecc.android.sdk.block.CompactBlockProcessor.State.Scanning
import cash.z.ecc.android.sdk.block.CompactBlockProcessor.State.Stopped
import cash.z.ecc.android.sdk.block.CompactBlockProcessor.State.Validating
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
import cash.z.ecc.android.sdk.db.entity.hasRawTransactionId
import cash.z.ecc.android.sdk.db.entity.isCancelled
@ -785,7 +786,11 @@ object DefaultSynchronizerFactory {
)
fun defaultBlockStore(initializer: Initializer): CompactBlockStore =
CompactBlockDbStore.new(initializer.context, initializer.network, initializer.rustBackend.pathCacheDb)
CompactBlockDbStore.new(
initializer.context,
initializer.network,
initializer.rustBackend.cacheDbFile
)
fun defaultService(initializer: Initializer): LightWalletService =
LightWalletGrpcService.new(initializer.context, initializer.lightWalletEndpoint)
@ -800,12 +805,23 @@ object DefaultSynchronizerFactory {
blockStore: CompactBlockStore
): CompactBlockDownloader = CompactBlockDownloader(service, blockStore)
fun defaultTxManager(
suspend fun defaultTxManager(
initializer: Initializer,
encoder: TransactionEncoder,
service: LightWalletService
): OutboundTransactionManager =
PersistentTransactionManager(initializer.context, encoder, service)
): OutboundTransactionManager {
val databaseFile = DatabaseCoordinator.getInstance(initializer.context).pendingTransactionsDbFile(
initializer.network,
initializer.alias
)
return PersistentTransactionManager(
initializer.context,
encoder,
service,
databaseFile
)
}
fun defaultProcessor(
initializer: Initializer,

View File

@ -603,7 +603,7 @@ class CompactBlockProcessor internal constructor(
return BlockProcessingResult.NoBlocksToProcess
}
Twig.sprout("validating")
twig("validating blocks in range $range in db: ${(rustBackend as RustBackend).pathCacheDb}")
twig("validating blocks in range $range in db: ${(rustBackend as RustBackend).cacheDbFile.absolutePath}")
val result = rustBackend.validateCombinedChain()
Twig.clip("validating")

View File

@ -0,0 +1,416 @@
package cash.z.ecc.android.sdk.db
import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.room.Room
import androidx.room.RoomDatabase
import cash.z.ecc.android.sdk.exception.InitializerException
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.internal.AndroidApiVersion
import cash.z.ecc.android.sdk.internal.Files
import cash.z.ecc.android.sdk.internal.LazyWithArgument
import cash.z.ecc.android.sdk.internal.NoBackupContextWrapper
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.getDatabasePathSuspend
import cash.z.ecc.android.sdk.internal.ext.renameToSuspend
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.ZcashNetwork
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.File
/**
* Wrapper class for various SDK databases operations. It always guaranties exclusive access to
* provided operations.
*
* @param context the application context
*/
@SuppressWarnings("TooManyFunctions")
internal class DatabaseCoordinator private constructor(context: Context) {
/*
* This implementation is thread-safe but is not multi-process safe.
*
* The mutex helps to ensure that two instances of the SDK being initialized in the same
* process do not have conflicts with regard to moving the databases around. However if an
* application decides to use multiple processes this could cause a problem during the one-time
* the database path migration.
*/
private val applicationContext = context.applicationContext
private val createFileMutex = Mutex()
private val deleteFileMutex = Mutex()
companion object {
@VisibleForTesting
internal const val DB_DATA_NAME_LEGACY = "Data.db" // $NON-NLS
const val DB_DATA_NAME = "data.sqlite3" // $NON-NLS
@VisibleForTesting
internal const val DB_CACHE_NAME_LEGACY = "Cache.db" // $NON-NLS
const val DB_CACHE_NAME = "cache.sqlite3" // $NON-NLS
@VisibleForTesting
internal const val DB_PENDING_TRANSACTIONS_NAME_LEGACY = "PendingTransactions.db" // $NON-NLS
const val DB_PENDING_TRANSACTIONS_NAME = "pending_transactions.sqlite3" // $NON-NLS
const val DATABASE_FILE_JOURNAL_SUFFIX = "journal" // $NON-NLS
const val DATABASE_FILE_WAL_SUFFIX = "wal" // $NON-NLS
@VisibleForTesting
internal const val ALIAS_LEGACY = "ZcashSdk" // $NON-NLS
private val lazy =
LazyWithArgument<Context, DatabaseCoordinator> { DatabaseCoordinator(it) }
fun getInstance(context: Context) = lazy.getInstance(context)
}
/**
* Returns the file of the Cache database that would correspond to the given alias
* and network attributes.
*
* @param network the network associated with the data in the cache database.
* @param alias the alias to convert into a database path
*
* @return the Cache database file
*/
internal suspend fun cacheDbFile(
network: ZcashNetwork,
alias: String
): File {
val dbLocationsPair = prepareDbFiles(
applicationContext,
network,
alias,
DB_CACHE_NAME_LEGACY,
DB_CACHE_NAME
)
createFileMutex.withLock {
return checkAndMoveDatabaseFiles(
dbLocationsPair.first,
dbLocationsPair.second
)
}
}
/**
* Returns the file of the Data database that would correspond to the given alias
* and network attributes.
*
* @param network the network associated with the data in the database.
* @param alias the alias to convert into a database path
*
* @return the Data database file
*/
internal suspend fun dataDbFile(
network: ZcashNetwork,
alias: String
): File {
val dbLocationsPair = prepareDbFiles(
applicationContext,
network,
alias,
DB_DATA_NAME_LEGACY,
DB_DATA_NAME
)
createFileMutex.withLock {
return checkAndMoveDatabaseFiles(
dbLocationsPair.first,
dbLocationsPair.second
)
}
}
/**
* Returns the file of the PendingTransaction database that would correspond to the given
* alias and network attributes. As the originally created file was called just
* PendingTransactions.db, we choose slightly different approach, but it also leads to
* original database files migration with additional renaming too.
*
* @param network the network associated with the data in the database.
* @param alias the alias to convert into a database path
*
* @return the PendingTransaction database file
*/
internal suspend fun pendingTransactionsDbFile(
network: ZcashNetwork,
alias: String
): File {
val legacyLocationDbFile = newDatabaseFilePointer(
null,
null,
DB_PENDING_TRANSACTIONS_NAME_LEGACY,
getDatabaseParentDir(applicationContext)
)
val preferredLocationDbFile = newDatabaseFilePointer(
network,
alias,
DB_PENDING_TRANSACTIONS_NAME,
Files.getZcashNoBackupSubdirectory(applicationContext)
)
createFileMutex.withLock {
return checkAndMoveDatabaseFiles(
legacyLocationDbFile,
preferredLocationDbFile
)
}
}
/**
* Function for common deletion of Data and Cache database files. It also checks and deletes
* additional journal and wal files, if they exist.
*
* @param network the network associated with the data in the database.
* @param alias the alias to convert into a database path
*/
internal suspend fun deleteDatabases(
network: ZcashNetwork,
alias: String
): Boolean {
deleteFileMutex.withLock {
val dataDeleted = deleteDatabase(
dataDbFile(network, alias)
)
val cacheDeleted = deleteDatabase(
cacheDbFile(network, alias)
)
return dataDeleted || cacheDeleted
}
}
/**
* This helper function prepares a legacy (i.e. previously created) database file, as well
* as the preferred (i.e. newly created) file for subsequent use (and eventually move).
*
* Note: the database file placed under the fake no_backup folder for devices with Android SDK
* level lower than 21.
*
* @param appContext the application context
* @param network the network associated with the data in the database.
* @param alias the alias to convert into a database path
* @param databaseName the name of the new database file
*/
private suspend fun prepareDbFiles(
appContext: Context,
network: ZcashNetwork,
alias: String,
databaseNameLegacy: String,
databaseName: String
): Pair<File, File> {
// Here we change the alias to be lowercase and underscored only if we work with the default
// Zcash alias, otherwise we need to keep an SDK caller alias the same to avoid the database
// files move breakage.
val aliasLegacy = if (ZcashSdk.DEFAULT_ALIAS == alias) {
ALIAS_LEGACY
} else {
alias
}
val legacyLocationDbFile = newDatabaseFilePointer(
network,
aliasLegacy,
databaseNameLegacy,
getDatabaseParentDir(appContext)
)
val preferredLocationDbFile = newDatabaseFilePointer(
network,
alias,
databaseName,
Files.getZcashNoBackupSubdirectory(appContext)
)
return Pair(
legacyLocationDbFile,
preferredLocationDbFile
)
}
/**
* This function do actual database file move or simply validate the file and return it.
* From the Android SDK level 21 it places database files into no_backup folder, as it does
* not allow automatic backup. On older APIs it places database files into databases folder,
* which allows automatic backup. It also copies database files between these two folders,
* if older folder usage is detected.
*
* @param legacyLocationDbFile the previously used file location
* @param preferredLocationDbFile the newly used file location
*/
private suspend fun checkAndMoveDatabaseFiles(
legacyLocationDbFile: File,
preferredLocationDbFile: File
): File {
var resultDbFile = preferredLocationDbFile
// check if the move wasn't already performed and if it's needed
if (!preferredLocationDbFile.existsSuspend() && legacyLocationDbFile.existsSuspend()) {
// We check the move operation result and fallback to the legacy file, if
// anything went wrong.
if (!moveDatabaseFile(legacyLocationDbFile, preferredLocationDbFile)) {
resultDbFile = legacyLocationDbFile
}
}
return resultDbFile
}
/**
* 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.
*
* @param legacyLocationDbFile the previously used file location (rename from)
* @param preferredLocationDbFile the newly used file location (rename to)
*/
private suspend fun moveDatabaseFile(
legacyLocationDbFile: File,
preferredLocationDbFile: File
): Boolean {
val filesToBeRenamed = mutableListOf<Pair<File, File>>().apply {
add(Pair(legacyLocationDbFile, preferredLocationDbFile))
}
// add journal database file, if exists
val journalSuffixedDbFile = File(
"${legacyLocationDbFile.absolutePath}-$DATABASE_FILE_JOURNAL_SUFFIX"
)
if (journalSuffixedDbFile.existsSuspend()) {
filesToBeRenamed.add(
Pair(
journalSuffixedDbFile,
File(
"${preferredLocationDbFile.absolutePath}-$DATABASE_FILE_JOURNAL_SUFFIX"
)
)
)
}
// add wal database file, if exists
val walSuffixedDbFile = File(
"${legacyLocationDbFile.absolutePath}-$DATABASE_FILE_WAL_SUFFIX"
)
if (walSuffixedDbFile.existsSuspend()) {
filesToBeRenamed.add(
Pair(
walSuffixedDbFile,
File(
"${preferredLocationDbFile.absolutePath}-$DATABASE_FILE_WAL_SUFFIX"
)
)
)
}
return runCatching {
return@runCatching filesToBeRenamed.all {
it.first.renameToSuspend(it.second)
}
}.onFailure {
twig("Failed while renaming database files with: $it")
}.getOrDefault(false)
}
/**
* This function returns previously used database folder path (i.e. databases). The databases
* folder is deprecated now, as it allows automatic data backup, which is not permitted for
* our database files.
*
* @param appContext the application context
*/
private suspend fun getDatabaseParentDir(appContext: Context): File {
return appContext.getDatabasePathSuspend("unused.db").parentFile
?: throw InitializerException.DatabasePathException
}
/**
* Simple helper function, which prepares a database file object by input parameters. It does
* not create the file, just determines the file path.
*
* @param network the network associated with the data in the database.
* @param alias the alias to convert into a database path
* @param dbFileName the name of the new database file
* @param parentDir the name of the parent directory, in which the file should be placed
*/
private fun newDatabaseFilePointer(
network: ZcashNetwork?,
alias: String?,
dbFileName: String,
parentDir: File
): File {
val aliasPrefix = if (alias == null) {
""
} else if (alias.endsWith('_')) {
alias
} else {
"${alias}_"
}
val networkPrefix = network?.networkName ?: ""
return if (aliasPrefix.isNotEmpty()) {
File(parentDir, "$aliasPrefix${networkPrefix}_$dbFileName")
} else {
File(parentDir, dbFileName)
}
}
/**
* Delete a database and its potential journal and wal file at the given path.
*
* The rollback journal (or newer wal) file is a temporary file used to implement atomic commit
* and rollback capabilities in SQLite.
*
* @param file the path of the db to erase.
* @return true when a file exists at the given path and was deleted.
*/
private suspend fun deleteDatabase(file: File): Boolean {
// Just try the journal and wal files too. Doesn't matter if they're not there.
File("${file.absolutePath}-$DATABASE_FILE_JOURNAL_SUFFIX").deleteSuspend()
File("${file.absolutePath}-$DATABASE_FILE_WAL_SUFFIX").deleteSuspend()
return file.deleteSuspend()
}
}
/**
* The purpose of this function is to provide Room.Builder via a static Room.databaseBuilder with
* an injection of our NoBackupContextWrapper to override the behavior of getDatabasePath() for
* Android SDK level 27 and higher and regular Context class for the Android SDK level 26 and lower.
*
* Note: ideally we'd make this extension function or override the Room.databaseBuilder function,
* but it's not possible, as it's a static function on Room class, which does not allow its
* instantiation.
*
* @param context
* @param klass The database class.
* @param databaseFile The database file.
* @return A {@code RoomDatabaseBuilder<T>} which you can use to create the database.
*/
internal fun <T : RoomDatabase?> commonDatabaseBuilder(
context: Context,
klass: Class<T>,
databaseFile: File
): RoomDatabase.Builder<T> {
return if (AndroidApiVersion.isAtLeastO_MR1) {
Room.databaseBuilder(
NoBackupContextWrapper(
context,
databaseFile.parentFile ?: throw InitializerException.DatabasePathException
),
klass,
databaseFile.name
)
} else {
Room.databaseBuilder(
context,
klass,
databaseFile.absolutePath
)
}
}

View File

@ -18,18 +18,18 @@ object ZcashSdk {
/**
* The theoretical maximum number of blocks in a reorg, due to other bottlenecks in the protocol design.
*/
val MAX_REORG_SIZE = 100
const val MAX_REORG_SIZE = 100
/**
* The maximum length of a memo.
*/
val MAX_MEMO_SIZE = 512
const val MAX_MEMO_SIZE = 512
/**
* The amount of blocks ahead of the current height where new transactions are set to expire. This value is controlled
* by the rust backend but it is helpful to know what it is set to and should be kept in sync.
*/
val EXPIRY_OFFSET = 20
const val EXPIRY_OFFSET = 20
/**
* Default size of batches of blocks to request from the compact block service.
@ -43,60 +43,57 @@ object ZcashSdk {
* Default size of batches of blocks to scan via librustzcash. The smaller this number the more granular information
* can be provided about scan state. Unfortunately, it may also lead to a lot of overhead during scanning.
*/
val SCAN_BATCH_SIZE = 150
const val SCAN_BATCH_SIZE = 150
/**
* Default amount of time, in milliseconds, to poll for new blocks. Typically, this should be about half the average
* block time.
*/
val POLL_INTERVAL = 20_000L
const val POLL_INTERVAL = 20_000L
/**
* Estimate of the time between blocks.
*/
val BLOCK_INTERVAL_MILLIS = 75_000L
const val BLOCK_INTERVAL_MILLIS = 75_000L
/**
* Default attempts at retrying.
*/
val RETRIES = 5
const val RETRIES = 5
/**
* The default maximum amount of time to wait during retry backoff intervals. Failed loops will never wait longer than
* this before retyring.
*/
val MAX_BACKOFF_INTERVAL = 600_000L
const val MAX_BACKOFF_INTERVAL = 600_000L
/**
* Default number of blocks to rewind when a chain reorg is detected. This should be large enough to recover from the
* reorg but smaller than the theoretical max reorg size of 100.
*/
val REWIND_DISTANCE = 10
val DB_DATA_NAME = "Data.db"
val DB_CACHE_NAME = "Cache.db"
const val REWIND_DISTANCE = 10
/**
* File name for the sappling spend params
*/
val SPEND_PARAM_FILE_NAME = "sapling-spend.params"
const val SPEND_PARAM_FILE_NAME = "sapling-spend.params"
/**
* File name for the sapling output params
*/
val OUTPUT_PARAM_FILE_NAME = "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.
*/
val CLOUD_PARAM_DIR_URL = "https://z.cash/downloads/"
const val CLOUD_PARAM_DIR_URL = "https://z.cash/downloads/"
/**
* The default memo to use when shielding transparent funds.
*/
val DEFAULT_SHIELD_FUNDS_MEMO_PREFIX = "shielding:"
const val DEFAULT_SHIELD_FUNDS_MEMO_PREFIX = "shielding:"
val DEFAULT_ALIAS: String = "ZcashSdk"
const val DEFAULT_ALIAS: String = "zcash_sdk"
}

View File

@ -0,0 +1,57 @@
package cash.z.ecc.android.sdk.internal
import android.os.Build
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.annotation.IntRange
internal object AndroidApiVersion {
/**
* @param sdk SDK version number to test against the current environment.
* @return `true` if [android.os.Build.VERSION.SDK_INT] is greater than or equal to
* [sdk].
*/
@ChecksSdkIntAtLeast(parameter = 0)
fun isAtLeast(@IntRange(from = Build.VERSION_CODES.BASE.toLong()) sdk: Int): Boolean {
return Build.VERSION.SDK_INT >= sdk
}
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.LOLLIPOP)
val isAtLeastL = isAtLeast(Build.VERSION_CODES.LOLLIPOP)
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.M)
val isAtLeastM = isAtLeast(Build.VERSION_CODES.M)
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N)
val isAtLeastN = isAtLeast(Build.VERSION_CODES.N)
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
val isAtLeastO = isAtLeast(Build.VERSION_CODES.O)
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O_MR1)
val isAtLeastO_MR1 = isAtLeast(Build.VERSION_CODES.O_MR1)
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P)
val isAtLeastP = isAtLeast(Build.VERSION_CODES.P)
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.Q)
val isAtLeastQ = isAtLeast(Build.VERSION_CODES.Q)
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R)
val isAtLeastR = isAtLeast(Build.VERSION_CODES.R)
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S)
val isAtLeastS = isAtLeast(Build.VERSION_CODES.S)
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
val isAtLeastT = isAtLeast(Build.VERSION_CODES.TIRAMISU)
/**
* This property indicates a preview version of the current device Android SDK. It works only on
* Android SDK 23 and later, on the previous SDK versions its value is always false.
*/
val isPreview = if (isAtLeastM) {
0 != Build.VERSION.PREVIEW_SDK_INT
} else {
false
}
}

View File

@ -0,0 +1,47 @@
package cash.z.ecc.android.sdk.internal
import android.content.Context
import cash.z.ecc.android.sdk.internal.ext.canWriteSuspend
import cash.z.ecc.android.sdk.internal.ext.existsSuspend
import cash.z.ecc.android.sdk.internal.ext.getNoBackupFilesDirCompat
import cash.z.ecc.android.sdk.internal.ext.mkdirsSuspend
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.File
/**
* Because the filesystem is a shared resource, this declares the filenames that the SDK is using
* in one centralized place.
*/
internal object Files {
/**
* Subdirectory under the Android "no backup" directory which is owned by the SDK.
*/
const val NO_BACKUP_SUBDIRECTORY = "co.electricoin.zcash" // $NON-NLS
private val accessMutex = Mutex()
/**
* @return Subdirectory of the "no_backup" directory that is owned by the SDK. The returned
* directory will exist when this method returns.
*
* As we use a suspend version of the file operations here, we protect the operations with mutex
* to prevent multiple threads to invoke the function at the same time.
*/
suspend fun getZcashNoBackupSubdirectory(context: Context): File {
val dir = File(context.getNoBackupFilesDirCompat(), NO_BACKUP_SUBDIRECTORY)
accessMutex.withLock {
if (!dir.existsSuspend()) {
if (!dir.mkdirsSuspend()) {
error("${dir.absolutePath} directory does not exist and could not be created")
}
}
if (!dir.canWriteSuspend()) {
error("${dir.absolutePath} directory is not writable")
}
}
return dir
}
}

View File

@ -0,0 +1,33 @@
package cash.z.ecc.android.sdk.internal
/**
* Implements a lazy singleton pattern with an input argument.
*
* This class is thread-safe.
*/
internal class LazyWithArgument<in Input, out Output>(private val deferredCreator: ((Input) -> Output)) {
@Volatile
private var singletonInstance: Output? = null
private val intrinsicLock = Any()
fun getInstance(input: Input): Output {
/*
* Double-checked idiom for lazy initialization, Effective Java 2nd edition page 283.
*/
var localSingletonInstance = singletonInstance
if (null == localSingletonInstance) {
synchronized(intrinsicLock) {
localSingletonInstance = singletonInstance
if (null == localSingletonInstance) {
localSingletonInstance = deferredCreator(input)
singletonInstance = localSingletonInstance
}
}
}
return localSingletonInstance!!
}
}

View File

@ -0,0 +1,47 @@
package cash.z.ecc.android.sdk.internal
import android.content.Context
import android.content.ContextWrapper
import android.os.Build
import androidx.annotation.RequiresApi
import java.io.File
/**
* A context class wrapper used for building our database classes. The advantage of this implementation
* is that we can control actions run on this context class. This is supposed to be used only for
* Android SDK level 27 and higher. The Room's underlying SQLite has a different implementation of
* SQLiteOpenHelper#getDatabaseLocked() and possibly other methods for Android SDK level 26 and lower.
* Which at the end call ContextImpl#openOrCreateDatabase(), instead of the overridden getDatabasePath(),
* and thus is not suitable for this custom context wrapper class.
*
* @param context
* @param parentDir The directory in which is the database file placed.
* @return Wrapped context class.
*/
@RequiresApi(Build.VERSION_CODES.O_MR1)
internal class NoBackupContextWrapper(
context: Context,
private val parentDir: File
) : ContextWrapper(context.applicationContext) {
/**
* Overriding this function gives us ability to control the result database file location.
*
* @param name Database file name.
* @return File located under no_backup/co.electricoin.zcash directory.
*/
override fun getDatabasePath(name: String): File {
twig("Database: $name in directory: ${parentDir.absolutePath}")
return File(parentDir, name)
}
override fun getApplicationContext(): Context {
// Prevent breakout
return this
}
override fun getBaseContext(): Context {
// Prevent breakout
return this
}
}

View File

@ -2,6 +2,9 @@ package cash.z.ecc.android.sdk.internal
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.existsSuspend
import cash.z.ecc.android.sdk.internal.ext.mkdirsSuspend
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
@ -133,7 +136,3 @@ class SaplingParamTool {
}
}
}
suspend fun File.existsSuspend() = withContext(Dispatchers.IO) { exists() }
suspend fun File.mkdirsSuspend() = withContext(Dispatchers.IO) { mkdirs() }
suspend fun File.deleteRecursivelySuspend() = withContext(Dispatchers.IO) { deleteRecursively() }

View File

@ -1,8 +1,8 @@
package cash.z.ecc.android.sdk.internal.block
import android.content.Context
import androidx.room.Room
import androidx.room.RoomDatabase
import cash.z.ecc.android.sdk.db.commonDatabaseBuilder
import cash.z.ecc.android.sdk.db.entity.CompactBlockEntity
import cash.z.ecc.android.sdk.internal.SdkDispatchers
import cash.z.ecc.android.sdk.internal.SdkExecutors
@ -11,6 +11,7 @@ import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.wallet.sdk.rpc.CompactFormats
import kotlinx.coroutines.withContext
import java.io.File
/**
* An implementation of CompactBlockStore that persists information to a database in the given
@ -45,23 +46,27 @@ class CompactBlockDbStore private constructor(
companion object {
/**
* @param appContext the application context. This is used for creating the database.
* @property dbPath the absolute path to the database.
* @property databaseFile the database file.
*/
fun new(
appContext: Context,
zcashNetwork: ZcashNetwork,
dbPath: String
databaseFile: File
): CompactBlockDbStore {
val cacheDb = createCompactBlockCacheDb(appContext.applicationContext, dbPath)
val cacheDb = createCompactBlockCacheDb(appContext.applicationContext, databaseFile)
return CompactBlockDbStore(zcashNetwork, cacheDb)
}
private fun createCompactBlockCacheDb(
appContext: Context,
dbPath: String
databaseFile: File
): CompactBlockDb {
return Room.databaseBuilder(appContext, CompactBlockDb::class.java, dbPath)
return commonDatabaseBuilder(
appContext,
CompactBlockDb::class.java,
databaseFile
)
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
// this is a simple cache of blocks. destroying the db should be benign
.fallbackToDestructiveMigration()

View File

@ -1,11 +1,51 @@
package cash.z.ecc.android.sdk.internal.ext
import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import cash.z.ecc.android.sdk.internal.AndroidApiVersion
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
suspend fun Context.getDatabasePathSuspend(fileName: String) =
internal suspend fun Context.getDatabasePathSuspend(fileName: String) =
withContext(Dispatchers.IO) { getDatabasePath(fileName) }
suspend fun Context.getCacheDirSuspend() =
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
internal suspend fun Context.getNoBackupFilesDirSuspend() =
withContext(Dispatchers.IO) { noBackupFilesDir }
internal suspend fun Context.getCacheDirSuspend() =
withContext(Dispatchers.IO) { cacheDir }
internal suspend fun Context.getFilesDirSuspend() =
withContext(Dispatchers.IO) { filesDir }
internal suspend fun Context.getDataDirCompatSuspend() =
withContext(Dispatchers.IO) { ContextCompat.getDataDir(this@getDataDirCompatSuspend) }
private const val FAKE_NO_BACKUP_FOLDER = "no_backup" // $NON-NLS
/**
* @return Path to the no backup folder, with fallback behavior for API < 21.
*/
internal suspend fun Context.getNoBackupFilesDirCompat(): File {
val dir = if (AndroidApiVersion.isAtLeastL) {
getNoBackupFilesDirSuspend()
} else {
File(getDataDirCompatSuspend(), FAKE_NO_BACKUP_FOLDER)
}
if (!dir.existsSuspend()) {
if (!dir.mkdirsSuspend()) {
error("no_backup directory does not exist and could not be created")
}
}
if (!dir.canWriteSuspend()) {
error("${dir.absolutePath} directory is not writable")
}
return dir
}

View File

@ -6,4 +6,14 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
suspend fun File.deleteSuspend() = withContext(Dispatchers.IO) { delete() }
internal suspend fun File.deleteSuspend() = withContext(Dispatchers.IO) { delete() }
internal suspend fun File.existsSuspend() = withContext(Dispatchers.IO) { exists() }
internal suspend fun File.mkdirsSuspend() = withContext(Dispatchers.IO) { mkdirs() }
internal suspend fun File.canWriteSuspend() = withContext(Dispatchers.IO) { canWrite() }
internal suspend fun File.renameToSuspend(dest: File) = withContext(Dispatchers.IO) { renameTo(dest) }
suspend fun File.deleteRecursivelySuspend() = withContext(Dispatchers.IO) { deleteRecursively() }

View File

@ -2,8 +2,8 @@ package cash.z.ecc.android.sdk.internal.transaction
import android.content.Context
import androidx.paging.PagedList
import androidx.room.Room
import androidx.room.RoomDatabase
import cash.z.ecc.android.sdk.db.commonDatabaseBuilder
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.internal.SdkDispatchers
@ -21,6 +21,7 @@ import cash.z.ecc.android.sdk.type.UnifiedFullViewingKey
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import java.io.File
/**
* Example of a repository that leverages the Room paging library to return a [PagedList] of
@ -123,7 +124,7 @@ internal class PagedTransactionRepository private constructor(
): PagedTransactionRepository {
initMissingDatabases(rustBackend, birthday, viewingKeys)
val db = buildDatabase(appContext.applicationContext, rustBackend.pathDataDb)
val db = buildDatabase(appContext.applicationContext, rustBackend.dataDbFile)
applyKeyMigrations(rustBackend, overwriteVks, viewingKeys)
return PagedTransactionRepository(zcashNetwork, db, pageSize)
@ -132,9 +133,13 @@ internal class PagedTransactionRepository private constructor(
/**
* Build the database and apply migrations.
*/
private suspend fun buildDatabase(context: Context, databasePath: String): DerivedDataDb {
private suspend fun buildDatabase(context: Context, databaseFile: File): DerivedDataDb {
twig("Building dataDb and applying migrations")
return Room.databaseBuilder(context, DerivedDataDb::class.java, databasePath)
return commonDatabaseBuilder(
context,
DerivedDataDb::class.java,
databaseFile
)
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
.setQueryExecutor(SdkExecutors.DATABASE_IO)
.setTransactionExecutor(SdkExecutors.DATABASE_IO)
@ -146,6 +151,7 @@ internal class PagedTransactionRepository private constructor(
.build().also {
// TODO: document why we do this. My guess is to catch database issues early or to trigger migrations--I forget why it was added but there was a good reason?
withContext(SdkDispatchers.DATABASE_IO) {
// TODO [#649]: https://github.com/zcash/zcash-android-wallet-sdk/issues/649
it.openHelper.writableDatabase.beginTransaction()
it.openHelper.writableDatabase.endTransaction()
}
@ -173,7 +179,7 @@ internal class PagedTransactionRepository private constructor(
private suspend fun maybeCreateDataDb(rustBackend: RustBackend) {
tryWarn("Warning: did not create dataDb. It probably already exists.") {
rustBackend.initDataDb()
twig("Initialized wallet for first run file: ${rustBackend.pathDataDb}")
twig("Initialized wallet for first run file: ${rustBackend.dataDbFile}")
}
}
@ -192,7 +198,7 @@ internal class PagedTransactionRepository private constructor(
rustBackend.initBlocksTable(checkpoint)
twig("seeded the database with sapling tree at height ${checkpoint.height}")
}
twig("database file: ${rustBackend.pathDataDb}")
twig("database file: ${rustBackend.dataDbFile}")
}
/**

View File

@ -1,8 +1,8 @@
package cash.z.ecc.android.sdk.internal.transaction
import android.content.Context
import androidx.room.Room
import androidx.room.RoomDatabase
import cash.z.ecc.android.sdk.db.commonDatabaseBuilder
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
import cash.z.ecc.android.sdk.db.entity.PendingTransactionEntity
import cash.z.ecc.android.sdk.db.entity.isCancelled
@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import kotlin.math.max
@ -56,12 +57,12 @@ class PersistentTransactionManager(
appContext: Context,
encoder: TransactionEncoder,
service: LightWalletService,
dataDbName: String = "PendingTransactions.db"
databaseFile: File
) : this(
Room.databaseBuilder(
commonDatabaseBuilder(
appContext,
PendingTransactionDb::class.java,
dataDbName
databaseFile
).setJournalMode(RoomDatabase.JournalMode.TRUNCATE).build(),
encoder,
service

View File

@ -23,19 +23,19 @@ import java.io.File
internal class RustBackend private constructor(
override val network: ZcashNetwork,
val birthdayHeight: BlockHeight,
val pathDataDb: String,
val pathCacheDb: String,
val dataDbFile: File,
val cacheDbFile: File,
val pathParamsDir: String
) : RustBackendWelding {
suspend fun clear(clearCacheDb: Boolean = true, clearDataDb: Boolean = true) {
if (clearCacheDb) {
twig("Deleting the cache database!")
File(pathCacheDb).deleteSuspend()
cacheDbFile.deleteSuspend()
}
if (clearDataDb) {
twig("Deleting the data database!")
File(pathDataDb).deleteSuspend()
dataDbFile.deleteSuspend()
}
}
@ -45,7 +45,7 @@ internal class RustBackend private constructor(
override suspend fun initDataDb() = withContext(SdkDispatchers.DATABASE_IO) {
initDataDb(
pathDataDb,
dataDbFile.absolutePath,
networkId = network.id
)
}
@ -55,7 +55,7 @@ internal class RustBackend private constructor(
return withContext(SdkDispatchers.DATABASE_IO) {
initAccountsTableWithKeys(
pathDataDb,
dataDbFile.absolutePath,
ufvks,
networkId = network.id
)
@ -76,7 +76,7 @@ internal class RustBackend private constructor(
): Boolean {
return withContext(SdkDispatchers.DATABASE_IO) {
initBlocksTable(
pathDataDb,
dataDbFile.absolutePath,
checkpoint.height.value,
checkpoint.hash,
checkpoint.epochSeconds,
@ -89,7 +89,7 @@ internal class RustBackend private constructor(
override suspend fun getShieldedAddress(account: Int) =
withContext(SdkDispatchers.DATABASE_IO) {
getShieldedAddress(
pathDataDb,
dataDbFile.absolutePath,
account,
networkId = network.id
)
@ -102,7 +102,7 @@ internal class RustBackend private constructor(
override suspend fun getBalance(account: Int): Zatoshi {
val longValue = withContext(SdkDispatchers.DATABASE_IO) {
getBalance(
pathDataDb,
dataDbFile.absolutePath,
account,
networkId = network.id
)
@ -114,7 +114,7 @@ internal class RustBackend private constructor(
override suspend fun getVerifiedBalance(account: Int): Zatoshi {
val longValue = withContext(SdkDispatchers.DATABASE_IO) {
getVerifiedBalance(
pathDataDb,
dataDbFile.absolutePath,
account,
networkId = network.id
)
@ -126,7 +126,7 @@ internal class RustBackend private constructor(
override suspend fun getReceivedMemoAsUtf8(idNote: Long) =
withContext(SdkDispatchers.DATABASE_IO) {
getReceivedMemoAsUtf8(
pathDataDb,
dataDbFile.absolutePath,
idNote,
networkId = network.id
)
@ -134,7 +134,7 @@ internal class RustBackend private constructor(
override suspend fun getSentMemoAsUtf8(idNote: Long) = withContext(SdkDispatchers.DATABASE_IO) {
getSentMemoAsUtf8(
pathDataDb,
dataDbFile.absolutePath,
idNote,
networkId = network.id
)
@ -142,8 +142,8 @@ internal class RustBackend private constructor(
override suspend fun validateCombinedChain() = withContext(SdkDispatchers.DATABASE_IO) {
val validationResult = validateCombinedChain(
pathCacheDb,
pathDataDb,
cacheDbFile.absolutePath,
dataDbFile.absolutePath,
networkId = network.id
)
@ -159,7 +159,7 @@ internal class RustBackend private constructor(
BlockHeight.new(
network,
getNearestRewindHeight(
pathDataDb,
dataDbFile.absolutePath,
height.value,
networkId = network.id
)
@ -174,7 +174,7 @@ internal class RustBackend private constructor(
override suspend fun rewindToHeight(height: BlockHeight) =
withContext(SdkDispatchers.DATABASE_IO) {
rewindToHeight(
pathDataDb,
dataDbFile.absolutePath,
height.value,
networkId = network.id
)
@ -184,8 +184,8 @@ internal class RustBackend private constructor(
return if (limit > 0) {
withContext(SdkDispatchers.DATABASE_IO) {
scanBlockBatch(
pathCacheDb,
pathDataDb,
cacheDbFile.absolutePath,
dataDbFile.absolutePath,
limit,
networkId = network.id
)
@ -193,8 +193,8 @@ internal class RustBackend private constructor(
} else {
withContext(SdkDispatchers.DATABASE_IO) {
scanBlocks(
pathCacheDb,
pathDataDb,
cacheDbFile.absolutePath,
dataDbFile.absolutePath,
networkId = network.id
)
}
@ -204,7 +204,7 @@ internal class RustBackend private constructor(
override suspend fun decryptAndStoreTransaction(tx: ByteArray) =
withContext(SdkDispatchers.DATABASE_IO) {
decryptAndStoreTransaction(
pathDataDb,
dataDbFile.absolutePath,
tx,
networkId = network.id
)
@ -219,7 +219,7 @@ internal class RustBackend private constructor(
memo: ByteArray?
): Long = withContext(SdkDispatchers.DATABASE_IO) {
createToAddress(
pathDataDb,
dataDbFile.absolutePath,
consensusBranchId,
account,
extsk,
@ -237,10 +237,10 @@ internal class RustBackend private constructor(
xprv: String,
memo: ByteArray?
): Long {
twig("TMP: shieldToAddress with db path: $pathDataDb, ${memo?.size}")
twig("TMP: shieldToAddress with db path: $dataDbFile, ${memo?.size}")
return withContext(SdkDispatchers.DATABASE_IO) {
shieldToAddress(
pathDataDb,
dataDbFile.absolutePath,
0,
extsk,
xprv,
@ -261,7 +261,7 @@ internal class RustBackend private constructor(
height: BlockHeight
): Boolean = withContext(SdkDispatchers.DATABASE_IO) {
putUtxo(
pathDataDb,
dataDbFile.absolutePath,
tAddress,
txId,
index,
@ -277,7 +277,7 @@ internal class RustBackend private constructor(
aboveHeightInclusive: BlockHeight
): Boolean = withContext(SdkDispatchers.DATABASE_IO) {
clearUtxos(
pathDataDb,
dataDbFile.absolutePath,
tAddress,
// The Kotlin API is inclusive, but the Rust API is exclusive.
// This can create invalid BlockHeights if the height is saplingActivationHeight.
@ -289,14 +289,14 @@ internal class RustBackend private constructor(
override suspend fun getDownloadedUtxoBalance(address: String): WalletBalance {
val verified = withContext(SdkDispatchers.DATABASE_IO) {
getVerifiedTransparentBalance(
pathDataDb,
dataDbFile.absolutePath,
address,
networkId = network.id
)
}
val total = withContext(SdkDispatchers.DATABASE_IO) {
getTotalTransparentBalance(
pathDataDb,
dataDbFile.absolutePath,
address,
networkId = network.id
)
@ -357,8 +357,8 @@ internal class RustBackend private constructor(
return RustBackend(
zcashNetwork,
birthdayHeight,
pathDataDb = dataDbPath,
pathCacheDb = cacheDbPath,
dataDbFile = File(dataDbPath),
cacheDbFile = File(cacheDbPath),
pathParamsDir = paramsPath
)
}

View File

@ -23,7 +23,6 @@ pluginManagement {
val gradleVersionsPluginVersion = extra["GRADLE_VERSIONS_PLUGIN_VERSION"].toString()
val kotlinVersion = extra["KOTLIN_VERSION"].toString()
val kspVersion = extra["KSP_VERSION"].toString()
val mavenPublishPluginVersion = extra["MAVEN_PUBLISH_GRADLE_PLUGIN"].toString()
val protobufVersion = extra["PROTOBUF_GRADLE_PLUGIN_VERSION"].toString()
id("com.android.application") version (androidGradlePluginVersion) apply (false)
@ -32,7 +31,6 @@ pluginManagement {
id("com.google.devtools.ksp") version(kspVersion) apply (false)
id("com.google.protobuf") version (protobufVersion) apply (false)
id("com.osacky.fulladle") version (fulladleVersion) apply (false)
id("com.vanniktech.maven.publish.base") version(mavenPublishPluginVersion) apply (false)
id("io.gitlab.arturbosch.detekt") version (detektVersion) apply (false)
id("org.jetbrains.dokka") version (dokkaVersion) apply (false)
id("org.jetbrains.kotlin.android") version (kotlinVersion) apply (false)
@ -42,8 +40,10 @@ pluginManagement {
}
dependencyResolutionManagement {
@Suppress("UnstableApiUsage")
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
@Suppress("UnstableApiUsage")
repositories {
val isRepoRestrictionEnabled = true
@ -90,7 +90,6 @@ dependencyResolutionManagement {
val junitVersion = extra["JUNIT_VERSION"].toString()
val kotlinVersion = extra["KOTLIN_VERSION"].toString()
val kotlinxCoroutinesVersion = extra["KOTLINX_COROUTINES_VERSION"].toString()
val mavenPublishGradlePluginVersion = extra["MAVEN_PUBLISH_GRADLE_PLUGIN"].toString()
val mockitoKotlinVersion = extra["MOCKITO_KOTLIN_VERSION"].toString()
val mockitoVersion = extra["MOCKITO_VERSION"].toString()
val okhttpVersion = extra["OKHTTP_VERSION"].toString()
@ -110,7 +109,6 @@ dependencyResolutionManagement {
// Gradle plugins
library("gradle-plugin-android", "com.android.tools.build:gradle:$androidGradlePluginVersion")
library("gradle-plugin-navigation", "androidx.navigation:navigation-safe-args-gradle-plugin:$androidxNavigationVersion")
library("gradle-plugin-publish", "com.vanniktech:gradle-maven-publish-plugin:$mavenPublishGradlePluginVersion")
library("gradle-plugin-rust", "org.mozilla.rust-android-gradle:plugin:$rustGradlePluginVersion")
// Special cases used by the grpc gradle plugin

View File

@ -7,6 +7,7 @@
<ID>ComplexMethod:SendFragment.kt$SendFragment$private fun onPendingTxUpdated(pendingTransaction: PendingTransaction?)</ID>
<ID>ComplexMethod:Transactions.kt$ConfirmedTransaction$override fun equals(other: Any?): Boolean</ID>
<ID>ComplexMethod:Transactions.kt$PendingTransactionEntity$override fun equals(other: Any?): Boolean</ID>
<ID>DestructuringDeclarationWithTooManyEntries:TestnetIntegrationTest.kt$TestnetIntegrationTest$val (height, hash, time, tree) = runBlocking { CheckpointTool.loadNearest( context, synchronizer.network, saplingActivation + 1 ) }</ID>
<ID>EmptyCatchBlock:SdkSynchronizer.kt$SdkSynchronizer${ }</ID>
<ID>EmptyFunctionBlock:MainActivity.kt$MainActivity${ }</ID>
<ID>EmptyFunctionBlock:Placeholders.kt$SampleSpendingKeyProvider${ }</ID>
@ -48,11 +49,9 @@
<ID>ImplicitDefaultLocale:BlockExt.kt$String.format("%02x", b)</ID>
<ID>ImplicitDefaultLocale:BlockExt.kt$String.format("%02x", this[i--])</ID>
<ID>ImplicitDefaultLocale:Twig.kt$TroubleshootingTwig.Companion$String.format("$tag %1\$tD %1\$tI:%1\$tM:%1\$tS.%1\$tN", System.currentTimeMillis())</ID>
<ID>InvalidPackageDeclaration:OpenForTesting.kt$package cash.z.ecc.android.sdk.annotation</ID>
<ID>LargeClass:CompactBlockProcessor.kt$CompactBlockProcessor</ID>
<ID>LongMethod:SdkSynchronizer.kt$SdkSynchronizer$private suspend fun refreshPendingTransactions()</ID>
<ID>LongParameterList:Initializer.kt$Initializer$( val context: Context, internal val rustBackend: RustBackend, val network: ZcashNetwork, val alias: String, val host: String, val port: Int, val viewingKeys: List&lt;UnifiedViewingKey>, val overwriteVks: Boolean, internal val checkpoint: Checkpoint )</ID>
<ID>LongParameterList:Initializer.kt$Initializer.Config$( seed: ByteArray, birthday: BlockHeight?, network: ZcashNetwork, host: String = network.defaultHost, port: Int = network.defaultPort, alias: String = ZcashSdk.DEFAULT_ALIAS )</ID>
<ID>LongParameterList:Initializer.kt$Initializer.Config$( viewingKey: UnifiedViewingKey, birthday: BlockHeight?, network: ZcashNetwork, host: String = network.defaultHost, port: Int = network.defaultPort, alias: String = ZcashSdk.DEFAULT_ALIAS )</ID>
<ID>LongParameterList:PagedTransactionRepository.kt$PagedTransactionRepository.Companion$( appContext: Context, zcashNetwork: ZcashNetwork, pageSize: Int = 10, rustBackend: RustBackend, birthday: Checkpoint, viewingKeys: List&lt;UnifiedViewingKey>, overwriteVks: Boolean = false )</ID>
<ID>LongParameterList:RustBackend.kt$RustBackend.Companion$( dbDataPath: String, account: Int, extsk: String, tsk: String, memo: ByteArray, spendParamsPath: String, outputParamsPath: String, networkId: Int )</ID>
<ID>LongParameterList:RustBackend.kt$RustBackend.Companion$( dbDataPath: String, consensusBranchId: Long, account: Int, extsk: String, to: String, value: Long, memo: ByteArray, spendParamsPath: String, outputParamsPath: String, networkId: Int )</ID>
@ -85,7 +84,6 @@
<ID>MagicNumber:DemoConstants.kt$DemoConstants$0.000018</ID>
<ID>MagicNumber:DemoConstants.kt$DemoConstants$0.00017</ID>
<ID>MagicNumber:DemoConstants.kt$DemoConstants$1075590</ID>
<ID>MagicNumber:DemoConstants.kt$DemoConstants$968085</ID>
<ID>MagicNumber:DerivedDataDb.kt$DerivedDataDb.Companion.&lt;no name provided>$3</ID>
<ID>MagicNumber:DerivedDataDb.kt$DerivedDataDb.Companion.&lt;no name provided>$4</ID>
<ID>MagicNumber:DerivedDataDb.kt$DerivedDataDb.Companion.&lt;no name provided>$5</ID>
@ -116,10 +114,6 @@
<ID>MagicNumber:UtxoViewHolder.kt$UtxoViewHolder$1000L</ID>
<ID>MagicNumber:WalletService.kt$1000L</ID>
<ID>MagicNumber:WalletService.kt$4.0</ID>
<ID>MagicNumber:ZcashNetwork.kt$ZcashNetwork.Mainnet$419_200</ID>
<ID>MagicNumber:ZcashNetwork.kt$ZcashNetwork.Mainnet$9067</ID>
<ID>MagicNumber:ZcashNetwork.kt$ZcashNetwork.Testnet$280_000</ID>
<ID>MagicNumber:ZcashNetwork.kt$ZcashNetwork.Testnet$9067</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$10</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$100</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$150</ID>
@ -130,16 +124,16 @@
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$512</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$600_000L</ID>
<ID>MagicNumber:ZcashSdk.kt$ZcashSdk$75_000L</ID>
<ID>MagicNumber:build.gradle.kts$16</ID>
<ID>MagicNumber:build.gradle.kts$21</ID>
<ID>MatchingDeclarationName:CurrencyFormatter.kt$Conversions</ID>
<ID>MaxLineLength:BatchMetrics.kt$BatchMetrics$class</ID>
<ID>MaxLineLength:BlockHeight.kt$BlockHeight.Companion$*</ID>
<ID>MaxLineLength:BranchIdTest.kt$BranchIdTest$assertEquals("Invalid branch ID for $networkName at height $height on ${rustBackend.network.networkName}", branchId, actual)</ID>
<ID>MaxLineLength:BranchIdTest.kt$BranchIdTest$assertEquals("Invalid branch Id Hex value for $networkName at height $height on ${rustBackend.network.networkName}", branchHex, clientBranch)</ID>
<ID>MaxLineLength:BranchIdTest.kt$BranchIdTest.Companion$val mainnetBackend = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Mainnet, ZcashNetwork.Mainnet.saplingActivationHeight) }</ID>
<ID>MaxLineLength:BranchIdTest.kt$BranchIdTest.Companion$val testnetBackend = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Testnet, ZcashNetwork.Testnet.saplingActivationHeight) }</ID>
<ID>MaxLineLength:ChangeServiceTest.kt$ChangeServiceTest$"Exception was of the wrong type. Expected ${ChainInfoNotMatching::class.simpleName} but was ${caughtException!!::class.simpleName}"</ID>
<ID>MaxLineLength:ChangeServiceTest.kt$ChangeServiceTest$downloader</ID>
<ID>MaxLineLength:CheckpointTool.kt$CheckpointTool$* @param treeFiles A list of files, sorted in descending order based on `int` value of the first part of the filename.</ID>
<ID>MaxLineLength:CompactBlockDbStore.kt$CompactBlockDbStore$override suspend fun getLatestHeight(): BlockHeight</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$"ERROR: unable to resolve reorg at height $result after ${consecutiveChainErrors.get()} correction attempts!"</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$(lastScannedHeight.value - range.start.value) / (range.endInclusive.value - range.start.value + 1).toFloat() * 100.0f</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$// Note: blocks are public information so it's okay to print them but, still, let's not unless we're debugging something</ID>
@ -149,7 +143,6 @@
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$// sometimes the initial block was inserted via checkpoint and will not appear in the cache. We can get the hash another way but prevHash is correctly null.</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$currentInfo.lastDownloadRange?.isEmpty() ?: true &amp;&amp; currentInfo.lastScanRange?.isEmpty() ?: true</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$if</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$lastDownloadRange</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$lastScanRange</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$lowerBoundHeight + MAX_REORG_SIZE + 2</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$tempOldestTransactionHeight.value - tempOldestTransactionHeight.value.rem(ZcashSdk.MAX_REORG_SIZE) - ZcashSdk.MAX_REORG_SIZE.toLong()</ID>
@ -169,7 +162,8 @@
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("not rewinding dataDb because the last scanned height is $lastScannedHeight and the last local block is $lastLocalBlock both of which are less than the target height of $targetHeight")</ID>
<ID>MaxLineLength:CompactBlockProcessor.kt$CompactBlockProcessor$twig("validation failed at block ${errorInfo.errorHeight} which had hash ${errorInfo.actualPrevHash} but the expected hash was ${errorInfo.expectedPrevHash}")</ID>
<ID>MaxLineLength:ConsensusBranchId.kt$ConsensusBranchId.SPROUT$// TODO: see if we can find a way to not rely on this separate source of truth (either stop converting from hex to display name in the apps or use Rust to get this info)</ID>
<ID>MaxLineLength:DarksideApi.kt$DarksideApi$onNext(it.newBuilderForType().setData(it.data).setHeight(tipHeight.toLong()).build())</ID>
<ID>MaxLineLength:DarksideApi.kt$DarksideApi$Darkside.DarksideEmptyBlocks.newBuilder().setHeight(startHeight.value).setCount(count).setNonce(nonce).build()</ID>
<ID>MaxLineLength:DarksideApi.kt$DarksideApi$onNext(it.newBuilderForType().setData(it.data).setHeight(tipHeight.value).build())</ID>
<ID>MaxLineLength:DarksideApi.kt$DarksideApi$twig("resetting darksidewalletd with saplingActivation=$saplingActivationHeight branchId=$branchId chainName=$chainName")</ID>
<ID>MaxLineLength:DarksideApi.kt$DarksideApi.EmptyResponse$if (error != null) throw RuntimeException("Server responded with an error: $error caused by ${error?.cause}")</ID>
<ID>MaxLineLength:DarksideTestCoordinator.kt$DarksideTestCoordinator$if</ID>
@ -238,13 +232,8 @@
<ID>MaxLineLength:InitializerTest.kt$InitializerTest$// assertEquals("Height should equal sapling activation height when defaultToOldestHeight is true", ZcashSdk.SAPLING_ACTIVATION_HEIGHT, initializer.birthday.height)</ID>
<ID>MaxLineLength:InitializerTest.kt$InitializerTest$// assertEquals("Incorrect height used for import.", ZcashSdk.SAPLING_ACTIVATION_HEIGHT, initializer.birthday.height)</ID>
<ID>MaxLineLength:InitializerTest.kt$InitializerTest$// assertNotEquals("Height should not equal sapling activation height when defaultToOldestHeight is false", ZcashSdk.SAPLING_ACTIVATION_HEIGHT, h)</ID>
<ID>MaxLineLength:LightWalletGrpcService.kt$LightWalletGrpcService$twig("getting channel isShutdown: ${channel.isShutdown} isTerminated: ${channel.isTerminated} getState: $state stateCount: $stateCount", -1)</ID>
<ID>MaxLineLength:LightWalletService.kt$LightWalletService$fun</ID>
<ID>MaxLineLength:ListUtxosFragment.kt$ListUtxosFragment$binding.inputAddress.setText(DerivationTool.deriveTransparentAddress(seed, ZcashNetwork.fromResources(requireApplicationContext())))</ID>
<ID>MaxLineLength:ListUtxosFragment.kt$ListUtxosFragment$binding.inputRangeStart.setText(ZcashNetwork.fromResources(requireApplicationContext()).saplingActivationHeight.toString())</ID>
<ID>MaxLineLength:ListUtxosFragment.kt$ListUtxosFragment$val endToUse = binding.inputRangeEnd.text.toString().toLongOrNull() ?: DemoConstants.getUxtoEndHeight(requireApplicationContext()).value</ID>
<ID>MaxLineLength:ListUtxosFragment.kt$ListUtxosFragment$val startToUse = max(binding.inputRangeStart.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)</ID>
<ID>MaxLineLength:ListUtxosFragment.kt$ListUtxosFragment$val txids = lightwalletService?.getTAddressTransactions(addressToUse, BlockHeight.new(network, startToUse)..BlockHeight.new(network, endToUse))</ID>
<ID>MaxLineLength:MaintainedTest.kt$TestPurpose.DARKSIDE$* These tests require a running instance of [darksidewalletd](https://github.com/zcash/lightwalletd/blob/master/docs/darksidewalletd.md).</ID>
<ID>MaxLineLength:MultiAccountIntegrationTest.kt$// private val secondKey = "zxviews1q0w208wwqqqqpqyxp978kt2qgq5gcyx4er907zhczxpepnnhqn0a47ztefjnk65w2573v7g5fd3hhskrg7srpxazfvrj4n2gm4tphvr74a9xnenpaxy645dmuqkevkjtkf5jld2f7saqs3xyunwquhksjpqwl4zx8zj73m8gk2d5d30pck67v5hua8u3chwtxyetmzjya8jdjtyn2aum7au0agftfh5q9m4g596tev9k365s84jq8n3laa5f4palt330dq0yede053sdyfv6l"</ID>
<ID>MaxLineLength:MultiAccountTest.kt$// private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"</ID>
@ -286,9 +275,8 @@
<ID>MaxLineLength:RustBackend.kt$RustBackend$// override fun parseTransactionDataList(tdl: LocalRpcTypes.TransactionDataList): LocalRpcTypes.TransparentTransactionList {</ID>
<ID>MaxLineLength:RustBackend.kt$RustBackend$throw NotImplementedError("TODO: implement this at the zcash_client_sqlite level. But for now, use DerivationTool, instead to derive addresses from seeds")</ID>
<ID>MaxLineLength:RustBackendWelding.kt$RustBackendWelding$suspend fun clearUtxos(tAddress: String, aboveHeightInclusive: BlockHeight = BlockHeight(network.saplingActivationHeight.value)): Boolean</ID>
<ID>MaxLineLength:SanityTest.kt$SanityTest$"$info\n ${wallet.networkName} Lightwalletd is too far behind. Downloader height $downloaderHeight is more than 10 blocks behind block explorer height $expectedHeight"</ID>
<ID>MaxLineLength:SanityTest.kt$SanityTest$"is using plaintext. This will cause problems for the test. Ensure that the `lightwalletd_allow_very_insecure_connections` resource value is false"</ID>
<ID>MaxLineLength:SanityTest.kt$SanityTest$assertTrue("$networkName failed to return a proper block. Height was ${block.height} but we expected $height", block.height.toInt() == height)</ID>
<ID>MaxLineLength:SanityTest.kt$SanityTest$"${wallet.endpoint} ${wallet.networkName} Lightwalletd is too far behind. Downloader height $downloaderHeight is more than 10 blocks behind block explorer height $expectedHeight"</ID>
<ID>MaxLineLength:SanityTest.kt$SanityTest$assertTrue("$networkName failed to return a proper block. Height was ${block.height} but we expected $height", block.height == height.value)</ID>
<ID>MaxLineLength:SanityTest.kt$SanityTest.Companion$"zxviews1q0hxkupsqqqqpqzsffgrk2smjuccedua7zswf5e3rgtv3ga9nhvhjug670egshd6me53r5n083s2m9mf4va4z7t39ltd3wr7hawnjcw09eu85q0ammsg0tsgx24p4ma0uvr4p8ltx5laum2slh2whc23ctwlnxme9w4dw92kalwk5u4wyem8dynknvvqvs68ktvm8qh7nx9zg22xfc77acv8hk3qqll9k3x4v2fa26puu2939ea7hy4hh60ywma69xtqhcy4037ne8g2sg8sq"</ID>
<ID>MaxLineLength:SanityTest.kt$SanityTest.Companion$"zxviewtestsapling1qv0ue89kqqqqpqqyt4cl5wvssx4wqq30e5m948p07dnwl9x3u75vvnzvjwwpjkrf8yk2gva0kkxk9p8suj4xawlzw9pajuxgap83wykvsuyzfrm33a2p2m4jz2205kgzx0l2lj2kyegtnuph6crkyvyjqmfxut84nu00wxgrstu5fy3eu49nzl8jzr4chmql4ysgg2t8htn9dtvxy8c7wx9rvcerqsjqm6lqln9syk3g8rr3xpy3l4nj0kawenzpcdtnv9qmy98vdhqzaf063"</ID>
<ID>MaxLineLength:SdkSynchronizer.kt$DefaultSynchronizerFactory$// TODO [#242]: Don't hard code page size. It is a workaround for Uncaught Exception: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. and is probably related to FlowPagedList</ID>
@ -300,7 +288,6 @@
<ID>MaxLineLength:SetupTest.kt$SetupTest$val phrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"</ID>
<ID>MaxLineLength:SetupTest.kt$SetupTest.Companion$private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"</ID>
<ID>MaxLineLength:ShieldFundsSample.kt$ShieldFundsSample$val SEED_PHRASE = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" // \"//\"deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"//"deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"</ID>
<ID>MaxLineLength:SmokeTest.kt$SmokeTest$"Wallet is using plaintext. This will cause problems for the test. Ensure that the `lightwalletd_allow_very_insecure_connections` resource value is false"</ID>
<ID>MaxLineLength:SmokeTest.kt$SmokeTest$Assert.assertEquals("Invalid CacheDB file", "/data/user/0/cash.z.ecc.android.sdk.test/databases/TestWallet_testnet_Cache.db", wallet.initializer.rustBackend.pathCacheDb)</ID>
<ID>MaxLineLength:SmokeTest.kt$SmokeTest$Assert.assertEquals("Invalid CacheDB params dir", "/data/user/0/cash.z.ecc.android.sdk.test/cache/params", wallet.initializer.rustBackend.pathParamsDir)</ID>
<ID>MaxLineLength:SmokeTest.kt$SmokeTest$Assert.assertEquals("Invalid DataDB file", "/data/user/0/cash.z.ecc.android.sdk.test/databases/TestWallet_testnet_Data.db", wallet.initializer.rustBackend.pathDataDb)</ID>
@ -326,19 +313,19 @@
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/ff6ea36765dc29793775c7aa71de19fca039c5b5b873a0497866e9c4bc48af01.txt"</ID>
<ID>MaxLineLength:TestExtensions.kt$Transactions$"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/t-shielded-spend.txt"</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet$suspend</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups$ALICE : Backups</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups$DEV_WALLET : Backups</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups$SAMPLE_WALLET : Backups</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups.BOB$BOB</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups.DEFAULT$DEFAULT</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups.ALICE$"quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely life cliff spirit admit weekend message recipe trumpet impact kitten"</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups.BOB$"canvas wine sugar acquire garment spy tongue odor hole cage year habit bullet make label human unit option top calm neutral try vocal arena"</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups.DEFAULT$"column rhythm acoustic gym cost fit keen maze fence seed mail medal shrimp tell relief clip cannon foster soldier shallow refuse lunar parrot banana"</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups.DEV_WALLET$"still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"</ID>
<ID>MaxLineLength:TestWallet.kt$TestWallet.Backups.SAMPLE_WALLET$"input frown warm senior anxiety abuse yard prefer churn reject people glimpse govern glory crumble swallow verb laptop switch trophy inform friend permit purpose"</ID>
<ID>MaxLineLength:TestnetIntegrationTest.kt$TestnetIntegrationTest.Companion$private const val seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"</ID>
<ID>MaxLineLength:TestnetIntegrationTest.kt$TestnetIntegrationTest.Companion$runBlocking { config.importWallet(seed, BlockHeight.new(ZcashNetwork.Testnet, birthdayHeight), ZcashNetwork.Testnet, lightWalletEndpoint) }</ID>
<ID>MaxLineLength:TransactionViewHolder.kt$TransactionViewHolder$icon.setColorFilter(ContextCompat.getColor(itemView.context, if (isInbound) R.color.tx_inbound else R.color.tx_outbound))</ID>
<ID>MaxLineLength:Transactions.kt$if (latestHeight == null || latestHeight.value &lt; saplingActivationHeight.value || expiryHeight &lt; saplingActivationHeight.value) return false</ID>
<ID>MaxLineLength:TransparentRestoreSample.kt$TransparentRestoreSample$// Assert.assertTrue("Not enough funds to run sample. Expected at least $TX_VALUE Zatoshi but found $value. Try adding funds to $address", value >= TX_VALUE)</ID>
<ID>MaxLineLength:TransparentRestoreSample.kt$TransparentRestoreSample$// twig("FOUND utxo balance of total: ${walletBalance.totalZatoshi} available: ${walletBalance.availableZatoshi}")</ID>
<ID>MaxLineLength:TransparentRestoreSample.kt$TransparentRestoreSample$// walletA.send(TX_VALUE, walletA.transparentAddress, "${TransparentRestoreSample::class.java.simpleName} z->t")</ID>
<ID>MaxLineLength:TransparentRestoreSample.kt$TransparentRestoreSample$Assert.assertTrue("Not enough funds to run sample. Expected some Zatoshi but found ${tbalance.available}. Try adding funds to $address", tbalance.available.value > 0)</ID>
<ID>MaxLineLength:TransparentRestoreSample.kt$TransparentRestoreSample$val walletSandbox = TestWallet(TestWallet.Backups.SAMPLE_WALLET.seedPhrase, "WalletC", Testnet, startHeight = 1330190)</ID>
<ID>MaxLineLength:TransparentTest.kt$TransparentTest$assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPublicKey(uvk.extpub, network = network))</ID>
<ID>MaxLineLength:TransparentTest.kt$TransparentTest.Companion$const val PHRASE = "deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"</ID>
<ID>MaxLineLength:TransparentTest.kt$TransparentTest.Companion.ExpectedTestnet$override val zAddr = "ztestsapling1wn3tw9w5rs55x5yl586gtk72e8hcfdq8zsnjzcu8p7ghm8lrx54axc74mvm335q7lmy3g0sqje6"</ID>
@ -361,7 +348,6 @@
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * Default amount of time, in milliseconds, to poll for new blocks. Typically, this should be about half the average * block time. */ val POLL_INTERVAL = 20_000L</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * Default attempts at retrying. */ val RETRIES = 5</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * Default number of blocks to rewind when a chain reorg is detected. This should be large enough to recover from the * reorg but smaller than the theoretical max reorg size of 100. */ val REWIND_DISTANCE = 10</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * Default size of batches of blocks to request from the compact block service. */ val DOWNLOAD_BATCH_SIZE = 100</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * Default size of batches of blocks to scan via librustzcash. The smaller this number the more granular information * can be provided about scan state. Unfortunately, it may also lead to a lot of overhead during scanning. */ val SCAN_BATCH_SIZE = 150</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * Estimate of the time between blocks. */ val BLOCK_INTERVAL_MILLIS = 75_000L</ID>
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$/** * File name for the sapling output params */ val OUTPUT_PARAM_FILE_NAME = "sapling-output.params"</ID>
@ -377,7 +363,7 @@
<ID>MayBeConst:ZcashSdk.kt$ZcashSdk$val DEFAULT_ALIAS: String = "ZcashSdk"</ID>
<ID>PrintStackTrace:CompactBlockProcessor.kt$CompactBlockProcessor$t</ID>
<ID>PrintStackTrace:PersistentTransactionManager.kt$PersistentTransactionManager$t</ID>
<ID>ReturnCount:CurrencyFormatter.kt$ inline fun String?.safelyConvertToBigDecimal(): BigDecimal?</ID>
<ID>ReturnCount:CurrencyFormatter.kt$inline fun String?.safelyConvertToBigDecimal(): BigDecimal?</ID>
<ID>ReturnCount:MainActivity.kt$MainActivity$fun getClipboardText(): String?</ID>
<ID>SpreadOperator:Initializer.kt$Initializer.Config$( *DerivationTool.deriveUnifiedViewingKeys( seed, network, numberOfAccounts ) )</ID>
<ID>SpreadOperator:PagedTransactionRepository.kt$PagedTransactionRepository.Companion$(*viewingKeys.toTypedArray())</ID>
@ -415,7 +401,6 @@
<ID>TooManyFunctions:HomeFragment.kt$HomeFragment : BaseDemoFragment</ID>
<ID>TooManyFunctions:Initializer.kt$Initializer$Companion : Erasable</ID>
<ID>TooManyFunctions:Initializer.kt$Initializer$Config</ID>
<ID>TooManyFunctions:LightWalletGrpcService.kt$LightWalletGrpcService : LightWalletService</ID>
<ID>TooManyFunctions:ListTransactionsFragment.kt$ListTransactionsFragment : BaseDemoFragment</ID>
<ID>TooManyFunctions:ListUtxosFragment.kt$ListUtxosFragment : BaseDemoFragment</ID>
<ID>TooManyFunctions:MainActivity.kt$MainActivity : AppCompatActivityOnPrimaryClipChangedListenerDrawerListener</ID>
@ -467,8 +452,13 @@
<ID>UnusedPrivateMember:SetupTest.kt$SetupTest.Companion$private const val lastBlockHash = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a"</ID>
<ID>UnusedPrivateMember:TestnetIntegrationTest.kt$TestnetIntegrationTest.Companion$private const val seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"</ID>
<ID>UnusedPrivateMember:TestnetIntegrationTest.kt$TestnetIntegrationTest.Companion$private const val targetHeight = 663250</ID>
<ID>UnusedPrivateMember:TransactionCounterUtil.kt$TransactionCounterUtil$private val network = ZcashNetwork.Mainnet</ID>
<ID>UseCheckOrError:FlowPagedListBuilder.kt$FlowPagedListBuilder$throw IllegalStateException("Unable to create executor based on dispatcher: $this")</ID>
<ID>UseCheckOrError:Placeholders.kt$SampleSpendingKeyProvider$throw IllegalStateException("This sample provider only supports the dummy seed")</ID>
<ID>UtilityClassWithPublicConstructor:DerivationTool.kt$DerivationTool</ID>
<ID>UtilityClassWithPublicConstructor:Placeholders.kt$SeedGenerator</ID>
<ID>UtilityClassWithPublicConstructor:SaplingParamTool.kt$SaplingParamTool</ID>
<ID>VariableNaming:ShieldFundsSample.kt$ShieldFundsSample$val SEED_PHRASE = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" // \"//\"deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"//"deputy visa gentle among clean scout farm drive comfort patch skin salt ranch cool ramp warrior drink narrow normal lunch behind salt deal person"</ID>
<ID>VariableNaming:TransparentRestoreSample.kt$TransparentRestoreSample$val TX_VALUE = Zatoshi(ZcashSdk.MINERS_FEE.value / 2)</ID>
</CurrentIssues>
</SmellBaseline>

View File

@ -46,6 +46,7 @@ output-reports:
# - 'TxtOutputReport'
# - 'XmlOutputReport'
# - 'HtmlOutputReport'
# - 'MdOutputReport'
comments:
active: true
@ -62,6 +63,9 @@ comments:
EndOfSentenceFormat:
active: false
endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
KDocReferencesNonPublicProperty:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
OutdatedDocumentation:
active: false
matchTypeParameters: true
@ -133,6 +137,15 @@ complexity:
NestedBlockDepth:
active: true
threshold: 4
NestedScopeFunctions:
active: false
threshold: 1
functions:
- 'kotlin.apply'
- 'kotlin.run'
- 'kotlin.with'
- 'kotlin.let'
- 'kotlin.also'
ReplaceSafeCallChainWithRun:
active: false
StringLiteralDuplication:
@ -159,19 +172,19 @@ coroutines:
GlobalCoroutineUsage:
active: false
InjectDispatcher:
active: false
active: true
dispatcherNames:
- 'IO'
- 'Default'
- 'Unconfined'
RedundantSuspendModifier:
active: false
active: true
SleepInsteadOfDelay:
active: false
active: true
SuspendFunWithCoroutineScopeReceiver:
active: false
SuspendFunWithFlowReturnType:
active: false
active: true
empty-blocks:
active: true
@ -218,7 +231,7 @@ exceptions:
- 'hashCode'
- 'toString'
InstanceOfCheckForException:
active: false
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
NotImplementedDeclaration:
active: false
@ -283,58 +296,46 @@ naming:
active: true
BooleanPropertyNaming:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
allowedPattern: '^(is|has|are)'
ignoreOverridden: true
ClassNaming:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
classPattern: '[A-Z][a-zA-Z0-9]*'
ConstructorParameterNaming:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
EnumNaming:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
forbiddenName: []
FunctionMaxLength:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
maximumFunctionNameLength: 30
FunctionMinLength:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
minimumFunctionNameLength: 3
FunctionNaming:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
functionPattern: '([a-z][a-zA-Z0-9]*)|(`.*`)'
functionPattern: '[a-z][a-zA-Z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
ignoreAnnotated:
- 'Composable'
FunctionParameterNaming:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
InvalidPackageDeclaration:
active: false
excludes: ['**/*.kts']
active: true
rootPackage: ''
requireRootInDeclaration: false
LambdaParameterNaming:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
parameterPattern: '[a-z][A-Za-z0-9]*|_'
MatchingDeclarationName:
active: true
@ -343,37 +344,30 @@ naming:
active: true
ignoreOverridden: true
NoNameShadowing:
active: false
active: true
NonBooleanPropertyPrefixedWithIs:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
ObjectPropertyNaming:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
TopLevelPropertyNaming:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
constantPattern: '[A-Z][_A-Z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
VariableMaxLength:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
maximumVariableNameLength: 64
VariableMinLength:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
minimumVariableNameLength: 1
VariableNaming:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
@ -383,6 +377,9 @@ performance:
active: true
ArrayPrimitive:
active: true
CouldBeSequence:
active: false
threshold: 3
ForEachOnRange:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
@ -395,7 +392,7 @@ performance:
potential-bugs:
active: true
AvoidReferentialEquality:
active: false
active: true
forbiddenTypePatterns:
- 'kotlin.String'
CastToNullableType:
@ -405,7 +402,7 @@ potential-bugs:
DontDowncastCollectionTypes:
active: false
DoubleMutabilityForCollection:
active: false
active: true
mutableTypes:
- 'kotlin.collections.MutableList'
- 'kotlin.collections.MutableMap'
@ -428,9 +425,9 @@ potential-bugs:
ExplicitGarbageCollectionCall:
active: true
HasPlatformType:
active: false
active: true
IgnoredReturnValue:
active: false
active: true
restrictToAnnotatedMethods: true
returnValueAnnotations:
- '*.CheckResult'
@ -454,7 +451,7 @@ potential-bugs:
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
ignoreOnClassesPattern: ''
MapGetWithNotNullAssertionOperator:
active: false
active: true
MissingPackageDeclaration:
active: false
excludes: ['**/*.kts']
@ -474,7 +471,7 @@ potential-bugs:
UnnecessarySafeCall:
active: true
UnreachableCatchBlock:
active: false
active: true
UnreachableCode:
active: true
UnsafeCallOnNullableType:
@ -483,9 +480,9 @@ potential-bugs:
UnsafeCast:
active: true
UnusedUnaryOperator:
active: false
active: true
UselessPostfixExpression:
active: false
active: true
WrongEqualsTypeParameter:
active: true
@ -493,6 +490,9 @@ style:
active: true
CanBeNonNullable:
active: false
CascadingCallWrapping:
active: false
includeElvis: true
ClassOrdering:
active: false
CollapsibleIfStatements:
@ -503,7 +503,7 @@ style:
DataClassShouldBeImmutable:
active: false
DestructuringDeclarationWithTooManyEntries:
active: false
active: true
maxDestructuringEntries: 3
EqualsNullCall:
active: true
@ -512,7 +512,7 @@ style:
ExplicitCollectionElementAccessMethod:
active: false
ExplicitItLambdaParameter:
active: false
active: true
ExpressionBodySyntax:
active: false
includeLineWrapping: false
@ -539,8 +539,11 @@ style:
ignorePackages:
- '*.internal'
- '*.internal.*'
ForbiddenVoid:
ForbiddenSuppress:
active: false
rules: []
ForbiddenVoid:
active: true
ignoreOverridden: false
ignoreUsageInGenerics: false
FunctionOnlyReturningConstant:
@ -559,7 +562,7 @@ style:
maxJumpCount: 1
MagicNumber:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts']
ignoreNumbers:
- '-1'
- '0'
@ -579,6 +582,9 @@ style:
active: false
MandatoryBracesLoops:
active: false
MaxChainedCallsOnSameLine:
active: false
maxChainedCalls: 5
MaxLineLength:
active: true
maxLineLength: 120
@ -597,8 +603,10 @@ style:
active: false
NoTabs:
active: false
ObjectLiteralToLambda:
NullableBooleanCheck:
active: false
ObjectLiteralToLambda:
active: true
OptionalAbstractKeyword:
active: true
OptionalUnit:
@ -612,7 +620,7 @@ style:
RedundantExplicitType:
active: false
RedundantHigherOrderMapUsage:
active: false
active: true
RedundantVisibilityModifierRule:
active: false
ReturnCount:
@ -644,8 +652,10 @@ style:
active: false
UnnecessaryApply:
active: true
UnnecessaryFilter:
UnnecessaryBackticks:
active: false
UnnecessaryFilter:
active: true
UnnecessaryInheritance:
active: true
UnnecessaryInnerClass:
@ -664,13 +674,13 @@ style:
active: true
allowedNames: '(_|ignored|expected|serialVersionUID)'
UseAnyOrNoneInsteadOfFind:
active: false
active: true
UseArrayLiteralsInAnnotations:
active: false
active: true
UseCheckNotNull:
active: false
active: true
UseCheckOrError:
active: false
active: true
UseDataClass:
active: false
allowVars: false
@ -681,19 +691,20 @@ style:
UseIfInsteadOfWhen:
active: false
UseIsNullOrEmpty:
active: false
active: true
UseOrEmpty:
active: false
active: true
UseRequire:
active: false
active: true
UseRequireNotNull:
active: false
active: true
UselessCallOnNotNull:
active: true
UtilityClassWithPublicConstructor:
active: true
VarCouldBeVal:
active: true
ignoreLateinitVar: false
WildcardImport:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']