v1.9.0-beta01 changes
This commit includes HUSH specific changes starting at v.1.9.0-beta01 release here: https://github.com/zcash/zcash-android-wallet-sdk/releases/tag/v1.9.0-beta01
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
1
.gitignore
vendored
@@ -81,3 +81,4 @@ DecompileChecker.kt
|
|||||||
backup-dbs/
|
backup-dbs/
|
||||||
*.db
|
*.db
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
sdk-lib/Cargo.lock
|
||||||
|
|||||||
3
.idea/.gitignore
generated
vendored
@@ -1,3 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
14
CHANGELOG.md
@@ -3,7 +3,19 @@ Change Log
|
|||||||
|
|
||||||
Upcoming
|
Upcoming
|
||||||
------------------------------------
|
------------------------------------
|
||||||
- Added `Zatoshi` typesafe object to represent amounts instead.
|
- Split `ZcashNetwork` into `ZcashNetwork` and `LightWalletEndpoint` to decouple network and server configuration
|
||||||
|
|
||||||
|
Version 1.8.0-beta01
|
||||||
|
------------------------------------
|
||||||
|
- Added `BlockHeight` typesafe object to represent block heights
|
||||||
|
- Significantly reduced memory usage, fixing potential OutOfMemoryError during block download
|
||||||
|
- Kotlin 1.7.10
|
||||||
|
- Updated checkpoints
|
||||||
|
|
||||||
|
Version 1.7.0-beta01
|
||||||
|
------------------------------------
|
||||||
|
- Added `Zatoshi` typesafe object to represent amounts.
|
||||||
|
- Kotlin 1.7.0
|
||||||
|
|
||||||
Version 1.6.0-beta01
|
Version 1.6.0-beta01
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|||||||
@@ -1,17 +1,33 @@
|
|||||||
Troubleshooting Migrations
|
Troubleshooting Migrations
|
||||||
==========
|
==========
|
||||||
|
|
||||||
Upcoming Migration to Version 1.7 from 1.6
|
Upcoming
|
||||||
|
--------------------------------------
|
||||||
|
`ZcashNetwork` is no longer an enum. The prior enum values are now declared as object properties `ZcashNetwork.Mainnet` and `ZcashNetwork.Testnet`. For the most part, this change should have minimal impact. ZcashNetwork was also moved from the package `cash.z.ecc.android.sdk.type` to `cash.z.ecc.android.sdk.model`, which will require a change to your import statements. The server fields have been removed from `ZcashNetwork`, allowing server and network configuration to be done independently.
|
||||||
|
|
||||||
|
`LightWalletEndpoint` is a new object to represent server information. Default values can be obtained from `LightWalletEndpoint.defaultForNetwork(ZcashNetwork)`
|
||||||
|
|
||||||
|
`Synchronizer` no longer allows changing the endpoint after construction. Instead, construct a new `Synchronizer` with the desired endpoint.
|
||||||
|
|
||||||
|
Migration to Version 1.8 from 1.7
|
||||||
|
--------------------------------------
|
||||||
|
Various APIs used `Int` to represent network block heights. Those APIs now use a typesafe `BlockHeight` type. BlockHeight is constructed with a factory method `BlockHeight.new(ZcashNetwork, Long)` which uses the network to validate the height is above the network's sapling activation height.
|
||||||
|
|
||||||
|
`WalletBirthday` has been renamed to `Checkpoint` and removed from the public API. Where clients previously passed in a `WalletBirthday` object, now a `BlockHeight` can be passed in instead.
|
||||||
|
|
||||||
|
Migration to Version 1.7 from 1.6
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
Various APIs used `Long` value to represent Zatoshi currency amounts. Those APIs now use a typesafe `Zatoshi` class. When passing amounts, simply wrap Long values with the Zatoshi constructor `Zatoshi(Long)`. When receiving values, simply unwrap Long values with `Zatoshi.value`.
|
Various APIs used `Long` value to represent Zatoshi currency amounts. Those APIs now use a typesafe `Zatoshi` class. When passing amounts, simply wrap Long values with the Zatoshi constructor `Zatoshi(Long)`. When receiving values, simply unwrap Long values with `Zatoshi.value`.
|
||||||
|
|
||||||
`WalletBalance` no longer has uninitialized default values. This means that `Synchronizer` fields that expose a WalletBalance now use `null` to signal an uninitialized value. Specifically this means `Synchronizer.orchardBalances`, `Synchronzier.saplingBalances`, and `Synchronizer.transparentBalances` have nullable values now.
|
`WalletBalance` no longer has uninitialized default values. This means that `Synchronizer` fields that expose a WalletBalance now use `null` to signal an uninitialized value. Specifically this means `Synchronizer.orchardBalances`, `Synchronzier.saplingBalances`, and `Synchronizer.transparentBalances` have nullable values now.
|
||||||
|
|
||||||
|
`WalletBalance` has been moved from the package `cash.z.ecc.android.sdk.type` to `cash.z.ecc.android.sdk.model`
|
||||||
|
|
||||||
`ZcashSdk.ZATOSHI_PER_ZEC` has been moved to `Zatoshi.ZATOSHI_PER_ZEC`.
|
`ZcashSdk.ZATOSHI_PER_ZEC` has been moved to `Zatoshi.ZATOSHI_PER_ZEC`.
|
||||||
|
|
||||||
`ZcashSdk.MINERS_FEE_ZATOSHI` has been renamed to `ZcashSdk.MINERS_FEE` and the type has changed from `Long` to `Zatoshi`.
|
`ZcashSdk.MINERS_FEE_ZATOSHI` has been renamed to `ZcashSdk.MINERS_FEE` and the type has changed from `Long` to `Zatoshi`.
|
||||||
|
|
||||||
Upcoming Migrating to Version 1.4.* from 1.3.*
|
Migrating to Version 1.4.* from 1.3.*
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
The main entrypoint to the SDK has changed.
|
The main entrypoint to the SDK has changed.
|
||||||
|
|
||||||
|
|||||||
217
README.md
@@ -16,211 +16,42 @@ This is a beta build and is currently under active development. Please be advise
|
|||||||
---
|
---
|
||||||
|
|
||||||
# Zcash Android SDK
|
# 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)
|
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.
|
||||||
- [Structure](#structure)
|
|
||||||
- [Overview](#overview)
|
|
||||||
- [Components](#components)
|
|
||||||
- [Quickstart](#quickstart)
|
|
||||||
- [Examples](#examples)
|
|
||||||
- [Compiling Sources](#compiling-sources)
|
|
||||||
- [Versioning](#versioning)
|
|
||||||
- [Examples](#examples)
|
|
||||||
|
|
||||||
## 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:
|
# 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.
|
||||||

|
|
||||||
|
|
||||||
Thankfully, the only thing an app developer has to be concerned with is the following:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
[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
|
|
||||||
|
|
||||||
A primitive example to exercise the SDK exists in this repo, under [Demo App](demo-app).
|
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
|
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:
|
||||||
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.
|
* 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).
|
## 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`
|
||||||
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. 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. 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.
|
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.
|
||||||
|
|||||||
@@ -6,12 +6,34 @@ plugins {
|
|||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
dependencyLocking {
|
dependencyLocking {
|
||||||
lockAllConfigurations()
|
// This property is treated specially, as it is not defined by default in the root gradle.properties
|
||||||
|
// and declaring it in the root gradle.properties is ignored by included builds. This only picks up
|
||||||
|
// a value declared as a system property, a command line argument, or a an environment variable.
|
||||||
|
val isDependencyLockingEnabled = if (project.hasProperty("ZCASH_IS_DEPENDENCY_LOCKING_ENABLED")) {
|
||||||
|
project.property("ZCASH_IS_DEPENDENCY_LOCKING_ENABLED").toString().toBoolean()
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDependencyLockingEnabled) {
|
||||||
|
lockAllConfigurations()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyLocking {
|
dependencyLocking {
|
||||||
lockAllConfigurations()
|
// This property is treated specially, as it is not defined by default in the root gradle.properties
|
||||||
|
// and declaring it in the root gradle.properties is ignored by included builds. This only picks up
|
||||||
|
// a value declared as a system property, a command line argument, or a an environment variable.
|
||||||
|
val isDependencyLockingEnabled = if (project.hasProperty("ZCASH_IS_DEPENDENCY_LOCKING_ENABLED")) {
|
||||||
|
project.property("ZCASH_IS_DEPENDENCY_LOCKING_ENABLED").toString().toBoolean()
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDependencyLockingEnabled) {
|
||||||
|
lockAllConfigurations()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per conversation in the KotlinLang Slack, Gradle uses Java 8 compatibility internally
|
// Per conversation in the KotlinLang Slack, Gradle uses Java 8 compatibility internally
|
||||||
|
|||||||
@@ -3,43 +3,43 @@
|
|||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
com.github.gundy:semver4j:0.16.4=classpath
|
com.github.gundy:semver4j:0.16.4=classpath
|
||||||
com.google.code.findbugs:jsr305:3.0.2=classpath
|
com.google.code.findbugs:jsr305:3.0.2=classpath
|
||||||
com.google.code.gson:gson:2.8.6=classpath
|
com.google.code.gson:gson:2.8.9=classpath
|
||||||
com.google.errorprone:error_prone_annotations:2.3.4=classpath
|
com.google.errorprone:error_prone_annotations:2.3.4=classpath
|
||||||
com.google.guava:failureaccess:1.0.1=classpath
|
com.google.guava:failureaccess:1.0.1=classpath
|
||||||
com.google.guava:guava:29.0-jre=classpath
|
com.google.guava:guava:29.0-jre=classpath
|
||||||
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath
|
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath
|
||||||
com.google.j2objc:j2objc-annotations:1.3=classpath
|
com.google.j2objc:j2objc-annotations:1.3=classpath
|
||||||
de.undercouch:gradle-download-task:4.1.1=classpath
|
de.undercouch:gradle-download-task:4.1.1=classpath
|
||||||
|
net.java.dev.jna:jna:5.6.0=classpath
|
||||||
org.checkerframework:checker-qual:2.11.1=classpath
|
org.checkerframework:checker-qual:2.11.1=classpath
|
||||||
org.gradle.kotlin.kotlin-dsl:org.gradle.kotlin.kotlin-dsl.gradle.plugin:2.1.7=classpath
|
org.gradle.kotlin.kotlin-dsl:org.gradle.kotlin.kotlin-dsl.gradle.plugin:2.3.3=classpath
|
||||||
org.gradle.kotlin:gradle-kotlin-dsl-plugins:2.1.7=classpath
|
org.gradle.kotlin:gradle-kotlin-dsl-plugins:2.3.3=classpath
|
||||||
org.jetbrains.intellij.deps:trove4j:1.0.20181211=classpath
|
org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath
|
||||||
org.jetbrains.kotlin:kotlin-android-extensions:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-android-extensions:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-build-common:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-build-common:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-compiler-runner:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-compiler-runner:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-daemon-client:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-daemon-client:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-native-utils:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-native-utils:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-project-model:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-project-model:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-sam-with-receiver:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-sam-with-receiver:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-scripting-common:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-scripting-common:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-scripting-jvm:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-stdlib:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-tooling-metadata:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-tooling-metadata:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-util-io:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-util-io:1.6.21=classpath
|
||||||
org.jetbrains.kotlin:kotlin-util-klib:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-util-klib:1.6.21=classpath
|
||||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath
|
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath
|
||||||
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=classpath
|
|
||||||
org.jetbrains:annotations:13.0=classpath
|
org.jetbrains:annotations:13.0=classpath
|
||||||
empty=
|
empty=
|
||||||
|
|||||||
@@ -1,45 +1,45 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
# This is a Gradle generated file for dependency locking.
|
||||||
# Manual edits can break the build and are not advised.
|
# Manual edits can break the build and are not advised.
|
||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
androidx.databinding:databinding-common:7.2.1=runtimeClasspath
|
androidx.databinding:databinding-common:7.2.2=runtimeClasspath
|
||||||
androidx.databinding:databinding-compiler-common:7.2.1=runtimeClasspath
|
androidx.databinding:databinding-compiler-common:7.2.2=runtimeClasspath
|
||||||
com.android.databinding:baseLibrary:7.2.1=runtimeClasspath
|
com.android.databinding:baseLibrary:7.2.2=runtimeClasspath
|
||||||
com.android.tools.analytics-library:crash:30.2.1=runtimeClasspath
|
com.android.tools.analytics-library:crash:30.2.2=runtimeClasspath
|
||||||
com.android.tools.analytics-library:protos:30.2.1=runtimeClasspath
|
com.android.tools.analytics-library:protos:30.2.2=runtimeClasspath
|
||||||
com.android.tools.analytics-library:shared:30.2.1=runtimeClasspath
|
com.android.tools.analytics-library:shared:30.2.2=runtimeClasspath
|
||||||
com.android.tools.analytics-library:tracker:30.2.1=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-core:1.0.0-beta09=runtimeClasspath
|
||||||
com.android.tools.build.jetifier:jetifier-processor: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:aapt2-proto:7.2.2-7984345=runtimeClasspath
|
||||||
com.android.tools.build:aaptcompiler:7.2.1=runtimeClasspath
|
com.android.tools.build:aaptcompiler:7.2.2=runtimeClasspath
|
||||||
com.android.tools.build:apksig:7.2.1=compileClasspath,runtimeClasspath
|
com.android.tools.build:apksig:7.2.2=compileClasspath,runtimeClasspath
|
||||||
com.android.tools.build:apkzlib:7.2.1=compileClasspath,runtimeClasspath
|
com.android.tools.build:apkzlib:7.2.2=compileClasspath,runtimeClasspath
|
||||||
com.android.tools.build:builder-model:7.2.1=compileClasspath,runtimeClasspath
|
com.android.tools.build:builder-model:7.2.2=compileClasspath,runtimeClasspath
|
||||||
com.android.tools.build:builder-test-api:7.2.1=runtimeClasspath
|
com.android.tools.build:builder-test-api:7.2.2=runtimeClasspath
|
||||||
com.android.tools.build:builder:7.2.1=compileClasspath,runtimeClasspath
|
com.android.tools.build:builder:7.2.2=compileClasspath,runtimeClasspath
|
||||||
com.android.tools.build:bundletool:1.8.2=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-api:7.2.2=compileClasspath,runtimeClasspath
|
||||||
com.android.tools.build:gradle:7.2.1=compileClasspath,runtimeClasspath
|
com.android.tools.build:gradle:7.2.2=compileClasspath,runtimeClasspath
|
||||||
com.android.tools.build:manifest-merger:30.2.1=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.build:transform-api:2.0.0-deprecated-use-gradle-api=runtimeClasspath
|
||||||
com.android.tools.ddms:ddmlib:30.2.1=runtimeClasspath
|
com.android.tools.ddms:ddmlib:30.2.2=runtimeClasspath
|
||||||
com.android.tools.layoutlib:layoutlib-api:30.2.1=runtimeClasspath
|
com.android.tools.layoutlib:layoutlib-api:30.2.2=runtimeClasspath
|
||||||
com.android.tools.lint:lint-model:30.2.1=runtimeClasspath
|
com.android.tools.lint:lint-model:30.2.2=runtimeClasspath
|
||||||
com.android.tools.lint:lint-typedef-remover:30.2.1=runtimeClasspath
|
com.android.tools.lint:lint-typedef-remover:30.2.2=runtimeClasspath
|
||||||
com.android.tools.utp:android-device-provider-ddmlib-proto:30.2.1=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.1=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.1=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.1=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.1=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.1=runtimeClasspath
|
com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.2.2=runtimeClasspath
|
||||||
com.android.tools:annotations:30.2.1=runtimeClasspath
|
com.android.tools:annotations:30.2.2=runtimeClasspath
|
||||||
com.android.tools:common:30.2.1=runtimeClasspath
|
com.android.tools:common:30.2.2=runtimeClasspath
|
||||||
com.android.tools:dvlib:30.2.1=runtimeClasspath
|
com.android.tools:dvlib:30.2.2=runtimeClasspath
|
||||||
com.android.tools:repository:30.2.1=runtimeClasspath
|
com.android.tools:repository:30.2.2=runtimeClasspath
|
||||||
com.android.tools:sdk-common:30.2.1=runtimeClasspath
|
com.android.tools:sdk-common:30.2.2=runtimeClasspath
|
||||||
com.android.tools:sdklib:30.2.1=runtimeClasspath
|
com.android.tools:sdklib:30.2.2=runtimeClasspath
|
||||||
com.android:signflinger:7.2.1=runtimeClasspath
|
com.android:signflinger:7.2.2=runtimeClasspath
|
||||||
com.android:zipflinger:7.2.1=compileClasspath,runtimeClasspath
|
com.android:zipflinger:7.2.2=compileClasspath,runtimeClasspath
|
||||||
com.fasterxml.jackson.core:jackson-annotations:2.11.1=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-core:2.11.1=runtimeClasspath
|
||||||
com.fasterxml.jackson.core:jackson-databind:2.11.1=runtimeClasspath
|
com.fasterxml.jackson.core:jackson-databind:2.11.1=runtimeClasspath
|
||||||
@@ -101,7 +101,7 @@ jakarta.activation:jakarta.activation-api:1.2.1=runtimeClasspath
|
|||||||
jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=runtimeClasspath
|
jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=runtimeClasspath
|
||||||
javax.inject:javax.inject:1=runtimeClasspath
|
javax.inject:javax.inject:1=runtimeClasspath
|
||||||
net.java.dev.jna:jna-platform:5.6.0=runtimeClasspath
|
net.java.dev.jna:jna-platform:5.6.0=runtimeClasspath
|
||||||
net.java.dev.jna:jna:5.6.0=runtimeClasspath
|
net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,runtimeClasspath
|
||||||
net.sf.jopt-simple:jopt-simple:4.9=runtimeClasspath
|
net.sf.jopt-simple:jopt-simple:4.9=runtimeClasspath
|
||||||
net.sf.kxml:kxml2:2.3.0=runtimeClasspath
|
net.sf.kxml:kxml2:2.3.0=runtimeClasspath
|
||||||
org.apache.commons:commons-compress:1.20=runtimeClasspath
|
org.apache.commons:commons-compress:1.20=runtimeClasspath
|
||||||
@@ -118,51 +118,54 @@ org.glassfish.jaxb:jaxb-runtime:2.3.2=runtimeClasspath
|
|||||||
org.glassfish.jaxb:txw2:2.3.2=runtimeClasspath
|
org.glassfish.jaxb:txw2:2.3.2=runtimeClasspath
|
||||||
org.jdom:jdom2:2.0.6=runtimeClasspath
|
org.jdom:jdom2:2.0.6=runtimeClasspath
|
||||||
org.jetbrains.dokka:dokka-core:1.4.32=runtimeClasspath
|
org.jetbrains.dokka:dokka-core:1.4.32=runtimeClasspath
|
||||||
org.jetbrains.intellij.deps:trove4j:1.0.20181211=kotlinCompilerClasspath
|
org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,runtimeClasspath
|
||||||
org.jetbrains.intellij.deps:trove4j:1.0.20200330=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-android-extensions:1.6.21=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.6.21=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-build-common:1.7.10=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-build-common:1.6.21=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.6.21=kotlinCompilerClasspath
|
||||||
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=kotlinCompilerClasspath
|
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.6.21=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-compiler-runner:1.6.21=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-daemon-client:1.6.21=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.6.21=kotlinCompilerClasspath
|
||||||
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=kotlinCompilerClasspath
|
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.6.21=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.6.21=kotlinCompilerPluginClasspathMain
|
||||||
org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.5.31=kotlinCompilerPluginClasspathMain
|
org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=compileClasspath,runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.6.21=compileClasspath,runtimeClasspath
|
org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.5.31=kotlinCompilerPluginClasspathMain
|
org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.6.21=kotlinCompilerPluginClasspathMain
|
||||||
org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.6.21=compileClasspath,runtimeClasspath
|
org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=compileClasspath,runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21=compileClasspath,runtimeClasspath
|
org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=compileClasspath,runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.6.21=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-native-utils:1.5.31=kotlinCompilerPluginClasspathMain
|
org.jetbrains.kotlin:kotlin-native-utils:1.6.21=kotlinCompilerPluginClasspathMain
|
||||||
org.jetbrains.kotlin:kotlin-native-utils:1.6.21=compileClasspath,runtimeClasspath
|
org.jetbrains.kotlin:kotlin-native-utils:1.7.10=compileClasspath,runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-project-model:1.5.31=kotlinCompilerPluginClasspathMain
|
org.jetbrains.kotlin:kotlin-project-model:1.6.21=kotlinCompilerPluginClasspathMain
|
||||||
org.jetbrains.kotlin:kotlin-project-model:1.6.21=compileClasspath,runtimeClasspath
|
org.jetbrains.kotlin:kotlin-project-model:1.7.10=compileClasspath,runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-reflect:1.5.31=compileClasspath,kotlinCompilerClasspath,runtimeClasspath
|
org.jetbrains.kotlin:kotlin-reflect:1.5.31=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-sam-with-receiver:1.5.31=kotlinCompilerPluginClasspathMain
|
org.jetbrains.kotlin:kotlin-reflect:1.6.21=compileClasspath,kotlinCompilerClasspath
|
||||||
org.jetbrains.kotlin:kotlin-script-runtime:1.5.31=kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain
|
org.jetbrains.kotlin:kotlin-sam-with-receiver:1.6.21=kotlinCompilerPluginClasspathMain
|
||||||
org.jetbrains.kotlin:kotlin-scripting-common:1.5.31=kotlinCompilerPluginClasspathMain
|
org.jetbrains.kotlin:kotlin-script-runtime:1.6.21=kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain
|
||||||
org.jetbrains.kotlin:kotlin-scripting-common:1.6.21=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-scripting-common:1.6.21=kotlinCompilerPluginClasspathMain
|
||||||
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.31=kotlinCompilerPluginClasspathMain
|
org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.6.21=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.6.21=kotlinCompilerPluginClasspathMain
|
||||||
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.31=kotlinCompilerPluginClasspathMain
|
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.6.21=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.6.21=kotlinCompilerPluginClasspathMain
|
||||||
org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.31=kotlinCompilerPluginClasspathMain
|
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-scripting-jvm:1.6.21=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-scripting-jvm:1.6.21=kotlinCompilerPluginClasspathMain
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=compileClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,runtimeClasspath
|
org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.0=kotlinCompilerPluginClasspathMain
|
org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=compileClasspath,runtimeClasspath
|
org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21=compileClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.0=kotlinCompilerPluginClasspathMain
|
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=compileClasspath,runtimeClasspath
|
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.21=compileClasspath
|
||||||
org.jetbrains.kotlin:kotlin-stdlib:1.5.31=compileClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,runtimeClasspath
|
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-tooling-metadata:1.6.21=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21=compileClasspath
|
||||||
org.jetbrains.kotlin:kotlin-util-io:1.5.31=kotlinCompilerPluginClasspathMain
|
org.jetbrains.kotlin:kotlin-stdlib:1.5.31=runtimeClasspath
|
||||||
org.jetbrains.kotlin:kotlin-util-io:1.6.21=compileClasspath,runtimeClasspath
|
org.jetbrains.kotlin:kotlin-stdlib:1.6.21=compileClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain
|
||||||
org.jetbrains.kotlin:kotlin-util-klib:1.6.21=runtimeClasspath
|
org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=compileClasspath,runtimeClasspath
|
||||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=kotlinCompilerPluginClasspathMain,runtimeClasspath
|
org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=runtimeClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-util-io:1.6.21=kotlinCompilerPluginClasspathMain
|
||||||
|
org.jetbrains.kotlin:kotlin-util-io:1.7.10=compileClasspath,runtimeClasspath
|
||||||
|
org.jetbrains.kotlin:kotlin-util-klib:1.7.10=runtimeClasspath
|
||||||
|
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=runtimeClasspath
|
||||||
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1=runtimeClasspath
|
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1=runtimeClasspath
|
||||||
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=kotlinCompilerPluginClasspathMain
|
|
||||||
org.jetbrains:annotations:13.0=compileClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,runtimeClasspath
|
org.jetbrains:annotations:13.0=compileClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,runtimeClasspath
|
||||||
org.jetbrains:markdown-jvm:0.2.1=runtimeClasspath
|
org.jetbrains:markdown-jvm:0.2.1=runtimeClasspath
|
||||||
org.jetbrains:markdown:0.2.1=runtimeClasspath
|
org.jetbrains:markdown:0.2.1=runtimeClasspath
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
|
|
||||||
//dependencyLocking {
|
//dependencyLocking {
|
||||||
// lockAllConfigurations()
|
// This property is treated specially, as it is not defined by default in the root gradle.properties
|
||||||
|
// and declaring it in the root gradle.properties is ignored by included builds. This only picks up
|
||||||
|
// a value declared as a system property, a command line argument, or a an environment variable.
|
||||||
|
// val isDependencyLockingEnabled = if (project.hasProperty("ZCASH_IS_DEPENDENCY_LOCKING_ENABLED")) {
|
||||||
|
// project.property("ZCASH_IS_DEPENDENCY_LOCKING_ENABLED").toString().toBoolean()
|
||||||
|
// } else {
|
||||||
|
// true
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (isDependencyLockingEnabled) {
|
||||||
|
// lockAllConfigurations()
|
||||||
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
|||||||
import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
||||||
import cash.z.ecc.android.sdk.darkside.test.ScopedTest
|
import cash.z.ecc.android.sdk.darkside.test.ScopedTest
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
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 org.junit.BeforeClass
|
import org.junit.BeforeClass
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -18,12 +20,12 @@ class InboundTxTests : ScopedTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTargetBlock_scanned() {
|
fun testTargetBlock_scanned() {
|
||||||
validator.validateMinHeightScanned(targetTxBlock - 1)
|
validator.validateMinHeightScanned(BlockHeight.new(ZcashNetwork.Mainnet, targetTxBlock.value - 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLatestHeight() {
|
fun testLatestHeight() {
|
||||||
validator.validateLatestHeight(targetTxBlock - 1)
|
validator.validateLatestHeight(BlockHeight.new(ZcashNetwork.Mainnet, targetTxBlock.value - 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -40,7 +42,7 @@ class InboundTxTests : ScopedTest() {
|
|||||||
validator.validateTxCount(2)
|
validator.validateTxCount(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addTransactions(targetHeight: Int, vararg txs: String) {
|
private fun addTransactions(targetHeight: BlockHeight, vararg txs: String) {
|
||||||
val overwriteBlockCount = 5
|
val overwriteBlockCount = 5
|
||||||
chainMaker
|
chainMaker
|
||||||
// .stageEmptyBlocks(targetHeight, overwriteBlockCount)
|
// .stageEmptyBlocks(targetHeight, overwriteBlockCount)
|
||||||
@@ -78,8 +80,8 @@ class InboundTxTests : ScopedTest() {
|
|||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/71935e29127a7de0b96081f4c8a42a9c11584d83adedfaab414362a6f3d965cf.txt"
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/71935e29127a7de0b96081f4c8a42a9c11584d83adedfaab414362a6f3d965cf.txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
private const val firstBlock = 663150
|
private val firstBlock = BlockHeight.new(ZcashNetwork.Mainnet, 663150L)
|
||||||
private const val targetTxBlock = 663188
|
private val targetTxBlock = BlockHeight.new(ZcashNetwork.Mainnet, 663188L)
|
||||||
private const val lastBlockHash = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a"
|
private const val lastBlockHash = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a"
|
||||||
private val sithLord = DarksideTestCoordinator()
|
private val sithLord = DarksideTestCoordinator()
|
||||||
private val validator = sithLord.validator
|
private val validator = sithLord.validator
|
||||||
@@ -93,7 +95,7 @@ class InboundTxTests : ScopedTest() {
|
|||||||
chainMaker
|
chainMaker
|
||||||
.resetBlocks(blocksUrl, startHeight = firstBlock, tipHeight = targetTxBlock)
|
.resetBlocks(blocksUrl, startHeight = firstBlock, tipHeight = targetTxBlock)
|
||||||
.stageEmptyBlocks(firstBlock + 1, 100)
|
.stageEmptyBlocks(firstBlock + 1, 100)
|
||||||
.applyTipHeight(targetTxBlock - 1)
|
.applyTipHeight(BlockHeight.new(ZcashNetwork.Mainnet, targetTxBlock.value - 1))
|
||||||
|
|
||||||
sithLord.synchronizer.start(classScope)
|
sithLord.synchronizer.start(classScope)
|
||||||
sithLord.await()
|
sithLord.await()
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package cash.z.ecc.android.sdk.darkside.reorgs
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
||||||
import cash.z.ecc.android.sdk.darkside.test.ScopedTest
|
import cash.z.ecc.android.sdk.darkside.test.ScopedTest
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.BeforeClass
|
import org.junit.BeforeClass
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -11,8 +13,8 @@ import org.junit.runner.RunWith
|
|||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ReorgSetupTest : ScopedTest() {
|
class ReorgSetupTest : ScopedTest() {
|
||||||
|
|
||||||
private val birthdayHeight = 663150
|
private val birthdayHeight = BlockHeight.new(ZcashNetwork.Mainnet, 663150)
|
||||||
private val targetHeight = 663250
|
private val targetHeight = BlockHeight.new(ZcashNetwork.Mainnet, 663250)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
|||||||
import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
||||||
import cash.z.ecc.android.sdk.darkside.test.ScopedTest
|
import cash.z.ecc.android.sdk.darkside.test.ScopedTest
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
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 org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.BeforeClass
|
import org.junit.BeforeClass
|
||||||
@@ -13,7 +15,10 @@ import org.junit.runner.RunWith
|
|||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ReorgSmallTest : ScopedTest() {
|
class ReorgSmallTest : ScopedTest() {
|
||||||
|
|
||||||
private val targetHeight = 663250
|
private val targetHeight = BlockHeight.new(
|
||||||
|
ZcashNetwork.Mainnet,
|
||||||
|
663250
|
||||||
|
)
|
||||||
private val hashBeforeReorg = "09ec0d5de30d290bc5a2318fbf6a2427a81c7db4790ce0e341a96aeac77108b9"
|
private val hashBeforeReorg = "09ec0d5de30d290bc5a2318fbf6a2427a81c7db4790ce0e341a96aeac77108b9"
|
||||||
private val hashAfterReorg = "tbd"
|
private val hashAfterReorg = "tbd"
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package cash.z.ecc.android.sdk.darkside.test
|
package cash.z.ecc.android.sdk.darkside.test
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import cash.z.ecc.android.sdk.R
|
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.Darkside
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.wallet.sdk.rpc.Darkside
|
import cash.z.wallet.sdk.rpc.Darkside
|
||||||
import cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL
|
import cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL
|
||||||
import cash.z.wallet.sdk.rpc.DarksideStreamerGrpc
|
import cash.z.wallet.sdk.rpc.DarksideStreamerGrpc
|
||||||
@@ -22,17 +24,11 @@ class DarksideApi(
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
appContext: Context,
|
appContext: Context,
|
||||||
host: String,
|
lightWalletEndpoint: LightWalletEndpoint
|
||||||
port: Int = ZcashNetwork.Mainnet.defaultPort,
|
|
||||||
usePlainText: Boolean = appContext.resources.getBoolean(
|
|
||||||
R.bool.lightwalletd_allow_very_insecure_connections
|
|
||||||
)
|
|
||||||
) : this(
|
) : this(
|
||||||
LightWalletGrpcService.createDefaultChannel(
|
LightWalletGrpcService.createDefaultChannel(
|
||||||
appContext,
|
appContext,
|
||||||
host,
|
lightWalletEndpoint
|
||||||
port,
|
|
||||||
usePlainText
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,7 +37,7 @@ class DarksideApi(
|
|||||||
//
|
//
|
||||||
|
|
||||||
fun reset(
|
fun reset(
|
||||||
saplingActivationHeight: Int = 419200,
|
saplingActivationHeight: BlockHeight = ZcashNetwork.Mainnet.saplingActivationHeight,
|
||||||
branchId: String = "e9ff75a6", // Canopy,
|
branchId: String = "e9ff75a6", // Canopy,
|
||||||
chainName: String = "darkside${ZcashNetwork.Mainnet.networkName}"
|
chainName: String = "darkside${ZcashNetwork.Mainnet.networkName}"
|
||||||
) = apply {
|
) = apply {
|
||||||
@@ -49,7 +45,7 @@ class DarksideApi(
|
|||||||
Darkside.DarksideMetaState.newBuilder()
|
Darkside.DarksideMetaState.newBuilder()
|
||||||
.setBranchID(branchId)
|
.setBranchID(branchId)
|
||||||
.setChainName(chainName)
|
.setChainName(chainName)
|
||||||
.setSaplingActivation(saplingActivationHeight)
|
.setSaplingActivation(saplingActivationHeight.value.toInt())
|
||||||
.build().let { request ->
|
.build().let { request ->
|
||||||
createStub().reset(request)
|
createStub().reset(request)
|
||||||
}
|
}
|
||||||
@@ -60,21 +56,21 @@ class DarksideApi(
|
|||||||
createStub().stageBlocks(url.toUrl())
|
createStub().stageBlocks(url.toUrl())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stageTransactions(url: String, targetHeight: Int) = apply {
|
fun stageTransactions(url: String, targetHeight: BlockHeight) = apply {
|
||||||
twig("staging transaction at height=$targetHeight from url=$url")
|
twig("staging transaction at height=$targetHeight from url=$url")
|
||||||
createStub().stageTransactions(
|
createStub().stageTransactions(
|
||||||
DarksideTransactionsURL.newBuilder().setHeight(targetHeight).setUrl(url).build()
|
DarksideTransactionsURL.newBuilder().setHeight(targetHeight.value).setUrl(url).build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stageEmptyBlocks(startHeight: Int, count: Int = 10, nonce: Int = Random.nextInt()) = apply {
|
fun stageEmptyBlocks(startHeight: BlockHeight, count: Int = 10, nonce: Int = Random.nextInt()) = apply {
|
||||||
twig("staging $count empty blocks starting at $startHeight with nonce $nonce")
|
twig("staging $count empty blocks starting at $startHeight with nonce $nonce")
|
||||||
createStub().stageBlocksCreate(
|
createStub().stageBlocksCreate(
|
||||||
Darkside.DarksideEmptyBlocks.newBuilder().setHeight(startHeight).setCount(count).setNonce(nonce).build()
|
Darkside.DarksideEmptyBlocks.newBuilder().setHeight(startHeight.value).setCount(count).setNonce(nonce).build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stageTransactions(txs: Iterator<Service.RawTransaction>?, tipHeight: Int) {
|
fun stageTransactions(txs: Iterator<Service.RawTransaction>?, tipHeight: BlockHeight) {
|
||||||
if (txs == null) {
|
if (txs == null) {
|
||||||
twig("no transactions to stage")
|
twig("no transactions to stage")
|
||||||
return
|
return
|
||||||
@@ -84,7 +80,7 @@ class DarksideApi(
|
|||||||
createStreamingStub().stageTransactionsStream(response).apply {
|
createStreamingStub().stageTransactionsStream(response).apply {
|
||||||
txs.forEach {
|
txs.forEach {
|
||||||
twig("stageTransactions: onNext calling!!!")
|
twig("stageTransactions: onNext calling!!!")
|
||||||
onNext(it.newBuilderForType().setData(it.data).setHeight(tipHeight.toLong()).build()) // apply the tipHeight because the passed in txs might not know their destination height (if they were created via SendTransaction)
|
onNext(it.newBuilderForType().setData(it.data).setHeight(tipHeight.value).build()) // apply the tipHeight because the passed in txs might not know their destination height (if they were created via SendTransaction)
|
||||||
twig("stageTransactions: onNext called")
|
twig("stageTransactions: onNext called")
|
||||||
}
|
}
|
||||||
twig("stageTransactions: onCompleted calling!!!")
|
twig("stageTransactions: onCompleted calling!!!")
|
||||||
@@ -94,7 +90,7 @@ class DarksideApi(
|
|||||||
response.await()
|
response.await()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun applyBlocks(tipHeight: Int) {
|
fun applyBlocks(tipHeight: BlockHeight) {
|
||||||
twig("applying blocks up to tipHeight=$tipHeight")
|
twig("applying blocks up to tipHeight=$tipHeight")
|
||||||
createStub().applyStaged(tipHeight.toHeight())
|
createStub().applyStaged(tipHeight.toHeight())
|
||||||
}
|
}
|
||||||
@@ -146,7 +142,7 @@ class DarksideApi(
|
|||||||
.withDeadlineAfter(singleRequestTimeoutSec, TimeUnit.SECONDS)
|
.withDeadlineAfter(singleRequestTimeoutSec, TimeUnit.SECONDS)
|
||||||
|
|
||||||
private fun String.toUrl() = Darkside.DarksideBlocksURL.newBuilder().setUrl(this).build()
|
private fun String.toUrl() = Darkside.DarksideBlocksURL.newBuilder().setUrl(this).build()
|
||||||
private fun Int.toHeight() = Darkside.DarksideHeight.newBuilder().setHeight(this).build()
|
private fun BlockHeight.toHeight() = Darkside.DarksideHeight.newBuilder().setHeight(this.value).build()
|
||||||
|
|
||||||
class EmptyResponse : StreamObserver<Service.Empty> {
|
class EmptyResponse : StreamObserver<Service.Empty> {
|
||||||
var completed = false
|
var completed = false
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import androidx.test.platform.app.InstrumentationRegistry
|
|||||||
import cash.z.ecc.android.sdk.SdkSynchronizer
|
import cash.z.ecc.android.sdk.SdkSynchronizer
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.Darkside
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import io.grpc.StatusRuntimeException
|
import io.grpc.StatusRuntimeException
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
@@ -14,19 +17,19 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
|
|
||||||
class DarksideTestCoordinator(val wallet: TestWallet) {
|
class DarksideTestCoordinator(val wallet: TestWallet) {
|
||||||
constructor(
|
constructor(
|
||||||
alias: String = "DarksideTestCoordinator",
|
alias: String = "DarksideTestCoordinator",
|
||||||
seedPhrase: String = DEFAULT_SEED_PHRASE,
|
seedPhrase: String = DEFAULT_SEED_PHRASE,
|
||||||
startHeight: Int = DEFAULT_START_HEIGHT,
|
startHeight: BlockHeight = DEFAULT_START_HEIGHT,
|
||||||
host: String = COMPUTER_LOCALHOST,
|
|
||||||
network: ZcashNetwork = ZcashNetwork.Mainnet,
|
network: ZcashNetwork = ZcashNetwork.Mainnet,
|
||||||
port: Int = network.defaultPort
|
endpoint: LightWalletEndpoint = LightWalletEndpoint.Darkside
|
||||||
) : this(TestWallet(seedPhrase, alias, network, host, startHeight = startHeight, port = port))
|
) : this(TestWallet(seedPhrase, alias, network, endpoint, startHeight = startHeight))
|
||||||
|
|
||||||
private val targetHeight = 663250
|
private val targetHeight = BlockHeight.new(wallet.network, 663250)
|
||||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
|
||||||
// dependencies: private
|
// dependencies: private
|
||||||
@@ -91,20 +94,20 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
|||||||
* Waits for, at most, the given amount of time for the synchronizer to download and scan blocks
|
* Waits for, at most, the given amount of time for the synchronizer to download and scan blocks
|
||||||
* and reach a 'SYNCED' status.
|
* and reach a 'SYNCED' status.
|
||||||
*/
|
*/
|
||||||
fun await(timeout: Long = 60_000L, targetHeight: Int = -1) = runBlocking {
|
fun await(timeout: Long = 60_000L, targetHeight: BlockHeight? = null) = runBlocking {
|
||||||
ScopedTest.timeoutWith(this, timeout) {
|
ScopedTest.timeoutWith(this, timeout) {
|
||||||
twig("*** Waiting up to ${timeout / 1_000}s for sync ***")
|
twig("*** Waiting up to ${timeout / 1_000}s for sync ***")
|
||||||
synchronizer.status.onEach {
|
synchronizer.status.onEach {
|
||||||
twig("got processor status $it")
|
twig("got processor status $it")
|
||||||
if (it == Synchronizer.Status.DISCONNECTED) {
|
if (it == Synchronizer.Status.DISCONNECTED) {
|
||||||
twig("waiting a bit before giving up on connection...")
|
twig("waiting a bit before giving up on connection...")
|
||||||
} else if (targetHeight != -1 && (synchronizer as SdkSynchronizer).processor.getLastScannedHeight() < targetHeight) {
|
} else if (targetHeight != null && (synchronizer as SdkSynchronizer).processor.getLastScannedHeight() < targetHeight) {
|
||||||
twig("awaiting new blocks from server...")
|
twig("awaiting new blocks from server...")
|
||||||
}
|
}
|
||||||
}.map {
|
}.map {
|
||||||
// whenever we're waiting for a target height, for simplicity, if we're sleeping,
|
// whenever we're waiting for a target height, for simplicity, if we're sleeping,
|
||||||
// and in between polls, then consider it that we're not synced
|
// and in between polls, then consider it that we're not synced
|
||||||
if (targetHeight != -1 && (synchronizer as SdkSynchronizer).processor.getLastScannedHeight() < targetHeight) {
|
if (targetHeight != null && (synchronizer as SdkSynchronizer).processor.getLastScannedHeight() < targetHeight) {
|
||||||
twig("switching status to DOWNLOADING because we're still waiting for height $targetHeight")
|
twig("switching status to DOWNLOADING because we're still waiting for height $targetHeight")
|
||||||
Synchronizer.Status.DOWNLOADING
|
Synchronizer.Status.DOWNLOADING
|
||||||
} else {
|
} else {
|
||||||
@@ -140,14 +143,14 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
|||||||
|
|
||||||
inner class DarksideTestValidator {
|
inner class DarksideTestValidator {
|
||||||
|
|
||||||
fun validateHasBlock(height: Int) {
|
fun validateHasBlock(height: BlockHeight) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
assertTrue((synchronizer as SdkSynchronizer).findBlockHashAsHex(height) != null)
|
assertTrue((synchronizer as SdkSynchronizer).findBlockHashAsHex(height) != null)
|
||||||
assertTrue((synchronizer as SdkSynchronizer).findBlockHash(height)?.size ?: 0 > 0)
|
assertTrue((synchronizer as SdkSynchronizer).findBlockHash(height)?.size ?: 0 > 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateLatestHeight(height: Int) = runBlocking<Unit> {
|
fun validateLatestHeight(height: BlockHeight) = runBlocking<Unit> {
|
||||||
val info = synchronizer.processorInfo.first()
|
val info = synchronizer.processorInfo.first()
|
||||||
val networkBlockHeight = info.networkBlockHeight
|
val networkBlockHeight = info.networkBlockHeight
|
||||||
assertTrue(
|
assertTrue(
|
||||||
@@ -157,41 +160,44 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateMinHeightDownloaded(minHeight: Int) = runBlocking<Unit> {
|
fun validateMinHeightDownloaded(minHeight: BlockHeight) = runBlocking<Unit> {
|
||||||
val info = synchronizer.processorInfo.first()
|
val info = synchronizer.processorInfo.first()
|
||||||
val lastDownloadedHeight = info.lastDownloadedHeight
|
val lastDownloadedHeight = info.lastDownloadedHeight
|
||||||
|
assertNotNull(lastDownloadedHeight)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
"Expected to have at least downloaded $minHeight but the last downloaded block was" +
|
"Expected to have at least downloaded $minHeight but the last downloaded block was" +
|
||||||
" $lastDownloadedHeight! Full details: $info",
|
" $lastDownloadedHeight! Full details: $info",
|
||||||
lastDownloadedHeight >= minHeight
|
lastDownloadedHeight!! >= minHeight
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateMinHeightScanned(minHeight: Int) = runBlocking<Unit> {
|
fun validateMinHeightScanned(minHeight: BlockHeight) = runBlocking<Unit> {
|
||||||
val info = synchronizer.processorInfo.first()
|
val info = synchronizer.processorInfo.first()
|
||||||
val lastScannedHeight = info.lastScannedHeight
|
val lastScannedHeight = info.lastScannedHeight
|
||||||
|
assertNotNull(lastScannedHeight)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
"Expected to have at least scanned $minHeight but the last scanned block was" +
|
"Expected to have at least scanned $minHeight but the last scanned block was" +
|
||||||
" $lastScannedHeight! Full details: $info",
|
" $lastScannedHeight! Full details: $info",
|
||||||
lastScannedHeight >= minHeight
|
lastScannedHeight!! >= minHeight
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateMaxHeightScanned(maxHeight: Int) = runBlocking<Unit> {
|
fun validateMaxHeightScanned(maxHeight: BlockHeight) = runBlocking<Unit> {
|
||||||
val lastDownloadedHeight = synchronizer.processorInfo.first().lastScannedHeight
|
val lastDownloadedHeight = synchronizer.processorInfo.first().lastScannedHeight
|
||||||
|
assertNotNull(lastDownloadedHeight)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
"Did not expect to be synced beyond $maxHeight but we are synced to" +
|
"Did not expect to be synced beyond $maxHeight but we are synced to" +
|
||||||
" $lastDownloadedHeight",
|
" $lastDownloadedHeight",
|
||||||
lastDownloadedHeight <= maxHeight
|
lastDownloadedHeight!! <= maxHeight
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateBlockHash(height: Int, expectedHash: String) {
|
fun validateBlockHash(height: BlockHeight, expectedHash: String) {
|
||||||
val hash = runBlocking { (synchronizer as SdkSynchronizer).findBlockHashAsHex(height) }
|
val hash = runBlocking { (synchronizer as SdkSynchronizer).findBlockHashAsHex(height) }
|
||||||
assertEquals(expectedHash, hash)
|
assertEquals(expectedHash, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onReorg(callback: (errorHeight: Int, rewindHeight: Int) -> Unit) {
|
fun onReorg(callback: (errorHeight: BlockHeight, rewindHeight: BlockHeight) -> Unit) {
|
||||||
synchronizer.onChainErrorHandler = callback
|
synchronizer.onChainErrorHandler = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +231,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
|||||||
//
|
//
|
||||||
|
|
||||||
inner class DarksideChainMaker {
|
inner class DarksideChainMaker {
|
||||||
var lastTipHeight = -1
|
var lastTipHeight: BlockHeight? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the darksidelightwalletd server, stages the blocks represented by the given URL, then
|
* Resets the darksidelightwalletd server, stages the blocks represented by the given URL, then
|
||||||
@@ -233,8 +239,8 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
|||||||
*/
|
*/
|
||||||
fun resetBlocks(
|
fun resetBlocks(
|
||||||
blocksUrl: String,
|
blocksUrl: String,
|
||||||
startHeight: Int = DEFAULT_START_HEIGHT,
|
startHeight: BlockHeight = DEFAULT_START_HEIGHT,
|
||||||
tipHeight: Int = startHeight + 100
|
tipHeight: BlockHeight = startHeight + 100
|
||||||
): DarksideChainMaker = apply {
|
): DarksideChainMaker = apply {
|
||||||
darkside
|
darkside
|
||||||
.reset(startHeight)
|
.reset(startHeight)
|
||||||
@@ -242,23 +248,23 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
|||||||
applyTipHeight(tipHeight)
|
applyTipHeight(tipHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stageTransaction(url: String, targetHeight: Int): DarksideChainMaker = apply {
|
fun stageTransaction(url: String, targetHeight: BlockHeight): DarksideChainMaker = apply {
|
||||||
darkside.stageTransactions(url, targetHeight)
|
darkside.stageTransactions(url, targetHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stageTransactions(targetHeight: Int, vararg urls: String): DarksideChainMaker = apply {
|
fun stageTransactions(targetHeight: BlockHeight, vararg urls: String): DarksideChainMaker = apply {
|
||||||
urls.forEach {
|
urls.forEach {
|
||||||
darkside.stageTransactions(it, targetHeight)
|
darkside.stageTransactions(it, targetHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stageEmptyBlocks(startHeight: Int, count: Int = 10): DarksideChainMaker = apply {
|
fun stageEmptyBlocks(startHeight: BlockHeight, count: Int = 10): DarksideChainMaker = apply {
|
||||||
darkside.stageEmptyBlocks(startHeight, count)
|
darkside.stageEmptyBlocks(startHeight, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stageEmptyBlock() = stageEmptyBlocks(lastTipHeight + 1, 1)
|
fun stageEmptyBlock() = stageEmptyBlocks(lastTipHeight!! + 1, 1)
|
||||||
|
|
||||||
fun applyTipHeight(tipHeight: Int): DarksideChainMaker = apply {
|
fun applyTipHeight(tipHeight: BlockHeight): DarksideChainMaker = apply {
|
||||||
twig("applying tip height of $tipHeight")
|
twig("applying tip height of $tipHeight")
|
||||||
darkside.applyBlocks(tipHeight)
|
darkside.applyBlocks(tipHeight)
|
||||||
lastTipHeight = tipHeight
|
lastTipHeight = tipHeight
|
||||||
@@ -277,14 +283,14 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun advanceBy(numEmptyBlocks: Int) {
|
fun advanceBy(numEmptyBlocks: Int) {
|
||||||
val nextBlock = lastTipHeight + 1
|
val nextBlock = lastTipHeight!! + 1
|
||||||
twig("adding $numEmptyBlocks empty blocks to the chain starting at $nextBlock")
|
twig("adding $numEmptyBlocks empty blocks to the chain starting at $nextBlock")
|
||||||
darkside.stageEmptyBlocks(nextBlock, numEmptyBlocks)
|
darkside.stageEmptyBlocks(nextBlock, numEmptyBlocks)
|
||||||
applyTipHeight(nextBlock + numEmptyBlocks)
|
applyTipHeight(nextBlock + numEmptyBlocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun applyPendingTransactions(targetHeight: Int = lastTipHeight + 1) {
|
fun applyPendingTransactions(targetHeight: BlockHeight = lastTipHeight!! + 1) {
|
||||||
stageEmptyBlocks(lastTipHeight + 1, targetHeight - lastTipHeight)
|
stageEmptyBlocks(lastTipHeight!! + 1, (targetHeight.value - lastTipHeight!!.value).toInt())
|
||||||
darkside.stageTransactions(darkside.getSentTransactions()?.iterator(), targetHeight)
|
darkside.stageTransactions(darkside.getSentTransactions()?.iterator(), targetHeight)
|
||||||
applyTipHeight(targetHeight)
|
applyTipHeight(targetHeight)
|
||||||
}
|
}
|
||||||
@@ -304,7 +310,7 @@ class DarksideTestCoordinator(val wallet: TestWallet) {
|
|||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-small-reorg.txt"
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-small-reorg.txt"
|
||||||
private const val largeReorg =
|
private const val largeReorg =
|
||||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-large-reorg.txt"
|
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-large-reorg.txt"
|
||||||
private const val DEFAULT_START_HEIGHT = 663150
|
private val DEFAULT_START_HEIGHT = BlockHeight.new(ZcashNetwork.Mainnet, 663150)
|
||||||
private const val DEFAULT_SEED_PHRASE =
|
private const val DEFAULT_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"
|
"still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,13 @@ import cash.z.ecc.android.sdk.db.entity.isPending
|
|||||||
import cash.z.ecc.android.sdk.internal.Twig
|
import cash.z.ecc.android.sdk.internal.Twig
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.Darkside
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.type.WalletBalance
|
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -35,9 +38,8 @@ class TestWallet(
|
|||||||
val seedPhrase: String,
|
val seedPhrase: String,
|
||||||
val alias: String = "TestWallet",
|
val alias: String = "TestWallet",
|
||||||
val network: ZcashNetwork = ZcashNetwork.Testnet,
|
val network: ZcashNetwork = ZcashNetwork.Testnet,
|
||||||
val host: String = network.defaultHost,
|
val endpoint: LightWalletEndpoint = LightWalletEndpoint.Darkside,
|
||||||
startHeight: Int? = null,
|
startHeight: BlockHeight? = null
|
||||||
val port: Int = network.defaultPort
|
|
||||||
) {
|
) {
|
||||||
constructor(
|
constructor(
|
||||||
backup: Backups,
|
backup: Backups,
|
||||||
@@ -65,7 +67,7 @@ class TestWallet(
|
|||||||
runBlocking { DerivationTool.deriveTransparentSecretKey(seed, network = network) }
|
runBlocking { DerivationTool.deriveTransparentSecretKey(seed, network = network) }
|
||||||
val initializer = runBlocking {
|
val initializer = runBlocking {
|
||||||
Initializer.new(context) { config ->
|
Initializer.new(context) { config ->
|
||||||
runBlocking { config.importWallet(seed, startHeight, network, host, alias = alias) }
|
runBlocking { config.importWallet(seed, startHeight, network, endpoint, alias = alias) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val synchronizer: SdkSynchronizer = runBlocking { Synchronizer.new(initializer) } as SdkSynchronizer
|
val synchronizer: SdkSynchronizer = runBlocking { Synchronizer.new(initializer) } as SdkSynchronizer
|
||||||
@@ -78,14 +80,11 @@ class TestWallet(
|
|||||||
runBlocking { DerivationTool.deriveTransparentAddress(seed, network = network) }
|
runBlocking { DerivationTool.deriveTransparentAddress(seed, network = network) }
|
||||||
val birthdayHeight get() = synchronizer.latestBirthdayHeight
|
val birthdayHeight get() = synchronizer.latestBirthdayHeight
|
||||||
val networkName get() = synchronizer.network.networkName
|
val networkName get() = synchronizer.network.networkName
|
||||||
val connectionInfo get() = service.connectionInfo.toString()
|
|
||||||
|
|
||||||
/* NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
suspend fun transparentBalance(): WalletBalance {
|
suspend fun transparentBalance(): WalletBalance {
|
||||||
synchronizer.refreshUtxos(transparentAddress, synchronizer.latestBirthdayHeight)
|
synchronizer.refreshUtxos(transparentAddress, synchronizer.latestBirthdayHeight)
|
||||||
return synchronizer.getTransparentBalance(transparentAddress)
|
return synchronizer.getTransparentBalance(transparentAddress)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
suspend fun sync(timeout: Long = -1): TestWallet {
|
suspend fun sync(timeout: Long = -1): TestWallet {
|
||||||
val killSwitch = walletScope.launch {
|
val killSwitch = walletScope.launch {
|
||||||
@@ -111,7 +110,7 @@ class TestWallet(
|
|||||||
suspend fun send(address: String = transparentAddress, memo: String = "", amount: Zatoshi = Zatoshi(500L), fromAccountIndex: Int = 0): TestWallet {
|
suspend fun send(address: String = transparentAddress, memo: String = "", amount: Zatoshi = Zatoshi(500L), fromAccountIndex: Int = 0): TestWallet {
|
||||||
Twig.sprout("$alias sending")
|
Twig.sprout("$alias sending")
|
||||||
synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo, fromAccountIndex)
|
synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo, fromAccountIndex)
|
||||||
.takeWhile { it.isPending() }
|
.takeWhile { it.isPending(null) }
|
||||||
.collect {
|
.collect {
|
||||||
twig("Updated transaction: $it")
|
twig("Updated transaction: $it")
|
||||||
}
|
}
|
||||||
@@ -119,15 +118,14 @@ class TestWallet(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun rewindToHeight(height: Int): TestWallet {
|
suspend fun rewindToHeight(height: BlockHeight): TestWallet {
|
||||||
synchronizer.rewindToNearestHeight(height, false)
|
synchronizer.rewindToNearestHeight(height, false)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
suspend fun shieldFunds(): TestWallet {
|
suspend fun shieldFunds(): TestWallet {
|
||||||
twig("checking $transparentAddress for transactions!")
|
twig("checking $transparentAddress for transactions!")
|
||||||
synchronizer.refreshUtxos(transparentAddress, 935000).let { count ->
|
synchronizer.refreshUtxos(transparentAddress, BlockHeight.new(ZcashNetwork.Mainnet, 935000)).let { count ->
|
||||||
twig("FOUND $count new UTXOs")
|
twig("FOUND $count new UTXOs")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +142,6 @@ class TestWallet(
|
|||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
suspend fun join(timeout: Long? = null): TestWallet {
|
suspend fun join(timeout: Long? = null): TestWallet {
|
||||||
// block until stopped
|
// block until stopped
|
||||||
@@ -167,13 +164,48 @@ class TestWallet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Backups(val seedPhrase: String, val testnetBirthday: Int, val mainnetBirthday: Int) {
|
enum class Backups(val seedPhrase: String, val testnetBirthday: BlockHeight, val mainnetBirthday: BlockHeight) {
|
||||||
// TODO: get the proper birthday values for these wallets
|
// TODO: get the proper birthday values for these wallets
|
||||||
DEFAULT("column rhythm acoustic gym cost fit keen maze fence seed mail medal shrimp tell relief clip cannon foster soldier shallow refuse lunar parrot banana", 1_355_928, 1_000_000),
|
DEFAULT(
|
||||||
SAMPLE_WALLET("input frown warm senior anxiety abuse yard prefer churn reject people glimpse govern glory crumble swallow verb laptop switch trophy inform friend permit purpose", 1_330_190, 1_000_000),
|
"column rhythm acoustic gym cost fit keen maze fence seed mail medal shrimp tell relief clip cannon foster soldier shallow refuse lunar parrot banana",
|
||||||
DEV_WALLET("still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread", 1_000_000, 991645),
|
BlockHeight.new(
|
||||||
ALICE("quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely life cliff spirit admit weekend message recipe trumpet impact kitten", 1_330_190, 1_000_000),
|
ZcashNetwork.Testnet,
|
||||||
BOB("canvas wine sugar acquire garment spy tongue odor hole cage year habit bullet make label human unit option top calm neutral try vocal arena", 1_330_190, 1_000_000),
|
1_355_928
|
||||||
|
),
|
||||||
|
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
|
||||||
|
),
|
||||||
|
SAMPLE_WALLET(
|
||||||
|
"input frown warm senior anxiety abuse yard prefer churn reject people glimpse govern glory crumble swallow verb laptop switch trophy inform friend permit purpose",
|
||||||
|
BlockHeight.new(
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
1_330_190
|
||||||
|
),
|
||||||
|
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
|
||||||
|
),
|
||||||
|
DEV_WALLET(
|
||||||
|
"still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread",
|
||||||
|
BlockHeight.new(
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
1_000_000
|
||||||
|
),
|
||||||
|
BlockHeight.new(ZcashNetwork.Mainnet, 991645)
|
||||||
|
),
|
||||||
|
ALICE(
|
||||||
|
"quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely life cliff spirit admit weekend message recipe trumpet impact kitten",
|
||||||
|
BlockHeight.new(
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
1_330_190
|
||||||
|
),
|
||||||
|
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
|
||||||
|
),
|
||||||
|
BOB(
|
||||||
|
"canvas wine sugar acquire garment spy tongue odor hole cage year habit bullet make label human unit option top calm neutral try vocal arena",
|
||||||
|
BlockHeight.new(
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
1_330_190
|
||||||
|
),
|
||||||
|
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
|
||||||
|
),
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
|||||||
import cash.z.ecc.android.sdk.internal.Twig
|
import cash.z.ecc.android.sdk.internal.Twig
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.Mainnet
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
@@ -86,17 +89,20 @@ class SampleCodeTest {
|
|||||||
// ///////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////
|
||||||
// Query latest block height
|
// Query latest block height
|
||||||
@Test fun getLatestBlockHeightTest() {
|
@Test fun getLatestBlockHeightTest() {
|
||||||
val lightwalletService = LightWalletGrpcService(context, lightwalletdHost)
|
val lightwalletService = LightWalletGrpcService.new(context, lightwalletdHost)
|
||||||
log("Latest Block: ${lightwalletService.getLatestBlockHeight()}")
|
log("Latest Block: ${lightwalletService.getLatestBlockHeight()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ///////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////
|
||||||
// Download compact block range
|
// Download compact block range
|
||||||
@Test fun getBlockRange() {
|
@Test fun getBlockRange() {
|
||||||
val blockRange = 500_000..500_009
|
val blockRange = BlockHeight.new(ZcashNetwork.Mainnet, 500_000)..BlockHeight.new(
|
||||||
val lightwalletService = LightWalletGrpcService(context, lightwalletdHost)
|
ZcashNetwork.Mainnet,
|
||||||
|
500_009
|
||||||
|
)
|
||||||
|
val lightwalletService = LightWalletGrpcService.new(context, lightwalletdHost)
|
||||||
val blocks = lightwalletService.getBlockRange(blockRange)
|
val blocks = lightwalletService.getBlockRange(blockRange)
|
||||||
assertEquals(blockRange.count(), blocks.size)
|
assertEquals(blockRange.endInclusive.value - blockRange.start.value, blocks.count())
|
||||||
|
|
||||||
blocks.forEachIndexed { i, block ->
|
blocks.forEachIndexed { i, block ->
|
||||||
log("Block #$i: height:${block.height} hash:${block.hash.toByteArray().toHex()}")
|
log("Block #$i: height:${block.height} hash:${block.hash.toByteArray().toHex()}")
|
||||||
@@ -140,7 +146,7 @@ class SampleCodeTest {
|
|||||||
val transactionFlow = synchronizer.sendToAddress(spendingKey, amount, address, memo)
|
val transactionFlow = synchronizer.sendToAddress(spendingKey, amount, address, memo)
|
||||||
transactionFlow.collect {
|
transactionFlow.collect {
|
||||||
log("pending transaction updated $it")
|
log("pending transaction updated $it")
|
||||||
assertTrue("Failed to send funds. See log for details.", !it?.isFailure())
|
assertTrue("Failed to send funds. See log for details.", !it.isFailure())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +156,7 @@ class SampleCodeTest {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val seed = "Insert seed for testing".toByteArray()
|
private val seed = "Insert seed for testing".toByteArray()
|
||||||
private val lightwalletdHost: String = ZcashNetwork.Mainnet.defaultHost
|
private val lightwalletdHost = LightWalletEndpoint.Mainnet
|
||||||
|
|
||||||
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
private val synchronizer: Synchronizer = run {
|
private val synchronizer: Synchronizer = run {
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ import androidx.viewbinding.ViewBinding
|
|||||||
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import cash.z.ecc.android.sdk.model.defaultForNetwork
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import com.google.android.material.navigation.NavigationView
|
import com.google.android.material.navigation.NavigationView
|
||||||
|
|
||||||
@@ -108,7 +110,11 @@ class MainActivity :
|
|||||||
if (lightwalletService != null) {
|
if (lightwalletService != null) {
|
||||||
lightwalletService?.shutdown()
|
lightwalletService?.shutdown()
|
||||||
}
|
}
|
||||||
lightwalletService = LightWalletGrpcService(applicationContext, ZcashNetwork.fromResources(applicationContext))
|
val network = ZcashNetwork.fromResources(applicationContext)
|
||||||
|
lightwalletService = LightWalletGrpcService.new(
|
||||||
|
applicationContext,
|
||||||
|
LightWalletEndpoint.defaultForNetwork(network)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onFabClicked(view: View) {
|
private fun onFabClicked(view: View) {
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
|||||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetAddressBinding
|
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetAddressBinding
|
||||||
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
|
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
|
||||||
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
|
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
|
|||||||
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
||||||
import cash.z.ecc.android.sdk.ext.collectWith
|
import cash.z.ecc.android.sdk.ext.collectWith
|
||||||
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
|
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import cash.z.ecc.android.sdk.model.defaultForNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.type.WalletBalance
|
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
@@ -50,8 +52,12 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
|
|||||||
// using the ViewingKey to initialize
|
// using the ViewingKey to initialize
|
||||||
runBlocking {
|
runBlocking {
|
||||||
Initializer.new(requireApplicationContext(), null) {
|
Initializer.new(requireApplicationContext(), null) {
|
||||||
it.setNetwork(ZcashNetwork.fromResources(requireApplicationContext()))
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
it.importWallet(viewingKey, network = ZcashNetwork.fromResources(requireApplicationContext()))
|
it.newWallet(
|
||||||
|
viewingKey,
|
||||||
|
network = network,
|
||||||
|
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}.let { initializer ->
|
}.let { initializer ->
|
||||||
synchronizer = Synchronizer.newBlocking(initializer)
|
synchronizer = Synchronizer.newBlocking(initializer)
|
||||||
@@ -81,7 +87,7 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
|
|||||||
|
|
||||||
private fun onStatus(status: Synchronizer.Status) {
|
private fun onStatus(status: Synchronizer.Status) {
|
||||||
binding.textStatus.text = "Status: $status"
|
binding.textStatus.text = "Status: $status"
|
||||||
val balance = synchronizer.saplingBalances.value
|
val balance: WalletBalance? = synchronizer.saplingBalances.value
|
||||||
if (null == balance) {
|
if (null == balance) {
|
||||||
binding.textBalance.text = "Calculating balance..."
|
binding.textBalance.text = "Calculating balance..."
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,11 +7,15 @@ import android.view.View
|
|||||||
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
||||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockBinding
|
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockBinding
|
||||||
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
|
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
|
||||||
|
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
||||||
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
|
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
|
||||||
import cash.z.ecc.android.sdk.demoapp.util.toHtml
|
import cash.z.ecc.android.sdk.demoapp.util.toHtml
|
||||||
import cash.z.ecc.android.sdk.demoapp.util.toRelativeTime
|
import cash.z.ecc.android.sdk.demoapp.util.toRelativeTime
|
||||||
import cash.z.ecc.android.sdk.demoapp.util.withCommas
|
import cash.z.ecc.android.sdk.demoapp.util.withCommas
|
||||||
import cash.z.ecc.android.sdk.ext.toHex
|
import cash.z.ecc.android.sdk.ext.toHex
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a compact block from the lightwalletd service and displays basic information about it.
|
* Retrieves a compact block from the lightwalletd service and displays basic information about it.
|
||||||
@@ -20,7 +24,7 @@ import cash.z.ecc.android.sdk.ext.toHex
|
|||||||
*/
|
*/
|
||||||
class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
|
class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
|
||||||
|
|
||||||
private fun setBlockHeight(blockHeight: Int) {
|
private fun setBlockHeight(blockHeight: BlockHeight) {
|
||||||
val blocks =
|
val blocks =
|
||||||
lightwalletService?.getBlockRange(blockHeight..blockHeight)
|
lightwalletService?.getBlockRange(blockHeight..blockHeight)
|
||||||
val block = blocks?.firstOrNull()
|
val block = blocks?.firstOrNull()
|
||||||
@@ -38,8 +42,11 @@ class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onApply(_unused: View? = null) {
|
private fun onApply(_unused: View? = null) {
|
||||||
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
|
val newHeight = min(binding.textBlockHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setBlockHeight(binding.textBlockHeight.text.toString().toInt())
|
setBlockHeight(BlockHeight.new(network, newHeight))
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
toast("Error: $t")
|
toast("Error: $t")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,13 @@ import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
|||||||
import cash.z.ecc.android.sdk.demoapp.R
|
import cash.z.ecc.android.sdk.demoapp.R
|
||||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockRangeBinding
|
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockRangeBinding
|
||||||
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
|
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
|
||||||
|
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
||||||
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
|
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
|
||||||
import cash.z.ecc.android.sdk.demoapp.util.toRelativeTime
|
import cash.z.ecc.android.sdk.demoapp.util.toRelativeTime
|
||||||
import cash.z.ecc.android.sdk.demoapp.util.withCommas
|
import cash.z.ecc.android.sdk.demoapp.util.withCommas
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a range of compact block from the lightwalletd service and displays basic information
|
* Retrieves a range of compact block from the lightwalletd service and displays basic information
|
||||||
@@ -20,16 +24,17 @@ import cash.z.ecc.android.sdk.demoapp.util.withCommas
|
|||||||
*/
|
*/
|
||||||
class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
|
class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
|
||||||
|
|
||||||
private fun setBlockRange(blockRange: IntRange) {
|
private fun setBlockRange(blockRange: ClosedRange<BlockHeight>) {
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
val blocks =
|
val blocks =
|
||||||
lightwalletService?.getBlockRange(blockRange)
|
lightwalletService?.getBlockRange(blockRange)
|
||||||
val fetchDelta = System.currentTimeMillis() - start
|
val fetchDelta = System.currentTimeMillis() - start
|
||||||
|
|
||||||
// Note: This is a demo so we won't worry about iterating efficiently over these blocks
|
// Note: This is a demo so we won't worry about iterating efficiently over these blocks
|
||||||
|
// Note: Converting the blocks sequence to a list can consume a lot of memory and may
|
||||||
|
// cause OOM.
|
||||||
binding.textInfo.text = Html.fromHtml(
|
binding.textInfo.text = Html.fromHtml(
|
||||||
blocks?.run {
|
blocks?.toList()?.run {
|
||||||
val count = size
|
val count = size
|
||||||
val emptyCount = count { it.vtxCount == 0 }
|
val emptyCount = count { it.vtxCount == 0 }
|
||||||
val maxTxs = maxByOrNull { it.vtxCount }
|
val maxTxs = maxByOrNull { it.vtxCount }
|
||||||
@@ -41,9 +46,9 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
|
|||||||
block.vtxList.maxOfOrNull { it.outputsCount } ?: -1
|
block.vtxList.maxOfOrNull { it.outputsCount } ?: -1
|
||||||
}
|
}
|
||||||
val maxOutTx = maxOuts?.vtxList?.maxByOrNull { it.outputsCount }
|
val maxOutTx = maxOuts?.vtxList?.maxByOrNull { it.outputsCount }
|
||||||
val txCount = sumBy { it.vtxCount }
|
val txCount = sumOf { it.vtxCount }
|
||||||
val outCount = sumBy { block -> block.vtxList.sumBy { it.outputsCount } }
|
val outCount = sumOf { block -> block.vtxList.sumOf { it.outputsCount } }
|
||||||
val inCount = sumBy { block -> block.vtxList.sumBy { it.spendsCount } }
|
val inCount = sumOf { block -> block.vtxList.sumOf { it.spendsCount } }
|
||||||
|
|
||||||
val processTime = System.currentTimeMillis() - start - fetchDelta
|
val processTime = System.currentTimeMillis() - start - fetchDelta
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@@ -69,8 +74,9 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onApply(_unused: View) {
|
private fun onApply(_unused: View) {
|
||||||
val start = binding.textStartHeight.text.toString().toInt()
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
val end = binding.textEndHeight.text.toString().toInt()
|
val start = max(binding.textStartHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)
|
||||||
|
val end = max(binding.textEndHeight.text.toString().toLongOrNull() ?: network.saplingActivationHeight.value, network.saplingActivationHeight.value)
|
||||||
if (start <= end) {
|
if (start <= end) {
|
||||||
try {
|
try {
|
||||||
with(binding.buttonApply) {
|
with(binding.buttonApply) {
|
||||||
@@ -78,7 +84,7 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
|
|||||||
setText(R.string.loading)
|
setText(R.string.loading)
|
||||||
binding.textInfo.setText(R.string.loading)
|
binding.textInfo.setText(R.string.loading)
|
||||||
post {
|
post {
|
||||||
setBlockRange(start..end)
|
setBlockRange(BlockHeight.new(network, start)..BlockHeight.new(network, end))
|
||||||
isEnabled = true
|
isEnabled = true
|
||||||
setText(R.string.apply)
|
setText(R.string.apply)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
|||||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetPrivateKeyBinding
|
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetPrivateKeyBinding
|
||||||
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
|
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
|
||||||
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
|
|||||||
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
||||||
import cash.z.ecc.android.sdk.ext.collectWith
|
import cash.z.ecc.android.sdk.ext.collectWith
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import cash.z.ecc.android.sdk.model.defaultForNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,12 +50,20 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
|
|||||||
// have the seed stored
|
// have the seed stored
|
||||||
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
||||||
|
|
||||||
initializer = runBlocking {
|
initializer = Initializer.newBlocking(
|
||||||
Initializer.new(requireApplicationContext()) {
|
requireApplicationContext(),
|
||||||
runBlocking { it.importWallet(seed, network = ZcashNetwork.fromResources(requireApplicationContext())) }
|
Initializer.Config {
|
||||||
it.setNetwork(ZcashNetwork.fromResources(requireApplicationContext()))
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
|
runBlocking {
|
||||||
|
it.importWallet(
|
||||||
|
seed,
|
||||||
|
birthday = null,
|
||||||
|
network = network,
|
||||||
|
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
address = runBlocking {
|
address = runBlocking {
|
||||||
DerivationTool.deriveShieldedAddress(
|
DerivationTool.deriveShieldedAddress(
|
||||||
seed,
|
seed,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package cash.z.ecc.android.sdk.demoapp.demos.listutxos
|
package cash.z.ecc.android.sdk.demoapp.demos.listutxos
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -13,20 +14,23 @@ import cash.z.ecc.android.sdk.Synchronizer
|
|||||||
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
||||||
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||||
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
||||||
import cash.z.ecc.android.sdk.demoapp.DemoConstants
|
|
||||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentListUtxosBinding
|
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentListUtxosBinding
|
||||||
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
|
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
|
||||||
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
import cash.z.ecc.android.sdk.demoapp.util.fromResources
|
||||||
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
|
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
|
||||||
import cash.z.ecc.android.sdk.ext.collectWith
|
import cash.z.ecc.android.sdk.ext.collectWith
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import cash.z.ecc.android.sdk.model.defaultForNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ===============================================================================================
|
* ===============================================================================================
|
||||||
@@ -62,8 +66,15 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
|||||||
// have the seed stored
|
// have the seed stored
|
||||||
seed = Mnemonics.MnemonicCode(sharedViewModel.seedPhrase.value).toSeed()
|
seed = Mnemonics.MnemonicCode(sharedViewModel.seedPhrase.value).toSeed()
|
||||||
initializer = runBlocking {
|
initializer = runBlocking {
|
||||||
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
Initializer.new(requireApplicationContext()) {
|
Initializer.new(requireApplicationContext()) {
|
||||||
runBlocking { it.importWallet(seed, network = ZcashNetwork.fromResources(requireApplicationContext())) }
|
runBlocking {
|
||||||
|
it.newWallet(
|
||||||
|
seed,
|
||||||
|
network = network,
|
||||||
|
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
|
||||||
|
)
|
||||||
|
}
|
||||||
it.alias = "Demo_Utxos"
|
it.alias = "Demo_Utxos"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,38 +89,48 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
|||||||
fun initUi() {
|
fun initUi() {
|
||||||
binding.inputAddress.setText(address)
|
binding.inputAddress.setText(address)
|
||||||
binding.inputRangeStart.setText(ZcashNetwork.fromResources(requireApplicationContext()).saplingActivationHeight.toString())
|
binding.inputRangeStart.setText(ZcashNetwork.fromResources(requireApplicationContext()).saplingActivationHeight.toString())
|
||||||
binding.inputRangeEnd.setText(DemoConstants.utxoEndHeight.toString())
|
binding.inputRangeEnd.setText(getUxtoEndHeight(requireApplicationContext()).value.toString())
|
||||||
|
|
||||||
binding.buttonLoad.setOnClickListener {
|
binding.buttonLoad.setOnClickListener {
|
||||||
mainActivity()?.hideKeyboard()
|
mainActivity()?.hideKeyboard()
|
||||||
//downloadTransactions()
|
downloadTransactions()
|
||||||
}
|
}
|
||||||
|
|
||||||
initTransactionUi()
|
initTransactionUi()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
fun downloadTransactions() {
|
fun downloadTransactions() {
|
||||||
binding.textStatus.text = "loading..."
|
binding.textStatus.text = "loading..."
|
||||||
binding.textStatus.post {
|
binding.textStatus.post {
|
||||||
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
binding.textStatus.requestFocus()
|
binding.textStatus.requestFocus()
|
||||||
val addressToUse = binding.inputAddress.text.toString()
|
val addressToUse = binding.inputAddress.text.toString()
|
||||||
val startToUse = binding.inputRangeStart.text.toString().toIntOrNull() ?: ZcashNetwork.fromResources(requireApplicationContext()).saplingActivationHeight
|
val startToUse = max(
|
||||||
val endToUse = binding.inputRangeEnd.text.toString().toIntOrNull() ?: DemoConstants.utxoEndHeight
|
binding.inputRangeStart.text.toString().toLongOrNull()
|
||||||
|
?: network.saplingActivationHeight.value,
|
||||||
|
network.saplingActivationHeight.value
|
||||||
|
)
|
||||||
|
val endToUse = binding.inputRangeEnd.text.toString().toLongOrNull()
|
||||||
|
?: getUxtoEndHeight(requireApplicationContext()).value
|
||||||
var allStart = now
|
var allStart = now
|
||||||
twig("loading transactions in range $startToUse..$endToUse")
|
twig("loading transactions in range $startToUse..$endToUse")
|
||||||
val txids = lightwalletService?.getTAddressTransactions(addressToUse, startToUse..endToUse)
|
val txids = lightwalletService?.getTAddressTransactions(
|
||||||
|
addressToUse,
|
||||||
|
BlockHeight.new(network, startToUse)..BlockHeight.new(network, endToUse)
|
||||||
|
)
|
||||||
var delta = now - allStart
|
var delta = now - allStart
|
||||||
updateStatus("found ${txids?.size} transactions in ${delta}ms.", false)
|
updateStatus("found ${txids?.size} transactions in ${delta}ms.", false)
|
||||||
|
|
||||||
txids?.map {
|
txids?.map {
|
||||||
it.data.apply {
|
// Disabled during migration to newer SDK version; this appears to have been
|
||||||
try {
|
// leveraging non-public APIs in the SDK so perhaps should be removed
|
||||||
runBlocking { initializer.rustBackend.decryptAndStoreTransaction(toByteArray()) }
|
// it.data.apply {
|
||||||
} catch (t: Throwable) {
|
// try {
|
||||||
twig("failed to decrypt and store transaction due to: $t")
|
// runBlocking { initializer.rustBackend.decryptAndStoreTransaction(toByteArray()) }
|
||||||
}
|
// } catch (t: Throwable) {
|
||||||
}
|
// twig("failed to decrypt and store transaction due to: $t")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}?.let { txData ->
|
}?.let { txData ->
|
||||||
// Disabled during migration to newer SDK version; this appears to have been
|
// Disabled during migration to newer SDK version; this appears to have been
|
||||||
// leveraging non-public APIs in the SDK so perhaps should be removed
|
// leveraging non-public APIs in the SDK so perhaps should be removed
|
||||||
@@ -136,7 +157,6 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
private val now get() = System.currentTimeMillis()
|
private val now get() = System.currentTimeMillis()
|
||||||
|
|
||||||
@@ -159,7 +179,12 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
|||||||
resetInBackground()
|
resetInBackground()
|
||||||
val seed = Mnemonics.MnemonicCode(sharedViewModel.seedPhrase.value).toSeed()
|
val seed = Mnemonics.MnemonicCode(sharedViewModel.seedPhrase.value).toSeed()
|
||||||
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
|
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
|
||||||
binding.inputAddress.setText(DerivationTool.deriveTransparentAddress(seed, ZcashNetwork.fromResources(requireApplicationContext())))
|
binding.inputAddress.setText(
|
||||||
|
DerivationTool.deriveTransparentAddress(
|
||||||
|
seed,
|
||||||
|
ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,4 +273,9 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
private fun getUxtoEndHeight(context: Context): BlockHeight {
|
||||||
|
return BlockHeight.new(ZcashNetwork.fromResources(context), 968085L)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,11 @@ import cash.z.ecc.android.sdk.ext.convertZecToZatoshi
|
|||||||
import cash.z.ecc.android.sdk.ext.toZecString
|
import cash.z.ecc.android.sdk.ext.toZecString
|
||||||
import cash.z.ecc.android.sdk.internal.Twig
|
import cash.z.ecc.android.sdk.internal.Twig
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import cash.z.ecc.android.sdk.model.defaultForNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.type.WalletBalance
|
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,8 +68,14 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
|||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
Initializer.new(requireApplicationContext()) {
|
Initializer.new(requireApplicationContext()) {
|
||||||
runBlocking { it.importWallet(seed, network = ZcashNetwork.fromResources(requireApplicationContext())) }
|
val network = ZcashNetwork.fromResources(requireApplicationContext())
|
||||||
it.setNetwork(ZcashNetwork.fromResources(requireApplicationContext()))
|
runBlocking {
|
||||||
|
it.newWallet(
|
||||||
|
seed,
|
||||||
|
network = network,
|
||||||
|
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.let { initializer ->
|
}.let { initializer ->
|
||||||
synchronizer = Synchronizer.newBlocking(initializer)
|
synchronizer = Synchronizer.newBlocking(initializer)
|
||||||
|
|||||||
@@ -4,10 +4,16 @@ package cash.z.ecc.android.sdk.demoapp.util
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import cash.z.ecc.android.sdk.demoapp.R
|
import cash.z.ecc.android.sdk.demoapp.R
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
fun ZcashNetwork.Companion.fromResources(context: Context) = ZcashNetwork.valueOf(
|
fun ZcashNetwork.Companion.fromResources(context: Context): ZcashNetwork {
|
||||||
context.getString(
|
val networkNameFromResources = context.getString(R.string.network_name).lowercase(Locale.ROOT)
|
||||||
R.string.network_name
|
return if (networkNameFromResources == Testnet.networkName) {
|
||||||
)
|
ZcashNetwork.Testnet
|
||||||
)
|
} else if (networkNameFromResources.lowercase(Locale.ROOT) == Mainnet.networkName) {
|
||||||
|
ZcashNetwork.Mainnet
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException("Unknown network name: $networkNameFromResources")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package cash.z.ecc.android.sdk.demoapp
|
package cash.z.ecc.android.sdk.demoapp
|
||||||
|
|
||||||
object DemoConstants {
|
object DemoConstants {
|
||||||
val utxoEndHeight: Int = 1150000
|
|
||||||
val sendAmount: Double = 0.00000000
|
val sendAmount: Double = 0.000018
|
||||||
|
|
||||||
// corresponds to address: zs15tzaulx5weua5c7l47l4pku2pw9fzwvvnsp4y80jdpul0y3nwn5zp7tmkcclqaca3mdjqjkl7hx
|
// corresponds to address: zs15tzaulx5weua5c7l47l4pku2pw9fzwvvnsp4y80jdpul0y3nwn5zp7tmkcclqaca3mdjqjkl7hx
|
||||||
val initialSeedWords: String =
|
val initialSeedWords: String =
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package cash.z.ecc.android.sdk.demoapp
|
package cash.z.ecc.android.sdk.demoapp
|
||||||
|
|
||||||
object DemoConstants {
|
object DemoConstants {
|
||||||
val utxoEndHeight: Int = 1075590
|
val utxoEndHeight: Int = 1166699
|
||||||
val sendAmount: Double = 0.00017
|
val sendAmount: Double = 0.00017
|
||||||
|
|
||||||
// corresponds to address: ztestsapling1zhqvuq8zdwa8nsnde7074kcfsat0w25n08jzuvz5skzcs6h9raxu898l48xwr8fmkny3zqqrgd9
|
// corresponds to address: ztestsapling1zhqvuq8zdwa8nsnde7074kcfsat0w25n08jzuvz5skzcs6h9raxu898l48xwr8fmkny3zqqrgd9
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Thankfully, the only thing an app developer has to be concerned with is the following:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# 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
@@ -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
|
||||||
@@ -68,6 +68,7 @@ Start by making sure the command line with Gradle works first, because **all the
|
|||||||
1. Note: When first opening the project, Android Studio will warn that Gradle checksums are not fully supported. Choose the "Use checksum" option. This is a security feature that we have explicitly enabled.
|
1. Note: When first opening the project, Android Studio will warn that Gradle checksums are not fully supported. Choose the "Use checksum" option. This is a security feature that we have explicitly enabled.
|
||||||
1. Shortly after opening the project, Android Studio may prompt about updating the Android Gradle Plugin. DO NOT DO THIS. If you do so, the build will fail because the project also has dependency locking enabled as a security feature. To learn more, see [Build integrity.md](Build%20Integrity.md)
|
1. Shortly after opening the project, Android Studio may prompt about updating the Android Gradle Plugin. DO NOT DO THIS. If you do so, the build will fail because the project also has dependency locking enabled as a security feature. To learn more, see [Build integrity.md](Build%20Integrity.md)
|
||||||
1. Android Studio may prompt about updating the Kotlin plugin. Do this. Our application often uses a newer version of Kotlin than is bundled with Android Studio.
|
1. Android Studio may prompt about updating the Kotlin plugin. Do this. Our application often uses a newer version of Kotlin than is bundled with Android Studio.
|
||||||
|
1. Note that some versions of Android Studio on Intel machines have trouble with dependency locks. If you experience this issue, the workaround is to add the following line to `~/.gradle/gradle.properties` `ZCASH_IS_DEPENDENCY_LOCKING_ENABLED=false`
|
||||||
1. After Android Studio finishes syncing with Gradle, look for the green "play" run button in the toolbar. To the left of it, choose the "demo-app" run configuration under the dropdown menu. Then hit the run button.
|
1. After Android Studio finishes syncing with Gradle, look for the green "play" run button in the toolbar. To the left of it, choose the "demo-app" run configuration under the dropdown menu. Then hit the run button.
|
||||||
1. Note: The SDK supports both testnet and mainnet. The decision to switch between them is made at the application level. To switch between build variants, look for "Build Variants" which is usually a small button in the left gutter of the Android Studio window.
|
1. Note: The SDK supports both testnet and mainnet. The decision to switch between them is made at the application level. To switch between build variants, look for "Build Variants" which is usually a small button in the left gutter of the Android Studio window.
|
||||||
|
|
||||||
@@ -87,7 +88,8 @@ Start by making sure the command line with Gradle works first, because **all the
|
|||||||
## Gradle Tasks
|
## Gradle Tasks
|
||||||
A variety of Gradle tasks are set up within the project, and these tasks are also accessible in Android Studio as run configurations.
|
A variety of Gradle tasks are set up within the project, and these tasks are also accessible in Android Studio as run configurations.
|
||||||
* `assemble` - Compiles the SDK and demo application but does not deploy it
|
* `assemble` - Compiles the SDK and demo application but does not deploy it
|
||||||
* `sdk-lib:connectedAndroidTest` - Runs the tests against the SDK
|
* `sdk-lib:test` - Runs unit tests in the SDK that don't require Android. This is generally a small number of tests against plain Kotlin code without Android dependencies.
|
||||||
|
* `sdk-lib:connectedAndroidTest` - Runs the tests against the SDK that require integration with Android.
|
||||||
* `darkside-test-lib:connectedAndroidTest` - Runs the tests against the SDK which require a localhost lightwalletd server running in darkside mode
|
* `darkside-test-lib:connectedAndroidTest` - Runs the tests against the SDK which require a localhost lightwalletd server running in darkside mode
|
||||||
* `assembleAndroidTest` - Compiles the application and tests, but does not deploy the application or run the tests. The Android Studio run configuration actually runs all of these tasks because the debug APKs are necessary to run the tests: `assembleDebug assembleZcashmainnetDebug assembleZcashtestnetDebug assembleAndroidTest`
|
* `assembleAndroidTest` - Compiles the application and tests, but does not deploy the application or run the tests. The Android Studio run configuration actually runs all of these tasks because the debug APKs are necessary to run the tests: `assembleDebug assembleZcashmainnetDebug assembleZcashtestnetDebug assembleAndroidTest`
|
||||||
* `detektAll` - Performs static analysis with Detekt
|
* `detektAll` - Performs static analysis with Detekt
|
||||||
@@ -127,4 +129,4 @@ For Continuous Integration, see [CI.md](CI.md). The rest of this section is reg
|
|||||||
1. If you are an Electric Coin Co team member: We are still setting up a process for this, because emulator.wtf does not yet support individual API tokens
|
1. If you are an Electric Coin Co team member: We are still setting up a process for this, because emulator.wtf does not yet support individual API tokens
|
||||||
1. If you are an open source contributor: Visit http://emulator.wtf and request an API key
|
1. If you are an open source contributor: Visit http://emulator.wtf and request an API key
|
||||||
1. Set the emulator.wtf API key as a global Gradle property `ZCASH_EMULATOR_WTF_API_KEY` under `~/.gradle/gradle.properties`
|
1. Set the emulator.wtf API key as a global Gradle property `ZCASH_EMULATOR_WTF_API_KEY` under `~/.gradle/gradle.properties`
|
||||||
1. Run the Gradle task `./gradlew testDebugWithEmulatorWtf :app:testZcashmainnetDebugWithEmulatorWtf` (emulator.wtf tasks do build the app, so you don't need to build them beforehand)
|
1. Run the Gradle task `./gradlew testDebugWithEmulatorWtf` (emulator.wtf tasks do build the tests and test APKs, so you don't need to build them beforehand. This is a different behavior compared to Firebase Test Lab)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 237 KiB After Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 445 KiB After Width: | Height: | Size: 445 KiB |
@@ -1,10 +1,9 @@
|
|||||||
# Speed up builds. Keep these flags here for quick debugging of issues.
|
# Speed up builds. Keep these flags here for quick debugging of issues.
|
||||||
# https://github.com/gradle/gradle/issues/13382
|
|
||||||
org.gradle.vfs.watch=false
|
|
||||||
org.gradle.configureondemand=false
|
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
org.gradle.parallel=true
|
org.gradle.configureondemand=false
|
||||||
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
|
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
|
||||||
|
org.gradle.parallel=true
|
||||||
|
org.gradle.vfs.watch=true
|
||||||
|
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.builder.sdkDownload=true
|
android.builder.sdkDownload=true
|
||||||
@@ -22,7 +21,7 @@ RELEASE_SIGNING_ENABLED=false
|
|||||||
# Required by the maven publishing plugin
|
# Required by the maven publishing plugin
|
||||||
SONATYPE_HOST=DEFAULT
|
SONATYPE_HOST=DEFAULT
|
||||||
|
|
||||||
LIBRARY_VERSION=1.7.0-beta01
|
LIBRARY_VERSION=1.9.0-beta01
|
||||||
|
|
||||||
# Kotlin compiler warnings can be considered errors, failing the build.
|
# 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.
|
# Currently set to false, because this project has a lot of warnings to fix first.
|
||||||
@@ -64,17 +63,17 @@ ANDROID_COMPILE_SDK_VERSION=33
|
|||||||
# When changing this, be sure to update .github/actions/setup/action.yml
|
# When changing this, be sure to update .github/actions/setup/action.yml
|
||||||
ANDROID_NDK_VERSION=22.1.7171670
|
ANDROID_NDK_VERSION=22.1.7171670
|
||||||
|
|
||||||
ANDROID_GRADLE_PLUGIN_VERSION=7.2.1
|
ANDROID_GRADLE_PLUGIN_VERSION=7.2.2
|
||||||
DETEKT_VERSION=1.20.0
|
DETEKT_VERSION=1.21.0
|
||||||
DOKKA_VERSION=1.6.21
|
DOKKA_VERSION=1.7.10
|
||||||
EMULATOR_WTF_GRADLE_PLUGIN_VERSION=0.0.10
|
EMULATOR_WTF_GRADLE_PLUGIN_VERSION=0.0.10
|
||||||
FLANK_VERSION=22.03.0
|
FLANK_VERSION=22.03.0
|
||||||
FULLADLE_VERSION=0.17.4
|
FULLADLE_VERSION=0.17.4
|
||||||
GRADLE_VERSIONS_PLUGIN_VERSION=0.42.0
|
GRADLE_VERSIONS_PLUGIN_VERSION=0.42.0
|
||||||
KTLINT_VERSION=0.46.1
|
KTLINT_VERSION=0.46.1
|
||||||
KSP_VERSION=1.6.21-1.0.6
|
KSP_VERSION=1.7.10-1.0.6
|
||||||
MAVEN_PUBLISH_GRADLE_PLUGIN=0.20.0
|
MAVEN_PUBLISH_GRADLE_PLUGIN=0.20.0
|
||||||
PROTOBUF_GRADLE_PLUGIN_VERSION=0.8.18
|
PROTOBUF_GRADLE_PLUGIN_VERSION=0.8.19
|
||||||
RUST_GRADLE_PLUGIN_VERSION=0.9.3
|
RUST_GRADLE_PLUGIN_VERSION=0.9.3
|
||||||
|
|
||||||
ANDROIDX_ANNOTATION_VERSION=1.3.0
|
ANDROIDX_ANNOTATION_VERSION=1.3.0
|
||||||
@@ -86,27 +85,27 @@ ANDROIDX_LIFECYCLE_VERSION=2.4.1
|
|||||||
ANDROIDX_MULTIDEX_VERSION=2.0.1
|
ANDROIDX_MULTIDEX_VERSION=2.0.1
|
||||||
ANDROIDX_NAVIGATION_VERSION=2.4.2
|
ANDROIDX_NAVIGATION_VERSION=2.4.2
|
||||||
ANDROIDX_PAGING_VERSION=2.1.2
|
ANDROIDX_PAGING_VERSION=2.1.2
|
||||||
ANDROIDX_ROOM_VERSION=2.4.2
|
ANDROIDX_ROOM_VERSION=2.4.3
|
||||||
ANDROIDX_TEST_JUNIT_VERSION=1.1.3
|
ANDROIDX_TEST_JUNIT_VERSION=1.1.3
|
||||||
ANDROIDX_TEST_ORCHESTRATOR_VERSION=1.1.0-alpha1
|
ANDROIDX_TEST_ORCHESTRATOR_VERSION=1.1.0-alpha1
|
||||||
ANDROIDX_TEST_VERSION=1.4.0
|
ANDROIDX_TEST_VERSION=1.4.0
|
||||||
ANDROIDX_UI_AUTOMATOR_VERSION=2.2.0-alpha1
|
ANDROIDX_UI_AUTOMATOR_VERSION=2.2.0-alpha1
|
||||||
BIP39_VERSION=1.0.2
|
BIP39_VERSION=1.0.4
|
||||||
COROUTINES_OKHTTP=1.0
|
COROUTINES_OKHTTP=1.0
|
||||||
GOOGLE_MATERIAL_VERSION=1.6.1
|
GOOGLE_MATERIAL_VERSION=1.6.1
|
||||||
GRPC_VERSION=1.47.0
|
GRPC_VERSION=1.48.1
|
||||||
GSON_VERSION=2.9.0
|
GSON_VERSION=2.9.0
|
||||||
GUAVA_VERSION=31.1-android
|
GUAVA_VERSION=31.1-android
|
||||||
JACOCO_VERSION=0.8.8
|
JACOCO_VERSION=0.8.8
|
||||||
JAVAX_ANNOTATION_VERSION=1.3.2
|
JAVAX_ANNOTATION_VERSION=1.3.2
|
||||||
JUNIT_VERSION=5.8.2
|
JUNIT_VERSION=5.9.0
|
||||||
KOTLINX_COROUTINES_VERSION=1.6.2
|
KOTLINX_COROUTINES_VERSION=1.6.4
|
||||||
KOTLIN_VERSION=1.6.21
|
KOTLIN_VERSION=1.7.10
|
||||||
MOCKITO_KOTLIN_VERSION=2.2.0
|
MOCKITO_KOTLIN_VERSION=2.2.0
|
||||||
MOCKITO_VERSION=4.6.1
|
MOCKITO_VERSION=4.6.1
|
||||||
OKHTTP_VERSION=4.10.0
|
OKHTTP_VERSION=4.10.0
|
||||||
OKIO_VERSION=3.1.0
|
OKIO_VERSION=3.2.0
|
||||||
PROTOC_VERSION=3.21.1
|
PROTOC_VERSION=3.21.4
|
||||||
ZCASH_WALLET_PLUGINS_VERSION=1.0.1
|
ZCASH_WALLET_PLUGINS_VERSION=1.0.1
|
||||||
|
|
||||||
# This shouldn't be changed, as Android doesn't support targets beyond Java 8
|
# This shouldn't be changed, as Android doesn't support targets beyond Java 8
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||||
distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda
|
distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
6
gradlew
vendored
@@ -205,6 +205,12 @@ set -- \
|
|||||||
org.gradle.wrapper.GradleWrapperMain \
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
|||||||
14
gradlew.bat
vendored
@@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ secp256k1 = "0.19"
|
|||||||
# consensus branch ID and activation heights, and v5 transaction parsing, backported.
|
# consensus branch ID and activation heights, and v5 transaction parsing, backported.
|
||||||
# https://github.com/zcash/librustzcash/pull/555
|
# https://github.com/zcash/librustzcash/pull/555
|
||||||
# https://github.com/zcash/librustzcash/pull/558
|
# https://github.com/zcash/librustzcash/pull/558
|
||||||
[patch.crates-io]
|
#[patch.crates-io]
|
||||||
zcash_client_backend = { git = 'https://github.com/zcash/librustzcash.git', rev='0a1ed9b8acf336bdc20ba49083c66f114ecf6877' }
|
#zcash_client_backend = { git = 'https://github.com/zcash/librustzcash.git', rev='0a1ed9b8acf336bdc20ba49083c66f114ecf6877' }
|
||||||
zcash_client_sqlite = { git = 'https://github.com/zcash/librustzcash.git', rev='0a1ed9b8acf336bdc20ba49083c66f114ecf6877' }
|
#zcash_client_sqlite = { git = 'https://github.com/zcash/librustzcash.git', rev='0a1ed9b8acf336bdc20ba49083c66f114ecf6877' }
|
||||||
zcash_primitives = { git = 'https://github.com/zcash/librustzcash.git', rev='0a1ed9b8acf336bdc20ba49083c66f114ecf6877' }
|
#zcash_primitives = { git = 'https://github.com/zcash/librustzcash.git', rev='0a1ed9b8acf336bdc20ba49083c66f114ecf6877' }
|
||||||
zcash_proofs = { git = 'https://github.com/zcash/librustzcash.git', rev='0a1ed9b8acf336bdc20ba49083c66f114ecf6877' }
|
#zcash_proofs = { git = 'https://github.com/zcash/librustzcash.git', rev='0a1ed9b8acf336bdc20ba49083c66f114ecf6877' }
|
||||||
|
|
||||||
## Uncomment this to test librustzcash changes locally
|
## Uncomment this to test librustzcash changes locally
|
||||||
#[patch.crates-io]
|
#[patch.crates-io]
|
||||||
@@ -43,11 +43,11 @@ zcash_proofs = { git = 'https://github.com/zcash/librustzcash.git', rev='0a1ed9b
|
|||||||
#zcash_proofs = { path = '../../clones/librustzcash/zcash_proofs' }
|
#zcash_proofs = { path = '../../clones/librustzcash/zcash_proofs' }
|
||||||
|
|
||||||
## Uncomment this to test someone else's librustzcash changes in a branch
|
## Uncomment this to test someone else's librustzcash changes in a branch
|
||||||
#[patch.crates-io]
|
[patch.crates-io]
|
||||||
#zcash_client_backend = { git = "https://github.com/zcash/librustzcash", branch = "branch-name" }
|
zcash_client_backend = { git = "https://git.hush.is/fekt/librustzcash", branch = "main" }
|
||||||
#zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash", branch = "branch-name" }
|
zcash_client_sqlite = { git = "https://git.hush.is/fekt/librustzcash", branch = "main" }
|
||||||
#zcash_primitives = { git = "https://github.com/zcash/librustzcash", branch = "branch-name" }
|
zcash_primitives = { git = "https://git.hush.is/fekt/librustzcash", branch = "main" }
|
||||||
#zcash_proofs = { git = "https://github.com/zcash/librustzcash", branch = "branch-name" }
|
zcash_proofs = { git = "https://git.hush.is/fekt/librustzcash", branch = "main" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
mainnet = ["zcash_client_sqlite/mainnet"]
|
mainnet = ["zcash_client_sqlite/mainnet"]
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package cash.z.ecc.android.sdk
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
import cash.z.ecc.android.sdk.tool.CheckpointTool
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
@@ -57,7 +57,7 @@ class AssetTest {
|
|||||||
|
|
||||||
private fun assertFileContents(network: ZcashNetwork, files: Array<String>?) {
|
private fun assertFileContents(network: ZcashNetwork, files: Array<String>?) {
|
||||||
files?.map { filename ->
|
files?.map { filename ->
|
||||||
val filePath = "${WalletBirthdayTool.birthdayDirectory(network)}/$filename"
|
val filePath = "${CheckpointTool.checkpointDirectory(network)}/$filename"
|
||||||
ApplicationProvider.getApplicationContext<Context>().assets.open(filePath)
|
ApplicationProvider.getApplicationContext<Context>().assets.open(filePath)
|
||||||
.use { inputSteam ->
|
.use { inputSteam ->
|
||||||
inputSteam.bufferedReader().use { bufferedReader ->
|
inputSteam.bufferedReader().use { bufferedReader ->
|
||||||
@@ -77,12 +77,13 @@ class AssetTest {
|
|||||||
val expectedNetworkName = when (network) {
|
val expectedNetworkName = when (network) {
|
||||||
ZcashNetwork.Mainnet -> "main"
|
ZcashNetwork.Mainnet -> "main"
|
||||||
ZcashNetwork.Testnet -> "test"
|
ZcashNetwork.Testnet -> "test"
|
||||||
|
else -> IllegalArgumentException("Unsupported network $network")
|
||||||
}
|
}
|
||||||
assertEquals("File: ${it.filename}", expectedNetworkName, jsonObject.getString("network"))
|
assertEquals("File: ${it.filename}", expectedNetworkName, jsonObject.getString("network"))
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"File: ${it.filename}",
|
"File: ${it.filename}",
|
||||||
WalletBirthdayTool.birthdayHeight(it.filename),
|
CheckpointTool.checkpointHeightFromFilename(network, it.filename),
|
||||||
jsonObject.getInt("height")
|
jsonObject.getInt("height")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -94,9 +95,9 @@ class AssetTest {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun listAssets(network: ZcashNetwork) = runBlocking {
|
fun listAssets(network: ZcashNetwork) = runBlocking {
|
||||||
WalletBirthdayTool.listBirthdayDirectoryContents(
|
CheckpointTool.listCheckpointDirectoryContents(
|
||||||
ApplicationProvider.getApplicationContext<Context>(),
|
ApplicationProvider.getApplicationContext<Context>(),
|
||||||
WalletBirthdayTool.birthdayDirectory(network)
|
CheckpointTool.checkpointDirectory(network)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package cash.z.ecc.android.sdk.ext
|
package cash.z.ecc.android.sdk.ext
|
||||||
|
|
||||||
import cash.z.ecc.android.sdk.Initializer
|
import cash.z.ecc.android.sdk.Initializer
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.util.SimpleMnemonics
|
import cash.z.ecc.android.sdk.util.SimpleMnemonics
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@@ -14,14 +14,14 @@ fun Initializer.Config.seedPhrase(seedPhrase: String, network: ZcashNetwork) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object BlockExplorer {
|
object BlockExplorer {
|
||||||
suspend fun fetchLatestHeight(): Int {
|
suspend fun fetchLatestHeight(): Long {
|
||||||
val client = OkHttpClient()
|
val client = OkHttpClient()
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url("https://api.blockchair.com/zcash/blocks?limit=1")
|
.url("https://api.blockchair.com/zcash/blocks?limit=1")
|
||||||
.build()
|
.build()
|
||||||
val result = client.newCall(request).await()
|
val result = client.newCall(request).await()
|
||||||
val body = result.body?.string()
|
val body = result.body?.string()
|
||||||
return JSONObject(body).getJSONArray("data").getJSONObject(0).getInt("id")
|
return JSONObject(body).getJSONArray("data").getJSONObject(0).getLong("id")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package cash.z.ecc.android.sdk.integration
|
|||||||
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
||||||
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
||||||
import cash.z.ecc.android.sdk.ext.BlockExplorer
|
import cash.z.ecc.android.sdk.ext.BlockExplorer
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.util.TestWallet
|
import cash.z.ecc.android.sdk.util.TestWallet
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -30,13 +30,6 @@ class SanityTest(
|
|||||||
val networkName = wallet.networkName
|
val networkName = wallet.networkName
|
||||||
val name = "$networkName wallet"
|
val name = "$networkName wallet"
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testNotPlaintext() {
|
|
||||||
val message =
|
|
||||||
"is using plaintext. This will cause problems for the test. Ensure that the `lightwalletd_allow_very_insecure_connections` resource value is false"
|
|
||||||
assertFalse("$name $message", wallet.service.connectionInfo.usePlaintext)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFilePaths() {
|
fun testFilePaths() {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
@@ -61,7 +54,7 @@ class SanityTest(
|
|||||||
assertEquals(
|
assertEquals(
|
||||||
"$name has invalid birthday height",
|
"$name has invalid birthday height",
|
||||||
birthday,
|
birthday,
|
||||||
wallet.initializer.birthday.height
|
wallet.initializer.checkpoint.height
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,25 +72,15 @@ class SanityTest(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testServerConnection() {
|
|
||||||
assertEquals(
|
|
||||||
"$name has an invalid server connection",
|
|
||||||
"$networkName.lite.hushpool.is:9067?usePlaintext=true",
|
|
||||||
wallet.connectionInfo
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLatestHeight() = runBlocking {
|
fun testLatestHeight() = runBlocking {
|
||||||
if (wallet.networkName == "mainnet") {
|
if (wallet.networkName == "mainnet") {
|
||||||
val expectedHeight = BlockExplorer.fetchLatestHeight()
|
val expectedHeight = BlockExplorer.fetchLatestHeight()
|
||||||
// fetch height directly because the synchronizer hasn't started, yet
|
// fetch height directly because the synchronizer hasn't started, yet
|
||||||
val downloaderHeight = wallet.service.getLatestBlockHeight()
|
val downloaderHeight = wallet.service.getLatestBlockHeight()
|
||||||
val info = wallet.connectionInfo
|
|
||||||
assertTrue(
|
assertTrue(
|
||||||
"$info\n ${wallet.networkName} Lightwalletd is too far behind. Downloader height $downloaderHeight is more than 10 blocks behind block explorer height $expectedHeight",
|
"${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
|
expectedHeight - 10 < downloaderHeight.value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,9 +88,9 @@ class SanityTest(
|
|||||||
@Test
|
@Test
|
||||||
fun testSingleBlockDownload() = runBlocking {
|
fun testSingleBlockDownload() = runBlocking {
|
||||||
// fetch block directly because the synchronizer hasn't started, yet
|
// fetch block directly because the synchronizer hasn't started, yet
|
||||||
val height = 1_000_000
|
val height = BlockHeight.new(wallet.network, 1_000_000)
|
||||||
val block = wallet.service.getBlockRange(height..height)[0]
|
val block = wallet.service.getBlockRange(height..height).first()
|
||||||
assertTrue("$networkName failed to return a proper block. Height was ${block.height} but we expected $height", block.height.toInt() == height)
|
assertTrue("$networkName failed to return a proper block. Height was ${block.height} but we expected $height", block.height == height.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import androidx.test.filters.LargeTest
|
|||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
||||||
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
|
||||||
import cash.z.ecc.android.sdk.util.TestWallet
|
import cash.z.ecc.android.sdk.util.TestWallet
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
@@ -19,16 +18,6 @@ import org.junit.Test
|
|||||||
@MediumTest
|
@MediumTest
|
||||||
class SmokeTest {
|
class SmokeTest {
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testNotPlaintext() {
|
|
||||||
val service =
|
|
||||||
wallet.synchronizer.processor.downloader.lightWalletService as LightWalletGrpcService
|
|
||||||
Assert.assertFalse(
|
|
||||||
"Wallet is using plaintext. This will cause problems for the test. Ensure that the `lightwalletd_allow_very_insecure_connections` resource value is false",
|
|
||||||
service.connectionInfo.usePlaintext
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFilePaths() {
|
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 DataDB file", "/data/user/0/cash.z.ecc.android.sdk.test/databases/TestWallet_testnet_Data.db", wallet.initializer.rustBackend.pathDataDb)
|
||||||
@@ -38,7 +27,7 @@ class SmokeTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBirthday() {
|
fun testBirthday() {
|
||||||
Assert.assertEquals("Invalid birthday height", 1_320_000, wallet.initializer.birthday.height)
|
Assert.assertEquals("Invalid birthday height", 1_320_000, wallet.initializer.checkpoint.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package cash.z.wallet.sdk.integration
|
package cash.z.ecc.android.sdk.integration
|
||||||
|
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
@@ -12,11 +12,13 @@ import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
|||||||
import cash.z.ecc.android.sdk.internal.Twig
|
import cash.z.ecc.android.sdk.internal.Twig
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.test.ScopedTest
|
import cash.z.ecc.android.sdk.test.ScopedTest
|
||||||
|
import cash.z.ecc.android.sdk.tool.CheckpointTool
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
|
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
@@ -38,9 +40,9 @@ class TestnetIntegrationTest : ScopedTest() {
|
|||||||
@Test
|
@Test
|
||||||
@Ignore("This test is broken")
|
@Ignore("This test is broken")
|
||||||
fun testLatestBlockTest() {
|
fun testLatestBlockTest() {
|
||||||
val service = LightWalletGrpcService(
|
val service = LightWalletGrpcService.new(
|
||||||
context,
|
context,
|
||||||
host
|
lightWalletEndpoint
|
||||||
)
|
)
|
||||||
val height = service.getLatestBlockHeight()
|
val height = service.getLatestBlockHeight()
|
||||||
assertTrue(height > saplingActivation)
|
assertTrue(height > saplingActivation)
|
||||||
@@ -49,7 +51,7 @@ class TestnetIntegrationTest : ScopedTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun testLoadBirthday() {
|
fun testLoadBirthday() {
|
||||||
val (height, hash, time, tree) = runBlocking {
|
val (height, hash, time, tree) = runBlocking {
|
||||||
WalletBirthdayTool.loadNearest(
|
CheckpointTool.loadNearest(
|
||||||
context,
|
context,
|
||||||
synchronizer.network,
|
synchronizer.network,
|
||||||
saplingActivation + 1
|
saplingActivation + 1
|
||||||
@@ -117,8 +119,8 @@ class TestnetIntegrationTest : ScopedTest() {
|
|||||||
companion object {
|
companion object {
|
||||||
init { Twig.plant(TroubleshootingTwig()) }
|
init { Twig.plant(TroubleshootingTwig()) }
|
||||||
|
|
||||||
const val host = "lightwalletd.testnet.z.cash"
|
val lightWalletEndpoint = LightWalletEndpoint("lightwalletd.testnet.z.cash", 9087, true)
|
||||||
private const val birthdayHeight = 963150
|
private const val birthdayHeight = 963150L
|
||||||
private const val targetHeight = 663250
|
private const val targetHeight = 663250
|
||||||
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"
|
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"
|
||||||
val seed = "cash.z.ecc.android.sdk.integration.IntegrationTest.seed.value.64bytes".toByteArray()
|
val seed = "cash.z.ecc.android.sdk.integration.IntegrationTest.seed.value.64bytes".toByteArray()
|
||||||
@@ -128,8 +130,8 @@ class TestnetIntegrationTest : ScopedTest() {
|
|||||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
private val initializer = runBlocking {
|
private val initializer = runBlocking {
|
||||||
Initializer.new(context) { config ->
|
Initializer.new(context) { config ->
|
||||||
config.setNetwork(ZcashNetwork.Testnet, host)
|
config.setNetwork(ZcashNetwork.Testnet, lightWalletEndpoint)
|
||||||
runBlocking { config.importWallet(seed, birthdayHeight, ZcashNetwork.Testnet) }
|
runBlocking { config.importWallet(seed, BlockHeight.new(ZcashNetwork.Testnet, birthdayHeight), ZcashNetwork.Testnet, lightWalletEndpoint) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private lateinit var synchronizer: Synchronizer
|
private lateinit var synchronizer: Synchronizer
|
||||||
|
|||||||
@@ -11,8 +11,12 @@ import cash.z.ecc.android.sdk.internal.block.CompactBlockStore
|
|||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.Mainnet
|
||||||
|
import cash.z.ecc.android.sdk.model.Testnet
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.test.ScopedTest
|
import cash.z.ecc.android.sdk.test.ScopedTest
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
@@ -34,13 +38,15 @@ import org.mockito.Spy
|
|||||||
class ChangeServiceTest : ScopedTest() {
|
class ChangeServiceTest : ScopedTest() {
|
||||||
|
|
||||||
val network = ZcashNetwork.Mainnet
|
val network = ZcashNetwork.Mainnet
|
||||||
|
val lightWalletEndpoint = LightWalletEndpoint.Mainnet
|
||||||
|
private val eccEndpoint = LightWalletEndpoint("lightwalletd.electriccoin.co", 9087, true)
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
lateinit var mockBlockStore: CompactBlockStore
|
lateinit var mockBlockStore: CompactBlockStore
|
||||||
var mockCloseable: AutoCloseable? = null
|
var mockCloseable: AutoCloseable? = null
|
||||||
|
|
||||||
@Spy
|
@Spy
|
||||||
val service = LightWalletGrpcService(context, network)
|
val service = LightWalletGrpcService.new(context, lightWalletEndpoint)
|
||||||
|
|
||||||
lateinit var downloader: CompactBlockDownloader
|
lateinit var downloader: CompactBlockDownloader
|
||||||
lateinit var otherService: LightWalletService
|
lateinit var otherService: LightWalletService
|
||||||
@@ -49,7 +55,7 @@ class ChangeServiceTest : ScopedTest() {
|
|||||||
fun setup() {
|
fun setup() {
|
||||||
initMocks()
|
initMocks()
|
||||||
downloader = CompactBlockDownloader(service, mockBlockStore)
|
downloader = CompactBlockDownloader(service, mockBlockStore)
|
||||||
otherService = LightWalletGrpcService(context, "lightwalletd.electriccoin.co")
|
otherService = LightWalletGrpcService.new(context, eccEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -70,7 +76,7 @@ class ChangeServiceTest : ScopedTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun testCleanSwitch() = runBlocking {
|
fun testCleanSwitch() = runBlocking {
|
||||||
downloader.changeService(otherService)
|
downloader.changeService(otherService)
|
||||||
val result = downloader.downloadBlockRange(900_000..901_000)
|
val result = downloader.downloadBlockRange(BlockHeight.new(network, 900_000)..BlockHeight.new(network, 901_000))
|
||||||
assertEquals(1_001, result)
|
assertEquals(1_001, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +87,7 @@ class ChangeServiceTest : ScopedTest() {
|
|||||||
@Test
|
@Test
|
||||||
@Ignore("This test is broken")
|
@Ignore("This test is broken")
|
||||||
fun testSwitchWhileActive() = runBlocking {
|
fun testSwitchWhileActive() = runBlocking {
|
||||||
val start = 900_000
|
val start = BlockHeight.new(ZcashNetwork.Mainnet, 900_000)
|
||||||
val count = 5
|
val count = 5
|
||||||
val differentiators = mutableListOf<String>()
|
val differentiators = mutableListOf<String>()
|
||||||
var initialValue = downloader.getServerInfo().buildUser
|
var initialValue = downloader.getServerInfo().buildUser
|
||||||
@@ -105,7 +111,7 @@ class ChangeServiceTest : ScopedTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun testSwitchToInvalidServer() = runBlocking {
|
fun testSwitchToInvalidServer() = runBlocking {
|
||||||
var caughtException: Throwable? = null
|
var caughtException: Throwable? = null
|
||||||
downloader.changeService(LightWalletGrpcService(context, "invalid.lightwalletd")) {
|
downloader.changeService(LightWalletGrpcService.new(context, LightWalletEndpoint("invalid.lightwalletd", 9087, true))) {
|
||||||
caughtException = it
|
caughtException = it
|
||||||
}
|
}
|
||||||
assertNotNull("Using an invalid host should generate an exception.", caughtException)
|
assertNotNull("Using an invalid host should generate an exception.", caughtException)
|
||||||
@@ -118,7 +124,7 @@ class ChangeServiceTest : ScopedTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun testSwitchToTestnetFails() = runBlocking {
|
fun testSwitchToTestnetFails() = runBlocking {
|
||||||
var caughtException: Throwable? = null
|
var caughtException: Throwable? = null
|
||||||
downloader.changeService(LightWalletGrpcService(context, ZcashNetwork.Testnet)) {
|
downloader.changeService(LightWalletGrpcService.new(context, LightWalletEndpoint.Testnet)) {
|
||||||
caughtException = it
|
caughtException = it
|
||||||
}
|
}
|
||||||
assertNotNull("Using an invalid host should generate an exception.", caughtException)
|
assertNotNull("Using an invalid host should generate an exception.", caughtException)
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package cash.z.ecc.android.sdk.internal
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import cash.z.ecc.android.sdk.internal.model.Checkpoint
|
||||||
|
import cash.z.ecc.fixture.CheckpointFixture
|
||||||
|
import cash.z.ecc.fixture.toJson
|
||||||
|
import org.json.JSONObject
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class CheckpointTest {
|
||||||
|
@Test
|
||||||
|
@SmallTest
|
||||||
|
fun deserialize() {
|
||||||
|
val fixtureCheckpoint = CheckpointFixture.new()
|
||||||
|
|
||||||
|
val deserialized = Checkpoint.from(CheckpointFixture.NETWORK, fixtureCheckpoint.toJson())
|
||||||
|
|
||||||
|
assertEquals(fixtureCheckpoint, deserialized)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SmallTest
|
||||||
|
fun epoch_seconds_as_long_that_would_overflow_int() {
|
||||||
|
val jsonString = CheckpointFixture.new(time = Long.MAX_VALUE).toJson()
|
||||||
|
|
||||||
|
Checkpoint.from(CheckpointFixture.NETWORK, jsonString).also {
|
||||||
|
assertEquals(Long.MAX_VALUE, it.epochSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SmallTest
|
||||||
|
fun parse_height_as_long_that_would_overflow_int() {
|
||||||
|
val jsonString = JSONObject().apply {
|
||||||
|
put(Checkpoint.KEY_VERSION, Checkpoint.VERSION_1)
|
||||||
|
put(Checkpoint.KEY_HEIGHT, UInt.MAX_VALUE.toLong())
|
||||||
|
put(Checkpoint.KEY_HASH, CheckpointFixture.HASH)
|
||||||
|
put(Checkpoint.KEY_EPOCH_SECONDS, CheckpointFixture.EPOCH_SECONDS)
|
||||||
|
put(Checkpoint.KEY_TREE, CheckpointFixture.TREE)
|
||||||
|
}.toString()
|
||||||
|
|
||||||
|
Checkpoint.from(CheckpointFixture.NETWORK, jsonString).also {
|
||||||
|
assertEquals(UInt.MAX_VALUE.toLong(), it.height.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package cash.z.ecc.android.sdk.internal
|
|
||||||
|
|
||||||
import androidx.test.filters.SmallTest
|
|
||||||
import cash.z.ecc.android.sdk.type.WalletBirthday
|
|
||||||
import cash.z.ecc.fixture.WalletBirthdayFixture
|
|
||||||
import cash.z.ecc.fixture.toJson
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class WalletBirthdayTest {
|
|
||||||
@Test
|
|
||||||
@SmallTest
|
|
||||||
fun deserialize() {
|
|
||||||
val fixtureBirthday = WalletBirthdayFixture.new()
|
|
||||||
|
|
||||||
val deserialized = WalletBirthday.from(fixtureBirthday.toJson())
|
|
||||||
|
|
||||||
assertEquals(fixtureBirthday, deserialized)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SmallTest
|
|
||||||
fun epoch_seconds_as_long_that_would_overflow_int() {
|
|
||||||
val jsonString = WalletBirthdayFixture.new(time = Long.MAX_VALUE).toJson()
|
|
||||||
|
|
||||||
WalletBirthday.from(jsonString).also {
|
|
||||||
assertEquals(Long.MAX_VALUE, it.time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,8 @@ package cash.z.ecc.android.sdk.jni
|
|||||||
|
|
||||||
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
||||||
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -14,9 +15,9 @@ import org.junit.runners.Parameterized
|
|||||||
*/
|
*/
|
||||||
@MaintainedTest(TestPurpose.REGRESSION)
|
@MaintainedTest(TestPurpose.REGRESSION)
|
||||||
@RunWith(Parameterized::class)
|
@RunWith(Parameterized::class)
|
||||||
class BranchIdTest(
|
class BranchIdTest internal constructor(
|
||||||
private val networkName: String,
|
private val networkName: String,
|
||||||
private val height: Int,
|
private val height: BlockHeight,
|
||||||
private val branchId: Long,
|
private val branchId: Long,
|
||||||
private val branchHex: String,
|
private val branchHex: String,
|
||||||
private val rustBackend: RustBackendWelding
|
private val rustBackend: RustBackendWelding
|
||||||
@@ -44,14 +45,14 @@ class BranchIdTest(
|
|||||||
// is an abnormal use of the SDK because this really should run at the rust level
|
// is an abnormal use of the SDK because this really should run at the rust level
|
||||||
// However, due to quirks on certain devices, we created this test at the Android level,
|
// However, due to quirks on certain devices, we created this test at the Android level,
|
||||||
// as a sanity check
|
// as a sanity check
|
||||||
val testnetBackend = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Testnet) }
|
val testnetBackend = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Testnet, ZcashNetwork.Testnet.saplingActivationHeight) }
|
||||||
val mainnetBackend = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Mainnet) }
|
val mainnetBackend = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Mainnet, ZcashNetwork.Mainnet.saplingActivationHeight) }
|
||||||
return listOf(
|
return listOf(
|
||||||
// Mainnet Cases
|
// Mainnet Cases
|
||||||
arrayOf("Sapling", 419_200, 1991772603L, "76b809bb", mainnetBackend),
|
arrayOf("Sapling", 1, 1991772603L, "76b809bb", mainnetBackend),
|
||||||
|
|
||||||
// Testnet Cases
|
// Testnet Cases
|
||||||
arrayOf("Sapling", 280_000, 1991772603L, "76b809bb", testnetBackend)
|
arrayOf("Sapling", 1, 1991772603L, "76b809bb", testnetBackend),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import cash.z.ecc.android.sdk.annotation.MaintainedTest
|
|||||||
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
import cash.z.ecc.android.sdk.annotation.TestPurpose
|
||||||
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||||
import cash.z.ecc.android.sdk.internal.Twig
|
import cash.z.ecc.android.sdk.internal.Twig
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.BeforeClass
|
import org.junit.BeforeClass
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package cash.z.ecc.android.sdk.sample
|
|||||||
|
|
||||||
import cash.z.ecc.android.sdk.internal.Twig
|
import cash.z.ecc.android.sdk.internal.Twig
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.util.TestWallet
|
import cash.z.ecc.android.sdk.util.TestWallet
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ package cash.z.ecc.android.sdk.sample
|
|||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork.Testnet
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.util.TestWallet
|
import cash.z.ecc.android.sdk.util.TestWallet
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
@@ -73,7 +74,7 @@ class TransparentRestoreSample {
|
|||||||
// wallet.rewindToHeight(1343500).join(45_000)
|
// wallet.rewindToHeight(1343500).join(45_000)
|
||||||
val wallet = TestWallet(TestWallet.Backups.SAMPLE_WALLET, alias = "WalletC")
|
val wallet = TestWallet(TestWallet.Backups.SAMPLE_WALLET, alias = "WalletC")
|
||||||
// wallet.sync().rewindToHeight(1339178).join(10000)
|
// wallet.sync().rewindToHeight(1339178).join(10000)
|
||||||
wallet.sync().rewindToHeight(1339178).send(
|
wallet.sync().rewindToHeight(BlockHeight.new(ZcashNetwork.Testnet, 1339178)).send(
|
||||||
"ztestsapling17zazsl8rryl8kjaqxnr2r29rw9d9a2mud37ugapm0s8gmyv0ue43h9lqwmhdsp3nu9dazeqfs6l",
|
"ztestsapling17zazsl8rryl8kjaqxnr2r29rw9d9a2mud37ugapm0s8gmyv0ue43h9lqwmhdsp3nu9dazeqfs6l",
|
||||||
"is send broken?"
|
"is send broken?"
|
||||||
).join(5)
|
).join(5)
|
||||||
@@ -85,7 +86,15 @@ class TransparentRestoreSample {
|
|||||||
@LargeTest
|
@LargeTest
|
||||||
@Ignore("This test is extremely slow")
|
@Ignore("This test is extremely slow")
|
||||||
fun kris() = runBlocking<Unit> {
|
fun kris() = runBlocking<Unit> {
|
||||||
val wallet0 = TestWallet(TestWallet.Backups.SAMPLE_WALLET.seedPhrase, "tmpabc", Testnet, startHeight = 1330190)
|
val wallet0 = TestWallet(
|
||||||
|
TestWallet.Backups.SAMPLE_WALLET.seedPhrase,
|
||||||
|
"tmpabc",
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
startHeight = BlockHeight.new(
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
1330190
|
||||||
|
)
|
||||||
|
)
|
||||||
// val wallet1 = SimpleWallet(WALLET0_PHRASE, "Wallet1")
|
// val wallet1 = SimpleWallet(WALLET0_PHRASE, "Wallet1")
|
||||||
|
|
||||||
wallet0.sync() // .shieldFunds()
|
wallet0.sync() // .shieldFunds()
|
||||||
@@ -107,7 +116,15 @@ class TransparentRestoreSample {
|
|||||||
*/
|
*/
|
||||||
// @Test
|
// @Test
|
||||||
fun hasFunds() = runBlocking<Unit> {
|
fun hasFunds() = runBlocking<Unit> {
|
||||||
val walletSandbox = TestWallet(TestWallet.Backups.SAMPLE_WALLET.seedPhrase, "WalletC", Testnet, startHeight = 1330190)
|
val walletSandbox = TestWallet(
|
||||||
|
TestWallet.Backups.SAMPLE_WALLET.seedPhrase,
|
||||||
|
"WalletC",
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
startHeight = BlockHeight.new(
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
1330190
|
||||||
|
)
|
||||||
|
)
|
||||||
// val job = walletA.walletScope.launch {
|
// val job = walletA.walletScope.launch {
|
||||||
// twig("Syncing WalletA")
|
// twig("Syncing WalletA")
|
||||||
// walletA.sync()
|
// walletA.sync()
|
||||||
@@ -125,7 +142,7 @@ class TransparentRestoreSample {
|
|||||||
// send z->t
|
// send z->t
|
||||||
// walletA.send(TX_VALUE, walletA.transparentAddress, "${TransparentRestoreSample::class.java.simpleName} z->t")
|
// walletA.send(TX_VALUE, walletA.transparentAddress, "${TransparentRestoreSample::class.java.simpleName} z->t")
|
||||||
|
|
||||||
walletSandbox.rewindToHeight(1339178)
|
walletSandbox.rewindToHeight(BlockHeight.new(ZcashNetwork.Testnet, 1339178))
|
||||||
twig("Done REWINDING!")
|
twig("Done REWINDING!")
|
||||||
twig("T-ADDR (for the win!): ${walletSandbox.transparentAddress}")
|
twig("T-ADDR (for the win!): ${walletSandbox.transparentAddress}")
|
||||||
delay(500)
|
delay(500)
|
||||||
|
|||||||
@@ -4,18 +4,19 @@ import android.content.Context
|
|||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool.IS_FALLBACK_ON_FAILURE
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import cash.z.ecc.android.sdk.tool.CheckpointTool.IS_FALLBACK_ON_FAILURE
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class WalletBirthdayToolTest {
|
class CheckpointToolTest {
|
||||||
@Test
|
@Test
|
||||||
@SmallTest
|
@SmallTest
|
||||||
fun birthday_height_from_filename() {
|
fun birthday_height_from_filename() {
|
||||||
assertEquals(123, WalletBirthdayTool.birthdayHeight("123.json"))
|
assertEquals(123, CheckpointTool.checkpointHeightFromFilename(ZcashNetwork.Mainnet, "123.json"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -27,13 +28,14 @@ class WalletBirthdayToolTest {
|
|||||||
|
|
||||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||||
val birthday = runBlocking {
|
val birthday = runBlocking {
|
||||||
WalletBirthdayTool.getFirstValidWalletBirthday(
|
CheckpointTool.getFirstValidWalletBirthday(
|
||||||
context,
|
context,
|
||||||
|
ZcashNetwork.Mainnet,
|
||||||
directory,
|
directory,
|
||||||
listOf("1300000.json", "1290000.json")
|
listOf("1300000.json", "1290000.json")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
assertEquals(1300000, birthday.height)
|
assertEquals(1300000, birthday.height.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -46,12 +48,13 @@ class WalletBirthdayToolTest {
|
|||||||
val directory = "co.electriccoin.zcash/checkpoint/badnet"
|
val directory = "co.electriccoin.zcash/checkpoint/badnet"
|
||||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||||
val birthday = runBlocking {
|
val birthday = runBlocking {
|
||||||
WalletBirthdayTool.getFirstValidWalletBirthday(
|
CheckpointTool.getFirstValidWalletBirthday(
|
||||||
context,
|
context,
|
||||||
|
ZcashNetwork.Mainnet,
|
||||||
directory,
|
directory,
|
||||||
listOf("1300000.json", "1290000.json")
|
listOf("1300000.json", "1290000.json")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
assertEquals(1290000, birthday.height)
|
assertEquals(1290000, birthday.height.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
package cash.z.ecc.android.sdk.util
|
package cash.z.ecc.android.sdk.util
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|||||||
@@ -6,10 +6,13 @@ import cash.z.ecc.android.sdk.Synchronizer
|
|||||||
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||||
import cash.z.ecc.android.sdk.internal.Twig
|
import cash.z.ecc.android.sdk.internal.Twig
|
||||||
import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
|
import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
|
||||||
|
import cash.z.ecc.android.sdk.internal.model.Checkpoint
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.ecc.android.sdk.type.WalletBirthday
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import cash.z.ecc.android.sdk.model.defaultForNetwork
|
||||||
|
import cash.z.ecc.android.sdk.tool.CheckpointTool
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -30,7 +33,7 @@ class BalancePrinterUtil {
|
|||||||
|
|
||||||
private val network = ZcashNetwork.Mainnet
|
private val network = ZcashNetwork.Mainnet
|
||||||
private val downloadBatchSize = 9_000
|
private val downloadBatchSize = 9_000
|
||||||
private val birthdayHeight = 523240
|
private val birthdayHeight = BlockHeight.new(network, 523240)
|
||||||
|
|
||||||
private val mnemonics = SimpleMnemonics()
|
private val mnemonics = SimpleMnemonics()
|
||||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
@@ -46,14 +49,14 @@ class BalancePrinterUtil {
|
|||||||
|
|
||||||
// private val rustBackend = RustBackend.init(context, cacheDbName, dataDbName)
|
// private val rustBackend = RustBackend.init(context, cacheDbName, dataDbName)
|
||||||
|
|
||||||
private lateinit var birthday: WalletBirthday
|
private lateinit var birthday: Checkpoint
|
||||||
private var synchronizer: Synchronizer? = null
|
private var synchronizer: Synchronizer? = null
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
Twig.plant(TroubleshootingTwig())
|
Twig.plant(TroubleshootingTwig())
|
||||||
cacheBlocks()
|
cacheBlocks()
|
||||||
birthday = runBlocking { WalletBirthdayTool.loadNearest(context, network, birthdayHeight) }
|
birthday = runBlocking { CheckpointTool.loadNearest(context, network, birthdayHeight) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cacheBlocks() = runBlocking {
|
private fun cacheBlocks() = runBlocking {
|
||||||
@@ -81,8 +84,8 @@ class BalancePrinterUtil {
|
|||||||
}.collect { seed ->
|
}.collect { seed ->
|
||||||
// TODO: clear the dataDb but leave the cacheDb
|
// TODO: clear the dataDb but leave the cacheDb
|
||||||
val initializer = Initializer.new(context) { config ->
|
val initializer = Initializer.new(context) { config ->
|
||||||
runBlocking { config.importWallet(seed, birthdayHeight, network) }
|
val endpoint = LightWalletEndpoint.defaultForNetwork(network)
|
||||||
config.setNetwork(network)
|
runBlocking { config.importWallet(seed, birthdayHeight, network, endpoint) }
|
||||||
config.alias = alias
|
config.alias = alias
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import cash.z.ecc.android.sdk.SdkSynchronizer
|
|||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||||
import cash.z.ecc.android.sdk.internal.Twig
|
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 kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
@@ -36,7 +38,7 @@ class DataDbScannerUtil {
|
|||||||
|
|
||||||
// private val rustBackend = RustBackend.init(context, cacheDbName, dataDbName)
|
// private val rustBackend = RustBackend.init(context, cacheDbName, dataDbName)
|
||||||
|
|
||||||
private val birthdayHeight = 600_000
|
private val birthdayHeight = 600_000L
|
||||||
private lateinit var synchronizer: Synchronizer
|
private lateinit var synchronizer: Synchronizer
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -67,7 +69,11 @@ class DataDbScannerUtil {
|
|||||||
val initializer = runBlocking {
|
val initializer = runBlocking {
|
||||||
Initializer.new(context) {
|
Initializer.new(context) {
|
||||||
it.setBirthdayHeight(
|
it.setBirthdayHeight(
|
||||||
birthdayHeight
|
BlockHeight.new(
|
||||||
|
ZcashNetwork.Mainnet,
|
||||||
|
birthdayHeight
|
||||||
|
),
|
||||||
|
false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,13 @@ import cash.z.ecc.android.sdk.db.entity.isPending
|
|||||||
import cash.z.ecc.android.sdk.internal.Twig
|
import cash.z.ecc.android.sdk.internal.Twig
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.Testnet
|
||||||
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.type.WalletBalance
|
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -35,9 +38,8 @@ class TestWallet(
|
|||||||
val seedPhrase: String,
|
val seedPhrase: String,
|
||||||
val alias: String = "TestWallet",
|
val alias: String = "TestWallet",
|
||||||
val network: ZcashNetwork = ZcashNetwork.Testnet,
|
val network: ZcashNetwork = ZcashNetwork.Testnet,
|
||||||
val host: String = network.defaultHost,
|
val endpoint: LightWalletEndpoint = LightWalletEndpoint.Testnet,
|
||||||
startHeight: Int? = null,
|
startHeight: BlockHeight? = null
|
||||||
val port: Int = network.defaultPort
|
|
||||||
) {
|
) {
|
||||||
constructor(
|
constructor(
|
||||||
backup: Backups,
|
backup: Backups,
|
||||||
@@ -65,7 +67,7 @@ class TestWallet(
|
|||||||
runBlocking { DerivationTool.deriveTransparentSecretKey(seed, network = network) }
|
runBlocking { DerivationTool.deriveTransparentSecretKey(seed, network = network) }
|
||||||
val initializer = runBlocking {
|
val initializer = runBlocking {
|
||||||
Initializer.new(context) { config ->
|
Initializer.new(context) { config ->
|
||||||
runBlocking { config.importWallet(seed, startHeight, network, host, alias = alias) }
|
runBlocking { config.importWallet(seed, startHeight, network, endpoint, alias = alias) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(initializer) as SdkSynchronizer
|
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(initializer) as SdkSynchronizer
|
||||||
@@ -78,14 +80,11 @@ class TestWallet(
|
|||||||
runBlocking { DerivationTool.deriveTransparentAddress(seed, network = network) }
|
runBlocking { DerivationTool.deriveTransparentAddress(seed, network = network) }
|
||||||
val birthdayHeight get() = synchronizer.latestBirthdayHeight
|
val birthdayHeight get() = synchronizer.latestBirthdayHeight
|
||||||
val networkName get() = synchronizer.network.networkName
|
val networkName get() = synchronizer.network.networkName
|
||||||
val connectionInfo get() = service.connectionInfo.toString()
|
|
||||||
|
|
||||||
/* NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
suspend fun transparentBalance(): WalletBalance {
|
suspend fun transparentBalance(): WalletBalance {
|
||||||
synchronizer.refreshUtxos(transparentAddress, synchronizer.latestBirthdayHeight)
|
synchronizer.refreshUtxos(transparentAddress, synchronizer.latestBirthdayHeight)
|
||||||
return synchronizer.getTransparentBalance(transparentAddress)
|
return synchronizer.getTransparentBalance(transparentAddress)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
suspend fun sync(timeout: Long = -1): TestWallet {
|
suspend fun sync(timeout: Long = -1): TestWallet {
|
||||||
val killSwitch = walletScope.launch {
|
val killSwitch = walletScope.launch {
|
||||||
@@ -111,7 +110,7 @@ class TestWallet(
|
|||||||
suspend fun send(address: String = transparentAddress, memo: String = "", amount: Zatoshi = Zatoshi(500L), fromAccountIndex: Int = 0): TestWallet {
|
suspend fun send(address: String = transparentAddress, memo: String = "", amount: Zatoshi = Zatoshi(500L), fromAccountIndex: Int = 0): TestWallet {
|
||||||
Twig.sprout("$alias sending")
|
Twig.sprout("$alias sending")
|
||||||
synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo, fromAccountIndex)
|
synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo, fromAccountIndex)
|
||||||
.takeWhile { it.isPending() }
|
.takeWhile { it.isPending(null) }
|
||||||
.collect {
|
.collect {
|
||||||
twig("Updated transaction: $it")
|
twig("Updated transaction: $it")
|
||||||
}
|
}
|
||||||
@@ -119,15 +118,14 @@ class TestWallet(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun rewindToHeight(height: Int): TestWallet {
|
suspend fun rewindToHeight(height: BlockHeight): TestWallet {
|
||||||
synchronizer.rewindToNearestHeight(height, false)
|
synchronizer.rewindToNearestHeight(height, false)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
suspend fun shieldFunds(): TestWallet {
|
suspend fun shieldFunds(): TestWallet {
|
||||||
twig("checking $transparentAddress for transactions!")
|
twig("checking $transparentAddress for transactions!")
|
||||||
synchronizer.refreshUtxos(transparentAddress, 935000).let { count ->
|
synchronizer.refreshUtxos(transparentAddress, BlockHeight.new(ZcashNetwork.Mainnet, 935000)).let { count ->
|
||||||
twig("FOUND $count new UTXOs")
|
twig("FOUND $count new UTXOs")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +142,6 @@ class TestWallet(
|
|||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
suspend fun join(timeout: Long? = null): TestWallet {
|
suspend fun join(timeout: Long? = null): TestWallet {
|
||||||
// block until stopped
|
// block until stopped
|
||||||
@@ -167,13 +164,48 @@ class TestWallet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Backups(val seedPhrase: String, val testnetBirthday: Int, val mainnetBirthday: Int) {
|
enum class Backups(val seedPhrase: String, val testnetBirthday: BlockHeight, val mainnetBirthday: BlockHeight) {
|
||||||
// TODO: get the proper birthday values for these wallets
|
// TODO: get the proper birthday values for these wallets
|
||||||
DEFAULT("column rhythm acoustic gym cost fit keen maze fence seed mail medal shrimp tell relief clip cannon foster soldier shallow refuse lunar parrot banana", 1_355_928, 1_000_000),
|
DEFAULT(
|
||||||
SAMPLE_WALLET("input frown warm senior anxiety abuse yard prefer churn reject people glimpse govern glory crumble swallow verb laptop switch trophy inform friend permit purpose", 1_330_190, 1_000_000),
|
"column rhythm acoustic gym cost fit keen maze fence seed mail medal shrimp tell relief clip cannon foster soldier shallow refuse lunar parrot banana",
|
||||||
DEV_WALLET("still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread", 1_000_000, 991645),
|
BlockHeight.new(
|
||||||
ALICE("quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely life cliff spirit admit weekend message recipe trumpet impact kitten", 1_330_190, 1_000_000),
|
ZcashNetwork.Testnet,
|
||||||
BOB("canvas wine sugar acquire garment spy tongue odor hole cage year habit bullet make label human unit option top calm neutral try vocal arena", 1_330_190, 1_000_000),
|
1_355_928
|
||||||
|
),
|
||||||
|
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
|
||||||
|
),
|
||||||
|
SAMPLE_WALLET(
|
||||||
|
"input frown warm senior anxiety abuse yard prefer churn reject people glimpse govern glory crumble swallow verb laptop switch trophy inform friend permit purpose",
|
||||||
|
BlockHeight.new(
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
1_330_190
|
||||||
|
),
|
||||||
|
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
|
||||||
|
),
|
||||||
|
DEV_WALLET(
|
||||||
|
"still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread",
|
||||||
|
BlockHeight.new(
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
1_000_000
|
||||||
|
),
|
||||||
|
BlockHeight.new(ZcashNetwork.Mainnet, 991645)
|
||||||
|
),
|
||||||
|
ALICE(
|
||||||
|
"quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely life cliff spirit admit weekend message recipe trumpet impact kitten",
|
||||||
|
BlockHeight.new(
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
1_330_190
|
||||||
|
),
|
||||||
|
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
|
||||||
|
),
|
||||||
|
BOB(
|
||||||
|
"canvas wine sugar acquire garment spy tongue odor hole cage year habit bullet make label human unit option top calm neutral try vocal arena",
|
||||||
|
BlockHeight.new(
|
||||||
|
ZcashNetwork.Testnet,
|
||||||
|
1_330_190
|
||||||
|
),
|
||||||
|
BlockHeight.new(ZcashNetwork.Mainnet, 1_000_000)
|
||||||
|
),
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
|||||||
import cash.z.ecc.android.sdk.internal.Twig
|
import cash.z.ecc.android.sdk.internal.Twig
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.Mainnet
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@@ -13,7 +16,7 @@ class TransactionCounterUtil {
|
|||||||
|
|
||||||
private val network = ZcashNetwork.Mainnet
|
private val network = ZcashNetwork.Mainnet
|
||||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
private val service = LightWalletGrpcService(context, network)
|
private val service = LightWalletGrpcService.new(context, LightWalletEndpoint.Mainnet)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Twig.plant(TroubleshootingTwig())
|
Twig.plant(TroubleshootingTwig())
|
||||||
@@ -23,7 +26,12 @@ class TransactionCounterUtil {
|
|||||||
@Ignore("This test is broken")
|
@Ignore("This test is broken")
|
||||||
fun testBlockSize() {
|
fun testBlockSize() {
|
||||||
val sizes = mutableMapOf<Int, Int>()
|
val sizes = mutableMapOf<Int, Int>()
|
||||||
service.getBlockRange(900_000..910_000).forEach { b ->
|
service.getBlockRange(
|
||||||
|
BlockHeight.new(ZcashNetwork.Mainnet, 900_000)..BlockHeight.new(
|
||||||
|
ZcashNetwork.Mainnet,
|
||||||
|
910_000
|
||||||
|
)
|
||||||
|
).forEach { b ->
|
||||||
twig("h: ${b.header.size()}")
|
twig("h: ${b.header.size()}")
|
||||||
val s = b.serializedSize
|
val s = b.serializedSize
|
||||||
sizes[s] = (sizes[s] ?: 0) + 1
|
sizes[s] = (sizes[s] ?: 0) + 1
|
||||||
@@ -38,7 +46,12 @@ class TransactionCounterUtil {
|
|||||||
val outputCounts = mutableMapOf<Int, Int>()
|
val outputCounts = mutableMapOf<Int, Int>()
|
||||||
var totalOutputs = 0
|
var totalOutputs = 0
|
||||||
var totalTxs = 0
|
var totalTxs = 0
|
||||||
service.getBlockRange(900_000..950_000).forEach { b ->
|
service.getBlockRange(
|
||||||
|
BlockHeight.new(ZcashNetwork.Mainnet, 900_000)..BlockHeight.new(
|
||||||
|
ZcashNetwork.Mainnet,
|
||||||
|
950_000
|
||||||
|
)
|
||||||
|
).forEach { b ->
|
||||||
b.header.size()
|
b.header.size()
|
||||||
b.vtxList.map { it.outputsCount }.forEach { oCount ->
|
b.vtxList.map { it.outputsCount }.forEach { oCount ->
|
||||||
outputCounts[oCount] = (outputCounts[oCount] ?: 0) + oCount.coerceAtLeast(1)
|
outputCounts[oCount] = (outputCounts[oCount] ?: 0) + oCount.coerceAtLeast(1)
|
||||||
|
|||||||
@@ -6,31 +6,34 @@ import cash.z.ecc.android.sdk.internal.KEY_HEIGHT
|
|||||||
import cash.z.ecc.android.sdk.internal.KEY_TREE
|
import cash.z.ecc.android.sdk.internal.KEY_TREE
|
||||||
import cash.z.ecc.android.sdk.internal.KEY_VERSION
|
import cash.z.ecc.android.sdk.internal.KEY_VERSION
|
||||||
import cash.z.ecc.android.sdk.internal.VERSION_1
|
import cash.z.ecc.android.sdk.internal.VERSION_1
|
||||||
import cash.z.ecc.android.sdk.type.WalletBirthday
|
import cash.z.ecc.android.sdk.internal.model.Checkpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
object WalletBirthdayFixture {
|
object CheckpointFixture {
|
||||||
|
val NETWORK = ZcashNetwork.Mainnet
|
||||||
|
|
||||||
// These came from the mainnet 1500000.json file
|
// These came from the mainnet 1500000.json file
|
||||||
const val HEIGHT = 1500000
|
val HEIGHT = BlockHeight.new(ZcashNetwork.Mainnet, 1500000L)
|
||||||
const val HASH = "00000000019e5b25a95c7607e7789eb326fddd69736970ebbe1c7d00247ef902"
|
const val HASH = "00000000019e5b25a95c7607e7789eb326fddd69736970ebbe1c7d00247ef902"
|
||||||
const val EPOCH_SECONDS = 1639913234L
|
const val EPOCH_SECONDS = 1639913234L
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
const val TREE = "01ce183032b16ed87fcc5052a42d908376526126346567773f55bc58a63e4480160013000001bae5112769a07772345dd402039f2949c457478fe9327363ff631ea9d78fb80d0177c0b6c21aa9664dc255336ed450914088108c38a9171c85875b4e53d31b3e140171add6f9129e124651ca894aa842a3c71b1738f3ee2b7ba829106524ef51e62101f9cebe2141ee9d0a3f3a3e28bce07fa6b6e1c7b42c01cc4fe611269e9d52da540001d0adff06de48569129bd2a211e3253716362da97270d3504d9c1b694689ebe3c0122aaaea90a7fa2773b8166937310f79a4278b25d759128adf3138d052da3725b0137fb2cbc176075a45db2a3c32d3f78e669ff2258fd974e99ec9fb314d7fd90180165aaee3332ea432d13a9398c4863b38b8a7a491877a5c46b0802dcd88f7e324301a9a262f8b92efc2e0e3e4bd1207486a79d62e87b4ab9cc41814d62a23c4e28040001e3c4ee998682df5c5e230d6968e947f83d0c03682f0cfc85f1e6ec8e8552c95a000155989fed7a8cc7a0d479498d6881ca3bafbe05c7095110f85c64442d6a06c25c0185cd8c141e620eda0ca0516f42240aedfabdf9189c8c6ac834b7bdebc171331d01ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
const val TREE = "01ce183032b16ed87fcc5052a42d908376526126346567773f55bc58a63e4480160013000001bae5112769a07772345dd402039f2949c457478fe9327363ff631ea9d78fb80d0177c0b6c21aa9664dc255336ed450914088108c38a9171c85875b4e53d31b3e140171add6f9129e124651ca894aa842a3c71b1738f3ee2b7ba829106524ef51e62101f9cebe2141ee9d0a3f3a3e28bce07fa6b6e1c7b42c01cc4fe611269e9d52da540001d0adff06de48569129bd2a211e3253716362da97270d3504d9c1b694689ebe3c0122aaaea90a7fa2773b8166937310f79a4278b25d759128adf3138d052da3725b0137fb2cbc176075a45db2a3c32d3f78e669ff2258fd974e99ec9fb314d7fd90180165aaee3332ea432d13a9398c4863b38b8a7a491877a5c46b0802dcd88f7e324301a9a262f8b92efc2e0e3e4bd1207486a79d62e87b4ab9cc41814d62a23c4e28040001e3c4ee998682df5c5e230d6968e947f83d0c03682f0cfc85f1e6ec8e8552c95a000155989fed7a8cc7a0d479498d6881ca3bafbe05c7095110f85c64442d6a06c25c0185cd8c141e620eda0ca0516f42240aedfabdf9189c8c6ac834b7bdebc171331d01ecceb776c043662617d62646ee60985521b61c0b860f3a9731e66ef74ed8fb320118f64df255c9c43db708255e7bf6bffd481e5c2f38fe9ed8f3d189f7f9cf2644"
|
||||||
|
|
||||||
fun new(
|
internal fun new(
|
||||||
height: Int = HEIGHT,
|
height: BlockHeight = HEIGHT,
|
||||||
hash: String = HASH,
|
hash: String = HASH,
|
||||||
time: Long = EPOCH_SECONDS,
|
time: Long = EPOCH_SECONDS,
|
||||||
tree: String = TREE
|
tree: String = TREE
|
||||||
) = WalletBirthday(height = height, hash = hash, time = time, tree = tree)
|
) = Checkpoint(height = height, hash = hash, epochSeconds = time, tree = tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun WalletBirthday.toJson() = JSONObject().apply {
|
internal fun Checkpoint.toJson() = JSONObject().apply {
|
||||||
put(WalletBirthday.KEY_VERSION, WalletBirthday.VERSION_1)
|
put(Checkpoint.KEY_VERSION, Checkpoint.VERSION_1)
|
||||||
put(WalletBirthday.KEY_HEIGHT, height)
|
put(Checkpoint.KEY_HEIGHT, height.value)
|
||||||
put(WalletBirthday.KEY_HASH, hash)
|
put(Checkpoint.KEY_HASH, hash)
|
||||||
put(WalletBirthday.KEY_EPOCH_SECONDS, time)
|
put(Checkpoint.KEY_EPOCH_SECONDS, epochSeconds)
|
||||||
put(WalletBirthday.KEY_TREE, tree)
|
put(Checkpoint.KEY_TREE, tree)
|
||||||
}.toString()
|
}.toString()
|
||||||
@@ -3,5 +3,5 @@
|
|||||||
"height": "1150000",
|
"height": "1150000",
|
||||||
"hash": "0000000650e627bd7da6868f14070aff8fdbd31ef7125fe77851976ed3adfc54",
|
"hash": "0000000650e627bd7da6868f14070aff8fdbd31ef7125fe77851976ed3adfc54",
|
||||||
"time": 1668316308,
|
"time": 1668316308,
|
||||||
"saplingtree": "012e058162e4e6bc9553c413134b66e5e89cd63c330fc557060878c623fdedc63d01533dbef6ad6b226c7138bef1e1961ca170510a91fdb1ff5972e1cd79c347401c150168979af907639a39b427d83d4602fd22867faffb0942cbb11955dac680aab85901d8d396d94feb0cc78cbec422c6fd09834c4031a1ffdcbef04178827add5193160102e9b3fecaf39d40e5e63ef3253d1ac5aee4141bdf26763de74033d6016f5955019f59fdd4a570ad22980428caea7b5fa61f55b52e2e6fbb29600eee53d31ed35f017c5ad297c1e83430ce9d3768fd38e74bd06757b67e2917b34d0f3f763803f32600000000015c1016fb7c68d85099ce8423d6446c2ea3d77a63b5ab6044f6eb0c024ddb0d5a01627b5eae7588998ef2645fe8be1ec3227d560828956b7df00632b26784c4f80a0000012d8bdb15bce00ab0c8bd332355a100d9db356ac05fb97412b479214dcefea331000001a5e10b312a666ed313eb0db76bfc977430ec6b463f944816c43cd82d42181d1f000001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
|
"saplingTree": "012e058162e4e6bc9553c413134b66e5e89cd63c330fc557060878c623fdedc63d01533dbef6ad6b226c7138bef1e1961ca170510a91fdb1ff5972e1cd79c347401c150168979af907639a39b427d83d4602fd22867faffb0942cbb11955dac680aab85901d8d396d94feb0cc78cbec422c6fd09834c4031a1ffdcbef04178827add5193160102e9b3fecaf39d40e5e63ef3253d1ac5aee4141bdf26763de74033d6016f5955019f59fdd4a570ad22980428caea7b5fa61f55b52e2e6fbb29600eee53d31ed35f017c5ad297c1e83430ce9d3768fd38e74bd06757b67e2917b34d0f3f763803f32600000000015c1016fb7c68d85099ce8423d6446c2ea3d77a63b5ab6044f6eb0c024ddb0d5a01627b5eae7588998ef2645fe8be1ec3227d560828956b7df00632b26784c4f80a0000012d8bdb15bce00ab0c8bd332355a100d9db356ac05fb97412b479214dcefea331000001a5e10b312a666ed313eb0db76bfc977430ec6b463f944816c43cd82d42181d1f000001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"network": "main",
|
||||||
|
"height": "1160000",
|
||||||
|
"hash": "000000006904fea1620eb53ad7f912197881e3d47a8a6683d943efc0ff43c94e",
|
||||||
|
"time": 1669068992,
|
||||||
|
"saplingTree": "01569b6b693b7fc56715b01e81a3d07dcfd723f54dc8f31cdb465509f39304755800150001dcf14961a27da1444097a9618a6a3d4a6a198b4afc8c9c2960e30da42c5790210001f95f7e52338af7b69ae511d4ae5c1fbaa394380e92fbb51cddfdc4b8b0cfef0601cd00c0bca2308a408b94327da9505d55a241aa05ae730884ee38c8d553d218290000000001d637d869f0247b4dc198156dee890fa12f44ad83c1ecd9f73bb89ab578ff31260001cebd13b9f31ab7c442c5a89562817d5b590b51ac75d735041d2389ceabd3cf7101e3a962887edb0c646e6390f87eb64ab4b12753338b8b72f3afcbca0b499b1f3a01447d8106fe66cea724f27e0f0310821d7e5c536b02f4540ce14f6359ec30650a014431c3e5e81d9dec6f8c51f83ad971de208c6a7d990f727f2b203af910f760410001a5e10b312a666ed313eb0db76bfc977430ec6b463f944816c43cd82d42181d1f000001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"network": "test",
|
|
||||||
"height": "1000000",
|
|
||||||
"hash": "00000002e920ff85f57a164e0637d12147e95428b4da8ca2f0dde4192f5df829",
|
|
||||||
"time": 1657012340,
|
|
||||||
"saplingTree": "014e4c1dba2b623864148a9dc4828fc67ee7f231907425be3695949749092e845b01ebe665e0198710c608da05a8a7964700642cbfd9b7bcac005ed967df32357e6a140001688ee9449b2d45afe7fb411f19c58f56269a43967fc1d1fdb9fd28edc2344016018746e37c9fd6c64662e26f2e146427a69ed3800280a9124c4d8c19f25a2aa155011b980b27687b29f85d315c87db08ea5559cc812159a838240b45bd88fd545059000171bc61ad6eacc00f6252a2898470e6a6391311db651b443e74f1f506e35ae61b011ca124f40831dd203b8d4eb8386f58b593d503168df44aa2eed6f4e3fdacd51000000001216607336029bd6f322d3decb8dd7ae1f8df1760b240030f1879b7aa3b4c646e0001f529692b1ee5845a6e0681b2efcc66342586397c79b09cdefa957aa1b0e614310156e2babf0dca08c8b1991c00a5d74d740e7d0c4b95099065016719e93455833a018d57c3859e298989e2eae8e1d8f9135944eef930e3ad20330e0de0541aacc94801f2cec17739de7e1476938f895b1a6381b36ec44ccdbbac2eeb60be43be6f815e01a8d81d60d7de99c1e45988bc29029102ab653c13b490ee0133dc739bf63a971601437aa93f8bebde50ea70fd8c7b60fe826aa6892fd6a9c6d72a60a7f8d12bea58000108a67f0c370f350b9179a081f2fc8d33b62e01e729419860b5ae143cbbcd2769"
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"network": "main",
|
||||||
|
"height": "1150000",
|
||||||
|
"hash": "0000000650e627bd7da6868f14070aff8fdbd31ef7125fe77851976ed3adfc54",
|
||||||
|
"time": 1668316308,
|
||||||
|
"saplingTree": "012e058162e4e6bc9553c413134b66e5e89cd63c330fc557060878c623fdedc63d01533dbef6ad6b226c7138bef1e1961ca170510a91fdb1ff5972e1cd79c347401c150168979af907639a39b427d83d4602fd22867faffb0942cbb11955dac680aab85901d8d396d94feb0cc78cbec422c6fd09834c4031a1ffdcbef04178827add5193160102e9b3fecaf39d40e5e63ef3253d1ac5aee4141bdf26763de74033d6016f5955019f59fdd4a570ad22980428caea7b5fa61f55b52e2e6fbb29600eee53d31ed35f017c5ad297c1e83430ce9d3768fd38e74bd06757b67e2917b34d0f3f763803f32600000000015c1016fb7c68d85099ce8423d6446c2ea3d77a63b5ab6044f6eb0c024ddb0d5a01627b5eae7588998ef2645fe8be1ec3227d560828956b7df00632b26784c4f80a0000012d8bdb15bce00ab0c8bd332355a100d9db356ac05fb97412b479214dcefea331000001a5e10b312a666ed313eb0db76bfc977430ec6b463f944816c43cd82d42181d1f000001708c9850eb440b259f233187662c5228804cb4500263949301b6fac8f6428f2301d6f84c424acdb1d10f8cef641662e0f63f954f07fe6199d504a61979c9ba3e13"
|
||||||
|
}
|
||||||
@@ -6,13 +6,15 @@ import cash.z.ecc.android.sdk.ext.ZcashSdk
|
|||||||
import cash.z.ecc.android.sdk.internal.SdkDispatchers
|
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.getCacheDirSuspend
|
||||||
import cash.z.ecc.android.sdk.internal.ext.getDatabasePathSuspend
|
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.internal.twig
|
||||||
import cash.z.ecc.android.sdk.jni.RustBackend
|
import cash.z.ecc.android.sdk.jni.RustBackend
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import cash.z.ecc.android.sdk.tool.CheckpointTool
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
|
|
||||||
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
|
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
|
||||||
import cash.z.ecc.android.sdk.type.WalletBirthday
|
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -20,16 +22,16 @@ import java.io.File
|
|||||||
/**
|
/**
|
||||||
* Simplified Initializer focused on starting from a ViewingKey.
|
* Simplified Initializer focused on starting from a ViewingKey.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("LongParameterList")
|
||||||
class Initializer private constructor(
|
class Initializer private constructor(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
val rustBackend: RustBackend,
|
internal val rustBackend: RustBackend,
|
||||||
val network: ZcashNetwork,
|
val network: ZcashNetwork,
|
||||||
val alias: String,
|
val alias: String,
|
||||||
val host: String,
|
val lightWalletEndpoint: LightWalletEndpoint,
|
||||||
val port: Int,
|
|
||||||
val viewingKeys: List<UnifiedViewingKey>,
|
val viewingKeys: List<UnifiedViewingKey>,
|
||||||
val overwriteVks: Boolean,
|
val overwriteVks: Boolean,
|
||||||
val birthday: WalletBirthday
|
internal val checkpoint: Checkpoint
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun erase() = erase(context, network, alias)
|
suspend fun erase() = erase(context, network, alias)
|
||||||
@@ -38,16 +40,13 @@ class Initializer private constructor(
|
|||||||
val viewingKeys: MutableList<UnifiedViewingKey> = mutableListOf(),
|
val viewingKeys: MutableList<UnifiedViewingKey> = mutableListOf(),
|
||||||
var alias: String = ZcashSdk.DEFAULT_ALIAS
|
var alias: String = ZcashSdk.DEFAULT_ALIAS
|
||||||
) {
|
) {
|
||||||
var birthdayHeight: Int? = null
|
var birthdayHeight: BlockHeight? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
lateinit var network: ZcashNetwork
|
lateinit var network: ZcashNetwork
|
||||||
private set
|
private set
|
||||||
|
|
||||||
lateinit var host: String
|
lateinit var lightWalletEndpoint: LightWalletEndpoint
|
||||||
private set
|
|
||||||
|
|
||||||
var port: Int = ZcashNetwork.Mainnet.defaultPort
|
|
||||||
private set
|
private set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,7 +85,7 @@ class Initializer private constructor(
|
|||||||
* transactions. Again, this value is only considered when [height] is null.
|
* transactions. Again, this value is only considered when [height] is null.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
fun setBirthdayHeight(height: Int?, defaultToOldestHeight: Boolean = false): Config =
|
fun setBirthdayHeight(height: BlockHeight?, defaultToOldestHeight: Boolean): Config =
|
||||||
apply {
|
apply {
|
||||||
this.birthdayHeight = height
|
this.birthdayHeight = height
|
||||||
this.defaultToOldestHeight = defaultToOldestHeight
|
this.defaultToOldestHeight = defaultToOldestHeight
|
||||||
@@ -105,7 +104,7 @@ class Initializer private constructor(
|
|||||||
* importing a pre-existing wallet. It is the same as calling
|
* importing a pre-existing wallet. It is the same as calling
|
||||||
* `birthdayHeight = importedHeight`.
|
* `birthdayHeight = importedHeight`.
|
||||||
*/
|
*/
|
||||||
fun importedWalletBirthday(importedHeight: Int?): Config = apply {
|
fun importedWalletBirthday(importedHeight: BlockHeight?): Config = apply {
|
||||||
birthdayHeight = importedHeight
|
birthdayHeight = importedHeight
|
||||||
defaultToOldestHeight = true
|
defaultToOldestHeight = true
|
||||||
}
|
}
|
||||||
@@ -159,12 +158,10 @@ class Initializer private constructor(
|
|||||||
*/
|
*/
|
||||||
fun setNetwork(
|
fun setNetwork(
|
||||||
network: ZcashNetwork,
|
network: ZcashNetwork,
|
||||||
host: String = network.defaultHost,
|
lightWalletEndpoint: LightWalletEndpoint
|
||||||
port: Int = network.defaultPort
|
|
||||||
): Config = apply {
|
): Config = apply {
|
||||||
this.network = network
|
this.network = network
|
||||||
this.host = host
|
this.lightWalletEndpoint = lightWalletEndpoint
|
||||||
this.port = port
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -172,18 +169,16 @@ class Initializer private constructor(
|
|||||||
*/
|
*/
|
||||||
suspend fun importWallet(
|
suspend fun importWallet(
|
||||||
seed: ByteArray,
|
seed: ByteArray,
|
||||||
birthdayHeight: Int? = null,
|
birthday: BlockHeight?,
|
||||||
network: ZcashNetwork,
|
network: ZcashNetwork,
|
||||||
host: String = network.defaultHost,
|
lightWalletEndpoint: LightWalletEndpoint,
|
||||||
port: Int = network.defaultPort,
|
|
||||||
alias: String = ZcashSdk.DEFAULT_ALIAS
|
alias: String = ZcashSdk.DEFAULT_ALIAS
|
||||||
): Config =
|
): Config =
|
||||||
importWallet(
|
importWallet(
|
||||||
DerivationTool.deriveUnifiedViewingKeys(seed, network = network)[0],
|
DerivationTool.deriveUnifiedViewingKeys(seed, network = network)[0],
|
||||||
birthdayHeight,
|
birthday,
|
||||||
network,
|
network,
|
||||||
host,
|
lightWalletEndpoint,
|
||||||
port,
|
|
||||||
alias
|
alias
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -192,15 +187,14 @@ class Initializer private constructor(
|
|||||||
*/
|
*/
|
||||||
fun importWallet(
|
fun importWallet(
|
||||||
viewingKey: UnifiedViewingKey,
|
viewingKey: UnifiedViewingKey,
|
||||||
birthdayHeight: Int? = null,
|
birthday: BlockHeight?,
|
||||||
network: ZcashNetwork,
|
network: ZcashNetwork,
|
||||||
host: String = network.defaultHost,
|
lightWalletEndpoint: LightWalletEndpoint,
|
||||||
port: Int = network.defaultPort,
|
|
||||||
alias: String = ZcashSdk.DEFAULT_ALIAS
|
alias: String = ZcashSdk.DEFAULT_ALIAS
|
||||||
): Config = apply {
|
): Config = apply {
|
||||||
setViewingKeys(viewingKey)
|
setViewingKeys(viewingKey)
|
||||||
setNetwork(network, host, port)
|
setNetwork(network, lightWalletEndpoint)
|
||||||
importedWalletBirthday(birthdayHeight)
|
importedWalletBirthday(birthday)
|
||||||
this.alias = alias
|
this.alias = alias
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,14 +204,12 @@ class Initializer private constructor(
|
|||||||
suspend fun newWallet(
|
suspend fun newWallet(
|
||||||
seed: ByteArray,
|
seed: ByteArray,
|
||||||
network: ZcashNetwork,
|
network: ZcashNetwork,
|
||||||
host: String = network.defaultHost,
|
lightWalletEndpoint: LightWalletEndpoint,
|
||||||
port: Int = network.defaultPort,
|
|
||||||
alias: String = ZcashSdk.DEFAULT_ALIAS
|
alias: String = ZcashSdk.DEFAULT_ALIAS
|
||||||
): Config = newWallet(
|
): Config = newWallet(
|
||||||
DerivationTool.deriveUnifiedViewingKeys(seed, network)[0],
|
DerivationTool.deriveUnifiedViewingKeys(seed, network)[0],
|
||||||
network,
|
network,
|
||||||
host,
|
lightWalletEndpoint,
|
||||||
port,
|
|
||||||
alias
|
alias
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -227,12 +219,11 @@ class Initializer private constructor(
|
|||||||
fun newWallet(
|
fun newWallet(
|
||||||
viewingKey: UnifiedViewingKey,
|
viewingKey: UnifiedViewingKey,
|
||||||
network: ZcashNetwork,
|
network: ZcashNetwork,
|
||||||
host: String = network.defaultHost,
|
lightWalletEndpoint: LightWalletEndpoint,
|
||||||
port: Int = network.defaultPort,
|
|
||||||
alias: String = ZcashSdk.DEFAULT_ALIAS
|
alias: String = ZcashSdk.DEFAULT_ALIAS
|
||||||
): Config = apply {
|
): Config = apply {
|
||||||
setViewingKeys(viewingKey)
|
setViewingKeys(viewingKey)
|
||||||
setNetwork(network, host, port)
|
setNetwork(network, lightWalletEndpoint)
|
||||||
newWalletBirthday()
|
newWalletBirthday()
|
||||||
this.alias = alias
|
this.alias = alias
|
||||||
}
|
}
|
||||||
@@ -284,8 +275,8 @@ class Initializer private constructor(
|
|||||||
}
|
}
|
||||||
// allow either null or a value greater than the activation height
|
// allow either null or a value greater than the activation height
|
||||||
if (
|
if (
|
||||||
(birthdayHeight ?: network.saplingActivationHeight)
|
(birthdayHeight?.value ?: network.saplingActivationHeight.value)
|
||||||
< network.saplingActivationHeight
|
< network.saplingActivationHeight.value
|
||||||
) {
|
) {
|
||||||
throw InitializerException.InvalidBirthdayHeightException(birthdayHeight, network)
|
throw InitializerException.InvalidBirthdayHeightException(birthdayHeight, network)
|
||||||
}
|
}
|
||||||
@@ -328,25 +319,33 @@ class Initializer private constructor(
|
|||||||
config: Config
|
config: Config
|
||||||
): Initializer {
|
): Initializer {
|
||||||
config.validate()
|
config.validate()
|
||||||
// heightToUse hardcoded for now, otherwise detects older JSON checkpoint files.
|
|
||||||
val heightToUse = 1150000
|
|
||||||
//config.birthdayHeight
|
|
||||||
//?: (if (config.defaultToOldestHeight == true) config.network.saplingActivationHeight else null)
|
|
||||||
val loadedBirthday =
|
|
||||||
WalletBirthdayTool.loadNearest(context, config.network, heightToUse)
|
|
||||||
|
|
||||||
val rustBackend = initRustBackend(context, config.network, config.alias, loadedBirthday)
|
val loadedCheckpoint = run {
|
||||||
|
val height = config.birthdayHeight
|
||||||
|
?: if (config.defaultToOldestHeight == true) {
|
||||||
|
config.network.saplingActivationHeight
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckpointTool.loadNearest(
|
||||||
|
context,
|
||||||
|
config.network,
|
||||||
|
height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val rustBackend = initRustBackend(context, config.network, config.alias, loadedCheckpoint.height)
|
||||||
|
|
||||||
return Initializer(
|
return Initializer(
|
||||||
context.applicationContext,
|
context.applicationContext,
|
||||||
rustBackend,
|
rustBackend,
|
||||||
config.network,
|
config.network,
|
||||||
config.alias,
|
config.alias,
|
||||||
config.host,
|
config.lightWalletEndpoint,
|
||||||
config.port,
|
|
||||||
config.viewingKeys,
|
config.viewingKeys,
|
||||||
config.overwriteVks,
|
config.overwriteVks,
|
||||||
loadedBirthday
|
loadedCheckpoint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,14 +376,14 @@ class Initializer private constructor(
|
|||||||
context: Context,
|
context: Context,
|
||||||
network: ZcashNetwork,
|
network: ZcashNetwork,
|
||||||
alias: String,
|
alias: String,
|
||||||
birthday: WalletBirthday
|
blockHeight: BlockHeight
|
||||||
): RustBackend {
|
): RustBackend {
|
||||||
return RustBackend.init(
|
return RustBackend.init(
|
||||||
cacheDbPath(context, network, alias),
|
cacheDbPath(context, network, alias),
|
||||||
dataDbPath(context, network, alias),
|
dataDbPath(context, network, alias),
|
||||||
File(context.getCacheDirSuspend(), "params").absolutePath,
|
File(context.getCacheDirSuspend(), "params").absolutePath,
|
||||||
network,
|
network,
|
||||||
birthday.height
|
blockHeight
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader
|
|||||||
import cash.z.ecc.android.sdk.internal.block.CompactBlockStore
|
import cash.z.ecc.android.sdk.internal.block.CompactBlockStore
|
||||||
import cash.z.ecc.android.sdk.internal.ext.toHexReversed
|
import cash.z.ecc.android.sdk.internal.ext.toHexReversed
|
||||||
import cash.z.ecc.android.sdk.internal.ext.tryNull
|
import cash.z.ecc.android.sdk.internal.ext.tryNull
|
||||||
|
import cash.z.ecc.android.sdk.internal.isEmpty
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
||||||
import cash.z.ecc.android.sdk.internal.transaction.OutboundTransactionManager
|
import cash.z.ecc.android.sdk.internal.transaction.OutboundTransactionManager
|
||||||
@@ -46,14 +47,15 @@ import cash.z.ecc.android.sdk.internal.transaction.TransactionRepository
|
|||||||
import cash.z.ecc.android.sdk.internal.transaction.WalletTransactionEncoder
|
import cash.z.ecc.android.sdk.internal.transaction.WalletTransactionEncoder
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
import cash.z.ecc.android.sdk.internal.twigTask
|
import cash.z.ecc.android.sdk.internal.twigTask
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.type.AddressType
|
import cash.z.ecc.android.sdk.type.AddressType
|
||||||
import cash.z.ecc.android.sdk.type.AddressType.Shielded
|
import cash.z.ecc.android.sdk.type.AddressType.Shielded
|
||||||
import cash.z.ecc.android.sdk.type.AddressType.Transparent
|
import cash.z.ecc.android.sdk.type.AddressType.Transparent
|
||||||
import cash.z.ecc.android.sdk.type.ConsensusMatchType
|
import cash.z.ecc.android.sdk.type.ConsensusMatchType
|
||||||
import cash.z.ecc.android.sdk.type.WalletBalance
|
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import cash.z.wallet.sdk.rpc.Service
|
import cash.z.wallet.sdk.rpc.Service
|
||||||
import io.grpc.ManagedChannel
|
import io.grpc.ManagedChannel
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
@@ -95,7 +97,6 @@ import kotlin.coroutines.EmptyCoroutineContext
|
|||||||
*/
|
*/
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
@FlowPreview
|
@FlowPreview
|
||||||
|
|
||||||
class SdkSynchronizer internal constructor(
|
class SdkSynchronizer internal constructor(
|
||||||
private val storage: TransactionRepository,
|
private val storage: TransactionRepository,
|
||||||
private val txManager: OutboundTransactionManager,
|
private val txManager: OutboundTransactionManager,
|
||||||
@@ -103,9 +104,9 @@ class SdkSynchronizer internal constructor(
|
|||||||
) : Synchronizer {
|
) : Synchronizer {
|
||||||
|
|
||||||
// pools
|
// pools
|
||||||
//private val _orchardBalances = MutableStateFlow<WalletBalance?>(null)
|
private val _orchardBalances = MutableStateFlow<WalletBalance?>(null)
|
||||||
private val _saplingBalances = MutableStateFlow<WalletBalance?>(null)
|
private val _saplingBalances = MutableStateFlow<WalletBalance?>(null)
|
||||||
//private val _transparentBalances = MutableStateFlow<WalletBalance?>(null)
|
private val _transparentBalances = MutableStateFlow<WalletBalance?>(null)
|
||||||
|
|
||||||
private val _status = ConflatedBroadcastChannel<Synchronizer.Status>(DISCONNECTED)
|
private val _status = ConflatedBroadcastChannel<Synchronizer.Status>(DISCONNECTED)
|
||||||
|
|
||||||
@@ -144,9 +145,9 @@ class SdkSynchronizer internal constructor(
|
|||||||
// Balances
|
// Balances
|
||||||
//
|
//
|
||||||
|
|
||||||
//override val orchardBalances = _orchardBalances.asStateFlow()
|
override val orchardBalances = _orchardBalances.asStateFlow()
|
||||||
override val saplingBalances = _saplingBalances.asStateFlow()
|
override val saplingBalances = _saplingBalances.asStateFlow()
|
||||||
//override val transparentBalances = _transparentBalances.asStateFlow()
|
override val transparentBalances = _transparentBalances.asStateFlow()
|
||||||
|
|
||||||
//
|
//
|
||||||
// Transactions
|
// Transactions
|
||||||
@@ -189,7 +190,7 @@ class SdkSynchronizer internal constructor(
|
|||||||
* The latest height seen on the network while processing blocks. This may differ from the
|
* The latest height seen on the network while processing blocks. This may differ from the
|
||||||
* latest height scanned and is useful for determining block confirmations and expiration.
|
* latest height scanned and is useful for determining block confirmations and expiration.
|
||||||
*/
|
*/
|
||||||
override val networkHeight: StateFlow<Int> = processor.networkHeight
|
override val networkHeight: StateFlow<BlockHeight?> = processor.networkHeight
|
||||||
|
|
||||||
//
|
//
|
||||||
// Error Handling
|
// Error Handling
|
||||||
@@ -231,7 +232,7 @@ class SdkSynchronizer internal constructor(
|
|||||||
* A callback to invoke whenever a chain error is encountered. These occur whenever the
|
* A callback to invoke whenever a chain error is encountered. These occur whenever the
|
||||||
* processor detects a missing or non-chain-sequential block (i.e. a reorg).
|
* processor detects a missing or non-chain-sequential block (i.e. a reorg).
|
||||||
*/
|
*/
|
||||||
override var onChainErrorHandler: ((errorHeight: Int, rewindHeight: Int) -> Any)? = null
|
override var onChainErrorHandler: ((errorHeight: BlockHeight, rewindHeight: BlockHeight) -> Any)? = null
|
||||||
|
|
||||||
//
|
//
|
||||||
// Public API
|
// Public API
|
||||||
@@ -243,9 +244,11 @@ class SdkSynchronizer internal constructor(
|
|||||||
* this, a wallet will more likely want to consume the flow of processor info using
|
* this, a wallet will more likely want to consume the flow of processor info using
|
||||||
* [processorInfo].
|
* [processorInfo].
|
||||||
*/
|
*/
|
||||||
override val latestHeight: Int get() = processor.currentInfo.networkBlockHeight
|
override val latestHeight
|
||||||
|
get() = processor.currentInfo.networkBlockHeight
|
||||||
|
|
||||||
override val latestBirthdayHeight: Int get() = processor.birthdayHeight
|
override val latestBirthdayHeight
|
||||||
|
get() = processor.birthdayHeight
|
||||||
|
|
||||||
override suspend fun prepare(): Synchronizer = apply {
|
override suspend fun prepare(): Synchronizer = apply {
|
||||||
// Do nothing; this could likely be removed
|
// Do nothing; this could likely be removed
|
||||||
@@ -305,24 +308,10 @@ class SdkSynchronizer internal constructor(
|
|||||||
*/
|
*/
|
||||||
override suspend fun getServerInfo(): Service.LightdInfo = processor.downloader.getServerInfo()
|
override suspend fun getServerInfo(): Service.LightdInfo = processor.downloader.getServerInfo()
|
||||||
|
|
||||||
/**
|
override suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight =
|
||||||
* Changes the server that is being used to download compact blocks. This will throw an
|
|
||||||
* exception if it detects that the server change is invalid e.g. switching to testnet from
|
|
||||||
* mainnet.
|
|
||||||
*/
|
|
||||||
override suspend fun changeServer(host: String, port: Int, errorHandler: (Throwable) -> Unit) {
|
|
||||||
val info =
|
|
||||||
(processor.downloader.lightWalletService as LightWalletGrpcService).connectionInfo
|
|
||||||
processor.downloader.changeService(
|
|
||||||
LightWalletGrpcService(info.appContext, host, port),
|
|
||||||
errorHandler
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getNearestRewindHeight(height: Int): Int =
|
|
||||||
processor.getNearestRewindHeight(height)
|
processor.getNearestRewindHeight(height)
|
||||||
|
|
||||||
override suspend fun rewindToNearestHeight(height: Int, alsoClearBlockCache: Boolean) {
|
override suspend fun rewindToNearestHeight(height: BlockHeight, alsoClearBlockCache: Boolean) {
|
||||||
processor.rewindToNearestHeight(height, alsoClearBlockCache)
|
processor.rewindToNearestHeight(height, alsoClearBlockCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,11 +325,11 @@ class SdkSynchronizer internal constructor(
|
|||||||
|
|
||||||
// TODO: turn this section into the data access API. For now, just aggregate all the things that we want to do with the underlying data
|
// TODO: turn this section into the data access API. For now, just aggregate all the things that we want to do with the underlying data
|
||||||
|
|
||||||
suspend fun findBlockHash(height: Int): ByteArray? {
|
suspend fun findBlockHash(height: BlockHeight): ByteArray? {
|
||||||
return (storage as? PagedTransactionRepository)?.findBlockHash(height)
|
return (storage as? PagedTransactionRepository)?.findBlockHash(height)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun findBlockHashAsHex(height: Int): String? {
|
suspend fun findBlockHashAsHex(height: BlockHeight): String? {
|
||||||
return findBlockHash(height)?.toHexReversed()
|
return findBlockHash(height)?.toHexReversed()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,12 +345,10 @@ class SdkSynchronizer internal constructor(
|
|||||||
// Private API
|
// Private API
|
||||||
//
|
//
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
suspend fun refreshUtxos() {
|
suspend fun refreshUtxos() {
|
||||||
twig("refreshing utxos", -1)
|
twig("refreshing utxos", -1)
|
||||||
refreshUtxos(getTransparentAddress())
|
refreshUtxos(getTransparentAddress())
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the latest balance, based on the blocks that have been scanned and transmit this
|
* Calculate the latest balance, based on the blocks that have been scanned and transmit this
|
||||||
@@ -369,7 +356,7 @@ class SdkSynchronizer internal constructor(
|
|||||||
*/
|
*/
|
||||||
suspend fun refreshAllBalances() {
|
suspend fun refreshAllBalances() {
|
||||||
refreshSaplingBalance()
|
refreshSaplingBalance()
|
||||||
// refreshTransparentBalance()
|
refreshTransparentBalance()
|
||||||
// TODO: refresh orchard balance
|
// TODO: refresh orchard balance
|
||||||
twig("Warning: Orchard balance does not yet refresh. Only some of the plumbing is in place.")
|
twig("Warning: Orchard balance does not yet refresh. Only some of the plumbing is in place.")
|
||||||
}
|
}
|
||||||
@@ -379,14 +366,11 @@ class SdkSynchronizer internal constructor(
|
|||||||
_saplingBalances.value = processor.getBalanceInfo()
|
_saplingBalances.value = processor.getBalanceInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
suspend fun refreshTransparentBalance() {
|
suspend fun refreshTransparentBalance() {
|
||||||
twig("refreshing transparent balance")
|
twig("refreshing transparent balance")
|
||||||
_transparentBalances.value = processor.getUtxoCacheBalance(getTransparentAddress())
|
_transparentBalances.value = processor.getUtxoCacheBalance(getTransparentAddress())
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
suspend fun isValidAddress(address: String): Boolean {
|
suspend fun isValidAddress(address: String): Boolean {
|
||||||
try {
|
try {
|
||||||
return !validateAddress(address).isNotValid
|
return !validateAddress(address).isNotValid
|
||||||
@@ -394,7 +378,6 @@ class SdkSynchronizer internal constructor(
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
private fun CoroutineScope.onReady() = launch(CoroutineExceptionHandler(::onCriticalError)) {
|
private fun CoroutineScope.onReady() = launch(CoroutineExceptionHandler(::onCriticalError)) {
|
||||||
twig("Preparing to start...")
|
twig("Preparing to start...")
|
||||||
@@ -486,7 +469,7 @@ class SdkSynchronizer internal constructor(
|
|||||||
return onSetupErrorHandler?.invoke(error) == true
|
return onSetupErrorHandler?.invoke(error) == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onChainError(errorHeight: Int, rewindHeight: Int) {
|
private fun onChainError(errorHeight: BlockHeight, rewindHeight: BlockHeight) {
|
||||||
twig("Chain error detected at height: $errorHeight. Rewinding to: $rewindHeight")
|
twig("Chain error detected at height: $errorHeight. Rewinding to: $rewindHeight")
|
||||||
if (onChainErrorHandler == null) {
|
if (onChainErrorHandler == null) {
|
||||||
twig(
|
twig(
|
||||||
@@ -501,7 +484,7 @@ class SdkSynchronizer internal constructor(
|
|||||||
/**
|
/**
|
||||||
* @param elapsedMillis the amount of time that passed since the last scan
|
* @param elapsedMillis the amount of time that passed since the last scan
|
||||||
*/
|
*/
|
||||||
private suspend fun onScanComplete(scannedRange: IntRange, elapsedMillis: Long) {
|
private suspend fun onScanComplete(scannedRange: ClosedRange<BlockHeight>?, elapsedMillis: Long) {
|
||||||
// We don't need to update anything if there have been no blocks
|
// We don't need to update anything if there have been no blocks
|
||||||
// refresh anyway if:
|
// refresh anyway if:
|
||||||
// - if it's the first time we finished scanning
|
// - if it's the first time we finished scanning
|
||||||
@@ -523,7 +506,7 @@ class SdkSynchronizer internal constructor(
|
|||||||
// balance refresh is complete.
|
// balance refresh is complete.
|
||||||
if (shouldRefresh) {
|
if (shouldRefresh) {
|
||||||
twigTask("Triggering utxo refresh since $reason!", -1) {
|
twigTask("Triggering utxo refresh since $reason!", -1) {
|
||||||
//refreshUtxos()
|
refreshUtxos()
|
||||||
}
|
}
|
||||||
twigTask("Triggering balance refresh since $reason!", -1) {
|
twigTask("Triggering balance refresh since $reason!", -1) {
|
||||||
refreshAllBalances()
|
refreshAllBalances()
|
||||||
@@ -701,22 +684,17 @@ class SdkSynchronizer internal constructor(
|
|||||||
txManager.monitorById(it.id)
|
txManager.monitorById(it.id)
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
override suspend fun refreshUtxos(address: String, startHeight: BlockHeight): Int? {
|
||||||
override suspend fun refreshUtxos(address: String, startHeight: Int): Int? {
|
|
||||||
return processor.refreshUtxos(address, startHeight)
|
return processor.refreshUtxos(address, startHeight)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
override suspend fun getTransparentBalance(tAddr: String): WalletBalance {
|
override suspend fun getTransparentBalance(tAddr: String): WalletBalance {
|
||||||
return processor.getUtxoCacheBalance(tAddr)
|
return processor.getUtxoCacheBalance(tAddr)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
override suspend fun isValidShieldedAddr(address: String) =
|
override suspend fun isValidShieldedAddr(address: String) =
|
||||||
txManager.isValidShieldedAddress(address)
|
txManager.isValidShieldedAddress(address)
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
override suspend fun isValidTransparentAddr(address: String) =
|
override suspend fun isValidTransparentAddr(address: String) =
|
||||||
txManager.isValidTransparentAddress(address)
|
txManager.isValidTransparentAddress(address)
|
||||||
|
|
||||||
@@ -737,7 +715,6 @@ class SdkSynchronizer internal constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
override suspend fun validateConsensusBranch(): ConsensusMatchType {
|
override suspend fun validateConsensusBranch(): ConsensusMatchType {
|
||||||
val serverBranchId = tryNull { processor.downloader.getServerInfo().consensusBranchId }
|
val serverBranchId = tryNull { processor.downloader.getServerInfo().consensusBranchId }
|
||||||
@@ -797,18 +774,19 @@ object DefaultSynchronizerFactory {
|
|||||||
suspend fun defaultTransactionRepository(initializer: Initializer): TransactionRepository =
|
suspend fun defaultTransactionRepository(initializer: Initializer): TransactionRepository =
|
||||||
PagedTransactionRepository.new(
|
PagedTransactionRepository.new(
|
||||||
initializer.context,
|
initializer.context,
|
||||||
|
initializer.network,
|
||||||
DEFAULT_PAGE_SIZE,
|
DEFAULT_PAGE_SIZE,
|
||||||
initializer.rustBackend,
|
initializer.rustBackend,
|
||||||
initializer.birthday,
|
initializer.checkpoint,
|
||||||
initializer.viewingKeys,
|
initializer.viewingKeys,
|
||||||
initializer.overwriteVks
|
initializer.overwriteVks
|
||||||
)
|
)
|
||||||
|
|
||||||
fun defaultBlockStore(initializer: Initializer): CompactBlockStore =
|
fun defaultBlockStore(initializer: Initializer): CompactBlockStore =
|
||||||
CompactBlockDbStore.new(initializer.context, initializer.rustBackend.pathCacheDb)
|
CompactBlockDbStore.new(initializer.context, initializer.network, initializer.rustBackend.pathCacheDb)
|
||||||
|
|
||||||
fun defaultService(initializer: Initializer): LightWalletService =
|
fun defaultService(initializer: Initializer): LightWalletService =
|
||||||
LightWalletGrpcService(initializer.context, initializer.host, initializer.port)
|
LightWalletGrpcService.new(initializer.context, initializer.lightWalletEndpoint)
|
||||||
|
|
||||||
fun defaultEncoder(
|
fun defaultEncoder(
|
||||||
initializer: Initializer,
|
initializer: Initializer,
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
|||||||
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||||
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
||||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.type.AddressType
|
import cash.z.ecc.android.sdk.type.AddressType
|
||||||
import cash.z.ecc.android.sdk.type.ConsensusMatchType
|
import cash.z.ecc.android.sdk.type.ConsensusMatchType
|
||||||
import cash.z.ecc.android.sdk.type.WalletBalance
|
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import cash.z.wallet.sdk.rpc.Service
|
import cash.z.wallet.sdk.rpc.Service
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -98,12 +99,12 @@ interface Synchronizer {
|
|||||||
* latest downloaded height or scanned height. Although this is present in [processorInfo], it
|
* latest downloaded height or scanned height. Although this is present in [processorInfo], it
|
||||||
* is such a frequently used value that it is convenient to have the real-time value by itself.
|
* is such a frequently used value that it is convenient to have the real-time value by itself.
|
||||||
*/
|
*/
|
||||||
val networkHeight: StateFlow<Int>
|
val networkHeight: StateFlow<BlockHeight?>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A stream of balance values for the orchard pool. Includes the available and total balance.
|
* A stream of balance values for the orchard pool. Includes the available and total balance.
|
||||||
*/
|
*/
|
||||||
//val orchardBalances: StateFlow<WalletBalance?>
|
val orchardBalances: StateFlow<WalletBalance?>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A stream of balance values for the sapling pool. Includes the available and total balance.
|
* A stream of balance values for the sapling pool. Includes the available and total balance.
|
||||||
@@ -113,7 +114,7 @@ interface Synchronizer {
|
|||||||
/**
|
/**
|
||||||
* A stream of balance values for the transparent pool. Includes the available and total balance.
|
* A stream of balance values for the transparent pool. Includes the available and total balance.
|
||||||
*/
|
*/
|
||||||
//val transparentBalances: StateFlow<WalletBalance?>
|
val transparentBalances: StateFlow<WalletBalance?>
|
||||||
|
|
||||||
/* Transactions */
|
/* Transactions */
|
||||||
|
|
||||||
@@ -145,13 +146,13 @@ interface Synchronizer {
|
|||||||
/**
|
/**
|
||||||
* An in-memory reference to the latest height seen on the network.
|
* An in-memory reference to the latest height seen on the network.
|
||||||
*/
|
*/
|
||||||
val latestHeight: Int
|
val latestHeight: BlockHeight?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An in-memory reference to the best known birthday height, which can change if the first
|
* An in-memory reference to the best known birthday height, which can change if the first
|
||||||
* transaction has not yet occurred.
|
* transaction has not yet occurred.
|
||||||
*/
|
*/
|
||||||
val latestBirthdayHeight: Int
|
val latestBirthdayHeight: BlockHeight?
|
||||||
|
|
||||||
//
|
//
|
||||||
// Operations
|
// Operations
|
||||||
@@ -238,9 +239,7 @@ interface Synchronizer {
|
|||||||
*
|
*
|
||||||
* @throws RuntimeException when the address is invalid.
|
* @throws RuntimeException when the address is invalid.
|
||||||
*/
|
*/
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
suspend fun isValidTransparentAddr(address: String): Boolean
|
suspend fun isValidTransparentAddr(address: String): Boolean
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate whether the server and this SDK share the same consensus branch. This is
|
* Validate whether the server and this SDK share the same consensus branch. This is
|
||||||
@@ -266,9 +265,7 @@ interface Synchronizer {
|
|||||||
*
|
*
|
||||||
* @return an instance of [AddressType] providing validation info regarding the given address.
|
* @return an instance of [AddressType] providing validation info regarding the given address.
|
||||||
*/
|
*/
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
suspend fun validateAddress(address: String): AddressType
|
suspend fun validateAddress(address: String): AddressType
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to cancel a transaction that is about to be sent. Typically, cancellation is only
|
* Attempts to cancel a transaction that is about to be sent. Typically, cancellation is only
|
||||||
@@ -287,38 +284,22 @@ interface Synchronizer {
|
|||||||
*/
|
*/
|
||||||
suspend fun getServerInfo(): Service.LightdInfo
|
suspend fun getServerInfo(): Service.LightdInfo
|
||||||
|
|
||||||
/**
|
|
||||||
* Gracefully change the server that the Synchronizer is currently using. In some cases, this
|
|
||||||
* will require waiting until current network activity is complete. Ideally, this would protect
|
|
||||||
* against accidentally switching between testnet and mainnet, by comparing the service info of
|
|
||||||
* the existing server with that of the new one.
|
|
||||||
*/
|
|
||||||
suspend fun changeServer(
|
|
||||||
host: String,
|
|
||||||
port: Int = network.defaultPort,
|
|
||||||
errorHandler: (Throwable) -> Unit = { throw it }
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download all UTXOs for the given address and store any new ones in the database.
|
* Download all UTXOs for the given address and store any new ones in the database.
|
||||||
*
|
*
|
||||||
* @return the number of utxos that were downloaded and addded to the UTXO table.
|
* @return the number of utxos that were downloaded and addded to the UTXO table.
|
||||||
*/
|
*/
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
suspend fun refreshUtxos(
|
suspend fun refreshUtxos(
|
||||||
tAddr: String,
|
tAddr: String,
|
||||||
sinceHeight: Int = network.saplingActivationHeight
|
since: BlockHeight = network.saplingActivationHeight
|
||||||
): Int?
|
): Int?
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the balance that the wallet knows about. This should be called after [refreshUtxos].
|
* Returns the balance that the wallet knows about. This should be called after [refreshUtxos].
|
||||||
*/
|
*/
|
||||||
/* THIS IS NOT SUPPORT IN HUSH LIGHTWALLETD
|
|
||||||
suspend fun getTransparentBalance(tAddr: String): WalletBalance
|
suspend fun getTransparentBalance(tAddr: String): WalletBalance
|
||||||
*/
|
|
||||||
|
|
||||||
suspend fun getNearestRewindHeight(height: Int): Int
|
suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the safest height to which we can rewind, given a desire to rewind to the height
|
* Returns the safest height to which we can rewind, given a desire to rewind to the height
|
||||||
@@ -326,7 +307,7 @@ interface Synchronizer {
|
|||||||
* arbitrary height. This handles all that complexity yet remains flexible in the future as
|
* arbitrary height. This handles all that complexity yet remains flexible in the future as
|
||||||
* improvements are made.
|
* improvements are made.
|
||||||
*/
|
*/
|
||||||
suspend fun rewindToNearestHeight(height: Int, alsoClearBlockCache: Boolean = false)
|
suspend fun rewindToNearestHeight(height: BlockHeight, alsoClearBlockCache: Boolean = false)
|
||||||
|
|
||||||
suspend fun quickRewind()
|
suspend fun quickRewind()
|
||||||
|
|
||||||
@@ -380,7 +361,7 @@ interface Synchronizer {
|
|||||||
* best to log these errors because they are the most common source of bugs and unexpected
|
* best to log these errors because they are the most common source of bugs and unexpected
|
||||||
* behavior in wallets, due to the chain data mutating and wallets becoming out of sync.
|
* behavior in wallets, due to the chain data mutating and wallets becoming out of sync.
|
||||||
*/
|
*/
|
||||||
var onChainErrorHandler: ((Int, Int) -> Any)?
|
var onChainErrorHandler: ((BlockHeight, BlockHeight) -> Any)?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the status of this Synchronizer, which is useful for communicating to the user.
|
* Represents the status of this Synchronizer, which is useful for communicating to the user.
|
||||||
|
|||||||
@@ -33,13 +33,15 @@ import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader
|
|||||||
import cash.z.ecc.android.sdk.internal.ext.retryUpTo
|
import cash.z.ecc.android.sdk.internal.ext.retryUpTo
|
||||||
import cash.z.ecc.android.sdk.internal.ext.retryWithBackoff
|
import cash.z.ecc.android.sdk.internal.ext.retryWithBackoff
|
||||||
import cash.z.ecc.android.sdk.internal.ext.toHexReversed
|
import cash.z.ecc.android.sdk.internal.ext.toHexReversed
|
||||||
|
import cash.z.ecc.android.sdk.internal.isEmpty
|
||||||
import cash.z.ecc.android.sdk.internal.transaction.PagedTransactionRepository
|
import cash.z.ecc.android.sdk.internal.transaction.PagedTransactionRepository
|
||||||
import cash.z.ecc.android.sdk.internal.transaction.TransactionRepository
|
import cash.z.ecc.android.sdk.internal.transaction.TransactionRepository
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
import cash.z.ecc.android.sdk.internal.twigTask
|
import cash.z.ecc.android.sdk.internal.twigTask
|
||||||
import cash.z.ecc.android.sdk.jni.RustBackend
|
import cash.z.ecc.android.sdk.jni.RustBackend
|
||||||
import cash.z.ecc.android.sdk.jni.RustBackendWelding
|
import cash.z.ecc.android.sdk.jni.RustBackendWelding
|
||||||
import cash.z.ecc.android.sdk.type.WalletBalance
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||||
import cash.z.wallet.sdk.rpc.Service
|
import cash.z.wallet.sdk.rpc.Service
|
||||||
import io.grpc.StatusRuntimeException
|
import io.grpc.StatusRuntimeException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -75,11 +77,11 @@ import kotlin.math.roundToInt
|
|||||||
* of the current wallet--the height before which we do not need to scan for transactions.
|
* of the current wallet--the height before which we do not need to scan for transactions.
|
||||||
*/
|
*/
|
||||||
@OpenForTesting
|
@OpenForTesting
|
||||||
class CompactBlockProcessor(
|
class CompactBlockProcessor internal constructor(
|
||||||
val downloader: CompactBlockDownloader,
|
val downloader: CompactBlockDownloader,
|
||||||
private val repository: TransactionRepository,
|
private val repository: TransactionRepository,
|
||||||
private val rustBackend: RustBackendWelding,
|
private val rustBackend: RustBackendWelding,
|
||||||
minimumHeight: Int = rustBackend.network.saplingActivationHeight
|
minimumHeight: BlockHeight = rustBackend.network.saplingActivationHeight
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Callback for any non-trivial errors that occur while processing compact blocks.
|
* Callback for any non-trivial errors that occur while processing compact blocks.
|
||||||
@@ -93,7 +95,7 @@ class CompactBlockProcessor(
|
|||||||
* Callback for reorgs. This callback is invoked when validation fails with the height at which
|
* Callback for reorgs. This callback is invoked when validation fails with the height at which
|
||||||
* an error was found and the lower bound to which the data will rewind, at most.
|
* an error was found and the lower bound to which the data will rewind, at most.
|
||||||
*/
|
*/
|
||||||
var onChainErrorListener: ((errorHeight: Int, rewindHeight: Int) -> Any)? = null
|
var onChainErrorListener: ((errorHeight: BlockHeight, rewindHeight: BlockHeight) -> Any)? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback for setup errors that occur prior to processing compact blocks. Can be used to
|
* Callback for setup errors that occur prior to processing compact blocks. Can be used to
|
||||||
@@ -117,12 +119,18 @@ class CompactBlockProcessor(
|
|||||||
var onScanMetricCompleteListener: ((BatchMetrics, Boolean) -> Unit)? = null
|
var onScanMetricCompleteListener: ((BatchMetrics, Boolean) -> Unit)? = null
|
||||||
|
|
||||||
private val consecutiveChainErrors = AtomicInteger(0)
|
private val consecutiveChainErrors = AtomicInteger(0)
|
||||||
private val lowerBoundHeight: Int = max(rustBackend.network.saplingActivationHeight, minimumHeight - MAX_REORG_SIZE)
|
private val lowerBoundHeight: BlockHeight = BlockHeight(
|
||||||
|
max(
|
||||||
|
rustBackend.network.saplingActivationHeight.value,
|
||||||
|
minimumHeight.value - MAX_REORG_SIZE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
private val _state: ConflatedBroadcastChannel<State> = ConflatedBroadcastChannel(Initialized)
|
private val _state: ConflatedBroadcastChannel<State> = ConflatedBroadcastChannel(Initialized)
|
||||||
private val _progress = ConflatedBroadcastChannel(0)
|
private val _progress = ConflatedBroadcastChannel(0)
|
||||||
private val _processorInfo = ConflatedBroadcastChannel(ProcessorInfo())
|
private val _processorInfo =
|
||||||
private val _networkHeight = MutableStateFlow(-1)
|
ConflatedBroadcastChannel(ProcessorInfo(null, null, null, null, null))
|
||||||
|
private val _networkHeight = MutableStateFlow<BlockHeight?>(null)
|
||||||
private val processingMutex = Mutex()
|
private val processingMutex = Mutex()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -139,7 +147,10 @@ class CompactBlockProcessor(
|
|||||||
* sequentially, due to the way sqlite works so it is okay for this not to be threadsafe or
|
* sequentially, due to the way sqlite works so it is okay for this not to be threadsafe or
|
||||||
* coroutine safe because processing cannot be concurrent.
|
* coroutine safe because processing cannot be concurrent.
|
||||||
*/
|
*/
|
||||||
internal var currentInfo = ProcessorInfo()
|
// This accessed by the Dispatchers.IO thread, which means multiple threads are reading/writing
|
||||||
|
// concurrently.
|
||||||
|
@Volatile
|
||||||
|
internal var currentInfo = ProcessorInfo(null, null, null, null, null)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The zcash network that is being processed. Either Testnet or Mainnet.
|
* The zcash network that is being processed. Either Testnet or Mainnet.
|
||||||
@@ -193,25 +204,38 @@ class CompactBlockProcessor(
|
|||||||
processNewBlocks()
|
processNewBlocks()
|
||||||
}
|
}
|
||||||
// immediately process again after failures in order to download new blocks right away
|
// immediately process again after failures in order to download new blocks right away
|
||||||
if (result == ERROR_CODE_RECONNECT) {
|
when (result) {
|
||||||
val napTime = calculatePollInterval(true)
|
BlockProcessingResult.Reconnecting -> {
|
||||||
twig("Unable to process new blocks because we are disconnected! Attempting to reconnect in ${napTime}ms")
|
val napTime = calculatePollInterval(true)
|
||||||
delay(napTime)
|
twig("Unable to process new blocks because we are disconnected! Attempting to reconnect in ${napTime}ms")
|
||||||
} else if (result == ERROR_CODE_NONE || result == ERROR_CODE_FAILED_ENHANCE) {
|
delay(napTime)
|
||||||
val noWorkDone = currentInfo.lastDownloadRange.isEmpty() && currentInfo.lastScanRange.isEmpty()
|
}
|
||||||
val summary = if (noWorkDone) "Nothing to process: no new blocks to download or scan" else "Done processing blocks"
|
BlockProcessingResult.NoBlocksToProcess, BlockProcessingResult.FailedEnhance -> {
|
||||||
consecutiveChainErrors.set(0)
|
val noWorkDone =
|
||||||
val napTime = calculatePollInterval()
|
currentInfo.lastDownloadRange?.isEmpty() ?: true && currentInfo.lastScanRange?.isEmpty() ?: true
|
||||||
twig("$summary${if (result == ERROR_CODE_FAILED_ENHANCE) " (but there were enhancement errors! We ignore those, for now. Memos in this block range are probably missing! This will be improved in a future release.)" else ""}! Sleeping for ${napTime}ms (latest height: ${currentInfo.networkBlockHeight}).")
|
val summary = if (noWorkDone) {
|
||||||
delay(napTime)
|
"Nothing to process: no new blocks to download or scan"
|
||||||
} else {
|
} else {
|
||||||
if (consecutiveChainErrors.get() >= RETRIES) {
|
"Done processing blocks"
|
||||||
val errorMessage = "ERROR: unable to resolve reorg at height $result after ${consecutiveChainErrors.get()} correction attempts!"
|
}
|
||||||
fail(CompactBlockProcessorException.FailedReorgRepair(errorMessage))
|
consecutiveChainErrors.set(0)
|
||||||
} else {
|
val napTime = calculatePollInterval()
|
||||||
handleChainError(result)
|
twig("$summary${if (result == BlockProcessingResult.FailedEnhance) " (but there were enhancement errors! We ignore those, for now. Memos in this block range are probably missing! This will be improved in a future release.)" else ""}! Sleeping for ${napTime}ms (latest height: ${currentInfo.networkBlockHeight}).")
|
||||||
|
delay(napTime)
|
||||||
|
}
|
||||||
|
is BlockProcessingResult.Error -> {
|
||||||
|
if (consecutiveChainErrors.get() >= RETRIES) {
|
||||||
|
val errorMessage =
|
||||||
|
"ERROR: unable to resolve reorg at height $result after ${consecutiveChainErrors.get()} correction attempts!"
|
||||||
|
fail(CompactBlockProcessorException.FailedReorgRepair(errorMessage))
|
||||||
|
} else {
|
||||||
|
handleChainError(result.failedAtHeight)
|
||||||
|
}
|
||||||
|
consecutiveChainErrors.getAndIncrement()
|
||||||
|
}
|
||||||
|
is BlockProcessingResult.Success -> {
|
||||||
|
// Do nothing. We are done.
|
||||||
}
|
}
|
||||||
consecutiveChainErrors.getAndIncrement()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (isActive && !_state.isClosedForSend && _state.value !is Stopped)
|
} while (isActive && !_state.isClosedForSend && _state.value !is Stopped)
|
||||||
@@ -238,32 +262,37 @@ class CompactBlockProcessor(
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private suspend fun processNewBlocks(): BlockProcessingResult = withContext(IO) {
|
||||||
* Process new blocks returning false whenever an error was found.
|
|
||||||
*
|
|
||||||
* @return -1 when processing was successful and did not encounter errors during validation or scanning. Otherwise
|
|
||||||
* return the block height where an error was found.
|
|
||||||
*/
|
|
||||||
private suspend fun processNewBlocks(): Int = withContext(IO) {
|
|
||||||
twig("beginning to process new blocks (with lower bound: $lowerBoundHeight)...", -1)
|
twig("beginning to process new blocks (with lower bound: $lowerBoundHeight)...", -1)
|
||||||
|
|
||||||
if (!updateRanges()) {
|
if (!updateRanges()) {
|
||||||
twig("Disconnection detected! Attempting to reconnect!")
|
twig("Disconnection detected! Attempting to reconnect!")
|
||||||
setState(Disconnected)
|
setState(Disconnected)
|
||||||
downloader.lightWalletService.reconnect()
|
downloader.lightWalletService.reconnect()
|
||||||
ERROR_CODE_RECONNECT
|
BlockProcessingResult.Reconnecting
|
||||||
} else if (currentInfo.lastDownloadRange.isEmpty() && currentInfo.lastScanRange.isEmpty()) {
|
} else if (currentInfo.lastDownloadRange.isEmpty() && currentInfo.lastScanRange.isEmpty()) {
|
||||||
setState(Scanned(currentInfo.lastScanRange))
|
setState(Scanned(currentInfo.lastScanRange))
|
||||||
ERROR_CODE_NONE
|
BlockProcessingResult.NoBlocksToProcess
|
||||||
} else {
|
} else {
|
||||||
downloadNewBlocks(currentInfo.lastDownloadRange)
|
downloadNewBlocks(currentInfo.lastDownloadRange)
|
||||||
val error = validateAndScanNewBlocks(currentInfo.lastScanRange)
|
val error = validateAndScanNewBlocks(currentInfo.lastScanRange)
|
||||||
if (error != ERROR_CODE_NONE) error else {
|
if (error != BlockProcessingResult.Success) {
|
||||||
enhanceTransactionDetails(currentInfo.lastScanRange)
|
error
|
||||||
|
} else {
|
||||||
|
currentInfo.lastScanRange?.let { enhanceTransactionDetails(it) }
|
||||||
|
?: BlockProcessingResult.NoBlocksToProcess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class BlockProcessingResult {
|
||||||
|
object NoBlocksToProcess : BlockProcessingResult()
|
||||||
|
object Success : BlockProcessingResult()
|
||||||
|
object Reconnecting : BlockProcessingResult()
|
||||||
|
object FailedEnhance : BlockProcessingResult()
|
||||||
|
data class Error(val failedAtHeight: BlockHeight) : BlockProcessingResult()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the latest range info and then uses that initialInfo to update (and transmit)
|
* Gets the latest range info and then uses that initialInfo to update (and transmit)
|
||||||
* the scan/download ranges that require processing.
|
* the scan/download ranges that require processing.
|
||||||
@@ -278,19 +307,39 @@ class CompactBlockProcessor(
|
|||||||
ProcessorInfo(
|
ProcessorInfo(
|
||||||
networkBlockHeight = downloader.getLatestBlockHeight(),
|
networkBlockHeight = downloader.getLatestBlockHeight(),
|
||||||
lastScannedHeight = getLastScannedHeight(),
|
lastScannedHeight = getLastScannedHeight(),
|
||||||
lastDownloadedHeight = max(getLastDownloadedHeight(), lowerBoundHeight - 1)
|
lastDownloadedHeight = getLastDownloadedHeight()?.let {
|
||||||
|
BlockHeight.new(
|
||||||
|
network,
|
||||||
|
max(
|
||||||
|
it.value,
|
||||||
|
lowerBoundHeight.value - 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
lastDownloadRange = null,
|
||||||
|
lastScanRange = null
|
||||||
).let { initialInfo ->
|
).let { initialInfo ->
|
||||||
updateProgress(
|
updateProgress(
|
||||||
networkBlockHeight = initialInfo.networkBlockHeight,
|
networkBlockHeight = initialInfo.networkBlockHeight,
|
||||||
lastScannedHeight = initialInfo.lastScannedHeight,
|
lastScannedHeight = initialInfo.lastScannedHeight,
|
||||||
lastDownloadedHeight = initialInfo.lastDownloadedHeight,
|
lastDownloadedHeight = initialInfo.lastDownloadedHeight,
|
||||||
lastScanRange = (initialInfo.lastScannedHeight + 1)..initialInfo.networkBlockHeight,
|
lastScanRange = if (initialInfo.lastScannedHeight != null && initialInfo.networkBlockHeight != null) {
|
||||||
lastDownloadRange = (
|
initialInfo.lastScannedHeight + 1..initialInfo.networkBlockHeight
|
||||||
max(
|
} else {
|
||||||
initialInfo.lastDownloadedHeight,
|
null
|
||||||
initialInfo.lastScannedHeight
|
},
|
||||||
) + 1
|
lastDownloadRange = if (initialInfo.networkBlockHeight != null) {
|
||||||
|
BlockHeight.new(
|
||||||
|
network,
|
||||||
|
buildList {
|
||||||
|
add(network.saplingActivationHeight.value)
|
||||||
|
initialInfo.lastDownloadedHeight?.let { add(it.value + 1) }
|
||||||
|
initialInfo.lastScannedHeight?.let { add(it.value + 1) }
|
||||||
|
}.max()
|
||||||
)..initialInfo.networkBlockHeight
|
)..initialInfo.networkBlockHeight
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@@ -306,35 +355,34 @@ class CompactBlockProcessor(
|
|||||||
* prevHash value matches the preceding block in the chain.
|
* prevHash value matches the preceding block in the chain.
|
||||||
*
|
*
|
||||||
* @param lastScanRange the range to be validated and scanned.
|
* @param lastScanRange the range to be validated and scanned.
|
||||||
*
|
|
||||||
* @return error code or [ERROR_CODE_NONE] when there is no error.
|
|
||||||
*/
|
*/
|
||||||
private suspend fun validateAndScanNewBlocks(lastScanRange: IntRange): Int = withContext(IO) {
|
private suspend fun validateAndScanNewBlocks(lastScanRange: ClosedRange<BlockHeight>?): BlockProcessingResult =
|
||||||
setState(Validating)
|
withContext(IO) {
|
||||||
var error = validateNewBlocks(lastScanRange)
|
setState(Validating)
|
||||||
if (error == ERROR_CODE_NONE) {
|
val result = validateNewBlocks(lastScanRange)
|
||||||
// in theory, a scan should not fail after validation succeeds but maybe consider
|
if (result == BlockProcessingResult.Success) {
|
||||||
// changing the rust layer to return the failed block height whenever scan does fail
|
// in theory, a scan should not fail after validation succeeds but maybe consider
|
||||||
// rather than a boolean
|
// changing the rust layer to return the failed block height whenever scan does fail
|
||||||
setState(Scanning)
|
// rather than a boolean
|
||||||
val success = scanNewBlocks(lastScanRange)
|
setState(Scanning)
|
||||||
if (!success) throw CompactBlockProcessorException.FailedScan()
|
val success = scanNewBlocks(lastScanRange)
|
||||||
else {
|
if (!success) {
|
||||||
setState(Scanned(lastScanRange))
|
throw CompactBlockProcessorException.FailedScan()
|
||||||
|
} else {
|
||||||
|
setState(Scanned(lastScanRange))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ERROR_CODE_NONE
|
|
||||||
} else {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun enhanceTransactionDetails(lastScanRange: IntRange): Int {
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun enhanceTransactionDetails(lastScanRange: ClosedRange<BlockHeight>): BlockProcessingResult {
|
||||||
Twig.sprout("enhancing")
|
Twig.sprout("enhancing")
|
||||||
twig("Enhancing transaction details for blocks $lastScanRange")
|
twig("Enhancing transaction details for blocks $lastScanRange")
|
||||||
setState(Enhancing)
|
setState(Enhancing)
|
||||||
return try {
|
return try {
|
||||||
val newTxs = repository.findNewTransactions(lastScanRange)
|
val newTxs = repository.findNewTransactions(lastScanRange)
|
||||||
if (newTxs == null) {
|
if (newTxs.isEmpty()) {
|
||||||
twig("no new transactions found in $lastScanRange")
|
twig("no new transactions found in $lastScanRange")
|
||||||
} else {
|
} else {
|
||||||
twig("enhancing ${newTxs.size} transaction(s)!")
|
twig("enhancing ${newTxs.size} transaction(s)!")
|
||||||
@@ -346,15 +394,18 @@ class CompactBlockProcessor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
newTxs?.onEach { newTransaction ->
|
newTxs?.onEach { newTransaction ->
|
||||||
if (newTransaction == null) twig("somehow, new transaction was null!!!")
|
if (newTransaction == null) {
|
||||||
else enhance(newTransaction)
|
twig("somehow, new transaction was null!!!")
|
||||||
|
} else {
|
||||||
|
enhance(newTransaction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
twig("Done enhancing transaction details")
|
twig("Done enhancing transaction details")
|
||||||
ERROR_CODE_NONE
|
BlockProcessingResult.Success
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
twig("Failed to enhance due to $t")
|
twig("Failed to enhance due to $t")
|
||||||
t.printStackTrace()
|
t.printStackTrace()
|
||||||
ERROR_CODE_FAILED_ENHANCE
|
BlockProcessingResult.FailedEnhance
|
||||||
} finally {
|
} finally {
|
||||||
Twig.clip("enhancing")
|
Twig.clip("enhancing")
|
||||||
}
|
}
|
||||||
@@ -374,8 +425,11 @@ class CompactBlockProcessor(
|
|||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
twig("Warning: failure on transaction: error: $t\ttransaction: $transaction")
|
twig("Warning: failure on transaction: error: $t\ttransaction: $transaction")
|
||||||
onProcessorError(
|
onProcessorError(
|
||||||
if (downloaded) EnhanceTxDecryptError(transaction.minedHeight, t)
|
if (downloaded) {
|
||||||
else EnhanceTxDownloadError(transaction.minedHeight, t)
|
EnhanceTxDecryptError(transaction.minedBlockHeight, t)
|
||||||
|
} else {
|
||||||
|
EnhanceTxDownloadError(transaction.minedBlockHeight, t)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,12 +445,18 @@ class CompactBlockProcessor(
|
|||||||
else -> {
|
else -> {
|
||||||
// verify that the server is correct
|
// verify that the server is correct
|
||||||
downloader.getServerInfo().let { info ->
|
downloader.getServerInfo().let { info ->
|
||||||
//val clientBranch = "%x".format(rustBackend.getBranchIdForHeight(info.blockHeight.toInt()))
|
|
||||||
val clientBranch = "76b809bb"
|
val clientBranch = "76b809bb"
|
||||||
val network = rustBackend.network.networkName
|
val network = rustBackend.network.networkName
|
||||||
when {
|
when {
|
||||||
!info.matchingNetwork(network) -> MismatchedNetwork(clientNetwork = network, serverNetwork = info.chainName)
|
!info.matchingNetwork(network) -> MismatchedNetwork(
|
||||||
!info.matchingConsensusBranchId(clientBranch) -> MismatchedBranch(clientBranch = clientBranch, serverBranch = info.consensusBranchId, networkName = network)
|
clientNetwork = network,
|
||||||
|
serverNetwork = info.chainName
|
||||||
|
)
|
||||||
|
!info.matchingConsensusBranchId(clientBranch) -> MismatchedBranch(
|
||||||
|
clientBranch = clientBranch,
|
||||||
|
serverBranch = info.consensusBranchId,
|
||||||
|
networkName = network
|
||||||
|
)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -426,34 +486,36 @@ class CompactBlockProcessor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
var failedUtxoFetches = 0
|
var failedUtxoFetches = 0
|
||||||
internal suspend fun refreshUtxos(tAddress: String, startHeight: Int): Int? = withContext(IO) {
|
internal suspend fun refreshUtxos(tAddress: String, startHeight: BlockHeight): Int? =
|
||||||
var count: Int? = null
|
withContext(IO) {
|
||||||
// todo: cleanup the way that we prevent this from running excessively
|
var count: Int? = null
|
||||||
// For now, try for about 3 blocks per app launch. If the service fails it is
|
// todo: cleanup the way that we prevent this from running excessively
|
||||||
// probably disabled on ligthtwalletd, so then stop trying until the next app launch.
|
// For now, try for about 3 blocks per app launch. If the service fails it is
|
||||||
if (failedUtxoFetches < 9) { // there are 3 attempts per block
|
// probably disabled on ligthtwalletd, so then stop trying until the next app launch.
|
||||||
try {
|
if (failedUtxoFetches < 9) { // there are 3 attempts per block
|
||||||
retryUpTo(3) {
|
try {
|
||||||
val result = downloader.lightWalletService.fetchUtxos(tAddress, startHeight)
|
retryUpTo(3) {
|
||||||
count = processUtxoResult(result, tAddress, startHeight)
|
val result = downloader.lightWalletService.fetchUtxos(tAddress, startHeight)
|
||||||
|
count = processUtxoResult(result, tAddress, startHeight)
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
failedUtxoFetches++
|
||||||
|
twig("Warning: Fetching UTXOs is repeatedly failing! We will only try about ${(9 - failedUtxoFetches + 2) / 3} more times then give up for this session.")
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} else {
|
||||||
failedUtxoFetches++
|
twig("Warning: gave up on fetching UTXOs for this session. It seems to unavailable on lightwalletd.")
|
||||||
twig("Warning: Fetching UTXOs is repeatedly failing! We will only try about ${(9 - failedUtxoFetches + 2) / 3} more times then give up for this session.")
|
|
||||||
}
|
}
|
||||||
} else {
|
count
|
||||||
twig("Warning: gave up on fetching UTXOs for this session. It seems to unavailable on lightwalletd.")
|
|
||||||
}
|
}
|
||||||
count
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
internal suspend fun processUtxoResult(
|
||||||
internal suspend fun processUtxoResult(result: List<Service.GetAddressUtxosReply>, tAddress: String, startHeight: Int): Int = withContext(IO) {
|
result: List<Service.GetAddressUtxosReply>,
|
||||||
|
tAddress: String,
|
||||||
|
startHeight: BlockHeight
|
||||||
|
): Int = withContext(IO) {
|
||||||
var skipped = 0
|
var skipped = 0
|
||||||
val aboveHeight = startHeight - 1
|
val aboveHeight = startHeight
|
||||||
twig("Clearing utxos above height $aboveHeight", -1)
|
twig("Clearing utxos above height $aboveHeight", -1)
|
||||||
rustBackend.clearUtxos(tAddress, aboveHeight)
|
rustBackend.clearUtxos(tAddress, aboveHeight)
|
||||||
twig("Checking for UTXOs above height $aboveHeight")
|
twig("Checking for UTXOs above height $aboveHeight")
|
||||||
@@ -466,7 +528,7 @@ class CompactBlockProcessor(
|
|||||||
utxo.index,
|
utxo.index,
|
||||||
utxo.script.toByteArray(),
|
utxo.script.toByteArray(),
|
||||||
utxo.valueZat,
|
utxo.valueZat,
|
||||||
utxo.height.toInt()
|
BlockHeight(utxo.height)
|
||||||
)
|
)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
// TODO: more accurately track the utxos that were skipped (in theory, this could fail for other reasons)
|
// TODO: more accurately track the utxos that were skipped (in theory, this could fail for other reasons)
|
||||||
@@ -477,7 +539,6 @@ class CompactBlockProcessor(
|
|||||||
// return the number of UTXOs that were downloaded
|
// return the number of UTXOs that were downloaded
|
||||||
result.size - skipped
|
result.size - skipped
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request all blocks in the given range and persist them locally for processing, later.
|
* Request all blocks in the given range and persist them locally for processing, later.
|
||||||
@@ -485,75 +546,81 @@ class CompactBlockProcessor(
|
|||||||
* @param range the range of blocks to download.
|
* @param range the range of blocks to download.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting // allow mocks to verify how this is called, rather than the downloader, which is more complex
|
@VisibleForTesting // allow mocks to verify how this is called, rather than the downloader, which is more complex
|
||||||
internal suspend fun downloadNewBlocks(range: IntRange) = withContext<Unit>(IO) {
|
internal suspend fun downloadNewBlocks(range: ClosedRange<BlockHeight>?) =
|
||||||
if (range.isEmpty()) {
|
withContext<Unit>(IO) {
|
||||||
twig("no blocks to download")
|
if (null == range || range.isEmpty()) {
|
||||||
} else {
|
twig("no blocks to download")
|
||||||
_state.send(Downloading)
|
} else {
|
||||||
Twig.sprout("downloading")
|
_state.send(Downloading)
|
||||||
twig("downloading blocks in range $range", -1)
|
Twig.sprout("downloading")
|
||||||
|
twig("downloading blocks in range $range", -1)
|
||||||
|
|
||||||
var downloadedBlockHeight = range.first
|
var downloadedBlockHeight = range.start
|
||||||
val missingBlockCount = range.last - range.first + 1
|
val missingBlockCount = range.endInclusive.value - range.start.value + 1
|
||||||
val batches = (
|
val batches = (
|
||||||
missingBlockCount / DOWNLOAD_BATCH_SIZE +
|
missingBlockCount / DOWNLOAD_BATCH_SIZE +
|
||||||
(if (missingBlockCount.rem(DOWNLOAD_BATCH_SIZE) == 0) 0 else 1)
|
(if (missingBlockCount.rem(DOWNLOAD_BATCH_SIZE) == 0L) 0 else 1)
|
||||||
)
|
)
|
||||||
var progress: Int
|
var progress: Int
|
||||||
twig("found $missingBlockCount missing blocks, downloading in $batches batches of $DOWNLOAD_BATCH_SIZE...")
|
twig("found $missingBlockCount missing blocks, downloading in $batches batches of $DOWNLOAD_BATCH_SIZE...")
|
||||||
for (i in 1..batches) {
|
for (i in 1..batches) {
|
||||||
retryUpTo(RETRIES, { CompactBlockProcessorException.FailedDownload(it) }) {
|
retryUpTo(RETRIES, { CompactBlockProcessorException.FailedDownload(it) }) {
|
||||||
val end = min((range.first + (i * DOWNLOAD_BATCH_SIZE)) - 1, range.last) // subtract 1 on the first value because the range is inclusive
|
val end = BlockHeight.new(
|
||||||
var count = 0
|
network,
|
||||||
twig("downloaded $downloadedBlockHeight..$end (batch $i of $batches) [${downloadedBlockHeight..end}]") {
|
min(
|
||||||
count = downloader.downloadBlockRange(downloadedBlockHeight..end)
|
(range.start.value + (i * DOWNLOAD_BATCH_SIZE)) - 1,
|
||||||
|
range.endInclusive.value
|
||||||
|
)
|
||||||
|
) // subtract 1 on the first value because the range is inclusive
|
||||||
|
var count = 0
|
||||||
|
twig("downloaded $downloadedBlockHeight..$end (batch $i of $batches) [${downloadedBlockHeight..end}]") {
|
||||||
|
count = downloader.downloadBlockRange(downloadedBlockHeight..end)
|
||||||
|
}
|
||||||
|
twig("downloaded $count blocks!")
|
||||||
|
progress = (i / batches.toFloat() * 100).roundToInt()
|
||||||
|
_progress.send(progress)
|
||||||
|
val lastDownloadedHeight = downloader.getLastDownloadedHeight()
|
||||||
|
updateProgress(lastDownloadedHeight = lastDownloadedHeight)
|
||||||
|
downloadedBlockHeight = end + 1
|
||||||
}
|
}
|
||||||
twig("downloaded $count blocks!")
|
|
||||||
progress = (i / batches.toFloat() * 100).roundToInt()
|
|
||||||
_progress.send(progress)
|
|
||||||
val lastDownloadedHeight = downloader.getLastDownloadedHeight().takeUnless { it < network.saplingActivationHeight } ?: -1
|
|
||||||
updateProgress(lastDownloadedHeight = lastDownloadedHeight)
|
|
||||||
downloadedBlockHeight = end
|
|
||||||
}
|
}
|
||||||
|
Twig.clip("downloading")
|
||||||
}
|
}
|
||||||
Twig.clip("downloading")
|
_progress.send(100)
|
||||||
}
|
}
|
||||||
_progress.send(100)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate all blocks in the given range, ensuring that the blocks are in ascending order, with
|
* Validate all blocks in the given range, ensuring that the blocks are in ascending order, with
|
||||||
* no gaps and are also chain-sequential. This means every block's prevHash value matches the
|
* no gaps and are also chain-sequential. This means every block's prevHash value matches the
|
||||||
* preceding block in the chain.
|
* preceding block in the chain. Validation starts at the back of the chain and works toward the tip.
|
||||||
*
|
*
|
||||||
* @param range the range of blocks to validate.
|
* @param range the range of blocks to validate.
|
||||||
*
|
|
||||||
* @return [ERROR_CODE_NONE] when there is no problem. Otherwise, return the lowest height where an error was
|
|
||||||
* found. In other words, validation starts at the back of the chain and works toward the tip.
|
|
||||||
*/
|
*/
|
||||||
private suspend fun validateNewBlocks(range: IntRange?): Int {
|
private suspend fun validateNewBlocks(range: ClosedRange<BlockHeight>?): BlockProcessingResult {
|
||||||
if (range?.isEmpty() != false) {
|
if (null == range || range.isEmpty()) {
|
||||||
twig("no blocks to validate: $range")
|
twig("no blocks to validate: $range")
|
||||||
return ERROR_CODE_NONE
|
return BlockProcessingResult.NoBlocksToProcess
|
||||||
}
|
}
|
||||||
Twig.sprout("validating")
|
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).pathCacheDb}")
|
||||||
val result = rustBackend.validateCombinedChain()
|
val result = rustBackend.validateCombinedChain()
|
||||||
Twig.clip("validating")
|
Twig.clip("validating")
|
||||||
return result
|
|
||||||
|
return if (null == result) {
|
||||||
|
BlockProcessingResult.Success
|
||||||
|
} else {
|
||||||
|
BlockProcessingResult.Error(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan all blocks in the given range, decrypting and persisting anything that matches our
|
* Scan all blocks in the given range, decrypting and persisting anything that matches our
|
||||||
* wallet.
|
* wallet. Scanning starts at the back of the chain and works toward the tip.
|
||||||
*
|
*
|
||||||
* @param range the range of blocks to scan.
|
* @param range the range of blocks to scan.
|
||||||
*
|
|
||||||
* @return [ERROR_CODE_NONE] when there is no problem. Otherwise, return the lowest height where an error was
|
|
||||||
* found. In other words, scanning starts at the back of the chain and works toward the tip.
|
|
||||||
*/
|
*/
|
||||||
private suspend fun scanNewBlocks(range: IntRange?): Boolean = withContext(IO) {
|
private suspend fun scanNewBlocks(range: ClosedRange<BlockHeight>?): Boolean = withContext(IO) {
|
||||||
if (range?.isEmpty() != false) {
|
if (null == range || range.isEmpty()) {
|
||||||
twig("no blocks to scan for range $range")
|
twig("no blocks to scan for range $range")
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -570,16 +637,18 @@ class CompactBlockProcessor(
|
|||||||
metrics.beginBatch()
|
metrics.beginBatch()
|
||||||
result = rustBackend.scanBlocks(SCAN_BATCH_SIZE)
|
result = rustBackend.scanBlocks(SCAN_BATCH_SIZE)
|
||||||
metrics.endBatch()
|
metrics.endBatch()
|
||||||
val lastScannedHeight = range.start + metrics.cumulativeItems - 1
|
val lastScannedHeight =
|
||||||
val percentValue = (lastScannedHeight - range.first) / (range.last - range.first + 1).toFloat() * 100.0f
|
BlockHeight.new(network, range.start.value + metrics.cumulativeItems - 1)
|
||||||
|
val percentValue =
|
||||||
|
(lastScannedHeight.value - range.start.value) / (range.endInclusive.value - range.start.value + 1).toFloat() * 100.0f
|
||||||
val percent = "%.0f".format(percentValue.coerceAtMost(100f).coerceAtLeast(0f))
|
val percent = "%.0f".format(percentValue.coerceAtMost(100f).coerceAtLeast(0f))
|
||||||
twig("batch scanned ($percent%): $lastScannedHeight/${range.last} | ${metrics.batchTime}ms, ${metrics.batchItems}blks, ${metrics.batchIps.format()}bps")
|
twig("batch scanned ($percent%): $lastScannedHeight/${range.endInclusive} | ${metrics.batchTime}ms, ${metrics.batchItems}blks, ${metrics.batchIps.format()}bps")
|
||||||
if (currentInfo.lastScannedHeight != lastScannedHeight) {
|
if (currentInfo.lastScannedHeight != lastScannedHeight) {
|
||||||
scannedNewBlocks = true
|
scannedNewBlocks = true
|
||||||
updateProgress(lastScannedHeight = lastScannedHeight)
|
updateProgress(lastScannedHeight = lastScannedHeight)
|
||||||
}
|
}
|
||||||
// if we made progress toward our scan, then keep trying
|
// if we made progress toward our scan, then keep trying
|
||||||
} while (result && scannedNewBlocks && lastScannedHeight < range.last)
|
} while (result && scannedNewBlocks && lastScannedHeight < range.endInclusive)
|
||||||
twig("batch scan complete! Total time: ${metrics.cumulativeTime} Total blocks measured: ${metrics.cumulativeItems} Cumulative bps: ${metrics.cumulativeIps.format()}")
|
twig("batch scan complete! Total time: ${metrics.cumulativeTime} Total blocks measured: ${metrics.cumulativeItems} Cumulative bps: ${metrics.cumulativeIps.format()}")
|
||||||
}
|
}
|
||||||
Twig.clip("scanning")
|
Twig.clip("scanning")
|
||||||
@@ -605,12 +674,12 @@ class CompactBlockProcessor(
|
|||||||
* blocks that we don't yet have.
|
* blocks that we don't yet have.
|
||||||
*/
|
*/
|
||||||
private suspend fun updateProgress(
|
private suspend fun updateProgress(
|
||||||
networkBlockHeight: Int = currentInfo.networkBlockHeight,
|
networkBlockHeight: BlockHeight? = currentInfo.networkBlockHeight,
|
||||||
lastScannedHeight: Int = currentInfo.lastScannedHeight,
|
lastScannedHeight: BlockHeight? = currentInfo.lastScannedHeight,
|
||||||
lastDownloadedHeight: Int = currentInfo.lastDownloadedHeight,
|
lastDownloadedHeight: BlockHeight? = currentInfo.lastDownloadedHeight,
|
||||||
lastScanRange: IntRange = currentInfo.lastScanRange,
|
lastScanRange: ClosedRange<BlockHeight>? = currentInfo.lastScanRange,
|
||||||
lastDownloadRange: IntRange = currentInfo.lastDownloadRange
|
lastDownloadRange: ClosedRange<BlockHeight>? = currentInfo.lastDownloadRange
|
||||||
): Unit = withContext(IO) {
|
) {
|
||||||
currentInfo = currentInfo.copy(
|
currentInfo = currentInfo.copy(
|
||||||
networkBlockHeight = networkBlockHeight,
|
networkBlockHeight = networkBlockHeight,
|
||||||
lastScannedHeight = lastScannedHeight,
|
lastScannedHeight = lastScannedHeight,
|
||||||
@@ -618,11 +687,14 @@ class CompactBlockProcessor(
|
|||||||
lastScanRange = lastScanRange,
|
lastScanRange = lastScanRange,
|
||||||
lastDownloadRange = lastDownloadRange
|
lastDownloadRange = lastDownloadRange
|
||||||
)
|
)
|
||||||
_networkHeight.value = networkBlockHeight
|
|
||||||
_processorInfo.send(currentInfo)
|
withContext(IO) {
|
||||||
|
_networkHeight.value = networkBlockHeight
|
||||||
|
_processorInfo.send(currentInfo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleChainError(errorHeight: Int) {
|
private suspend fun handleChainError(errorHeight: BlockHeight) {
|
||||||
// TODO consider an error object containing hash information
|
// TODO consider an error object containing hash information
|
||||||
printValidationErrorInfo(errorHeight)
|
printValidationErrorInfo(errorHeight)
|
||||||
determineLowerBound(errorHeight).let { lowerBound ->
|
determineLowerBound(errorHeight).let { lowerBound ->
|
||||||
@@ -632,14 +704,17 @@ class CompactBlockProcessor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getNearestRewindHeight(height: Int): Int {
|
suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight {
|
||||||
// TODO: add a concept of original checkpoint height to the processor. For now, derive it
|
// TODO: add a concept of original checkpoint height to the processor. For now, derive it
|
||||||
val originalCheckpoint = lowerBoundHeight + MAX_REORG_SIZE + 2 // add one because we already have the checkpoint. Add one again because we delete ABOVE the block
|
val originalCheckpoint =
|
||||||
|
lowerBoundHeight + MAX_REORG_SIZE + 2 // add one because we already have the checkpoint. Add one again because we delete ABOVE the block
|
||||||
return if (height < originalCheckpoint) {
|
return if (height < originalCheckpoint) {
|
||||||
originalCheckpoint
|
originalCheckpoint
|
||||||
} else {
|
} else {
|
||||||
// tricky: subtract one because we delete ABOVE this block
|
// tricky: subtract one because we delete ABOVE this block
|
||||||
rustBackend.getNearestRewindHeight(height) - 1
|
// This could create an invalid height if if height was saplingActivationHeight
|
||||||
|
val rewindHeight = BlockHeight(height.value - 1)
|
||||||
|
rustBackend.getNearestRewindHeight(rewindHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -649,7 +724,10 @@ class CompactBlockProcessor(
|
|||||||
suspend fun quickRewind() {
|
suspend fun quickRewind() {
|
||||||
val height = max(currentInfo.lastScannedHeight, repository.lastScannedHeight())
|
val height = max(currentInfo.lastScannedHeight, repository.lastScannedHeight())
|
||||||
val blocksPerDay = 60 * 60 * 24 * 1000 / ZcashSdk.BLOCK_INTERVAL_MILLIS.toInt()
|
val blocksPerDay = 60 * 60 * 24 * 1000 / ZcashSdk.BLOCK_INTERVAL_MILLIS.toInt()
|
||||||
val twoWeeksBack = (height - blocksPerDay * 14).coerceAtLeast(lowerBoundHeight)
|
val twoWeeksBack = BlockHeight.new(
|
||||||
|
network,
|
||||||
|
(height.value - blocksPerDay * 14).coerceAtLeast(lowerBoundHeight.value)
|
||||||
|
)
|
||||||
rewindToNearestHeight(twoWeeksBack, false)
|
rewindToNearestHeight(twoWeeksBack, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -657,45 +735,73 @@ class CompactBlockProcessor(
|
|||||||
* @param alsoClearBlockCache when true, also clear the block cache which forces a redownload of
|
* @param alsoClearBlockCache when true, also clear the block cache which forces a redownload of
|
||||||
* blocks. Otherwise, the cached blocks will be used in the rescan, which in most cases, is fine.
|
* blocks. Otherwise, the cached blocks will be used in the rescan, which in most cases, is fine.
|
||||||
*/
|
*/
|
||||||
suspend fun rewindToNearestHeight(height: Int, alsoClearBlockCache: Boolean = false) = withContext(IO) {
|
suspend fun rewindToNearestHeight(
|
||||||
processingMutex.withLockLogged("rewindToHeight") {
|
height: BlockHeight,
|
||||||
val lastScannedHeight = currentInfo.lastScannedHeight
|
alsoClearBlockCache: Boolean = false
|
||||||
val lastLocalBlock = repository.lastScannedHeight()
|
) =
|
||||||
val targetHeight = getNearestRewindHeight(height)
|
withContext(IO) {
|
||||||
twig("Rewinding from $lastScannedHeight to requested height: $height using target height: $targetHeight with last local block: $lastLocalBlock")
|
processingMutex.withLockLogged("rewindToHeight") {
|
||||||
if (targetHeight < lastScannedHeight || (lastScannedHeight == -1 && (targetHeight < lastLocalBlock))) {
|
val lastScannedHeight = currentInfo.lastScannedHeight
|
||||||
rustBackend.rewindToHeight(targetHeight)
|
val lastLocalBlock = repository.lastScannedHeight()
|
||||||
} else {
|
val targetHeight = getNearestRewindHeight(height)
|
||||||
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")
|
twig("Rewinding from $lastScannedHeight to requested height: $height using target height: $targetHeight with last local block: $lastLocalBlock")
|
||||||
}
|
if ((null == lastScannedHeight && targetHeight < lastLocalBlock) || (null != lastScannedHeight && targetHeight < lastScannedHeight)) {
|
||||||
|
rustBackend.rewindToHeight(targetHeight)
|
||||||
|
} else {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
if (alsoClearBlockCache) {
|
val currentNetworkBlockHeight = currentInfo.networkBlockHeight
|
||||||
twig("Also clearing block cache back to $targetHeight. These rewound blocks will download in the next scheduled scan")
|
|
||||||
downloader.rewindToHeight(targetHeight)
|
if (alsoClearBlockCache) {
|
||||||
// communicate that the wallet is no longer synced because it might remain this way for 20+ seconds because we only download on 20s time boundaries so we can't trigger any immediate action
|
twig("Also clearing block cache back to $targetHeight. These rewound blocks will download in the next scheduled scan")
|
||||||
setState(Downloading)
|
downloader.rewindToHeight(targetHeight)
|
||||||
updateProgress(
|
// communicate that the wallet is no longer synced because it might remain this way for 20+ seconds because we only download on 20s time boundaries so we can't trigger any immediate action
|
||||||
lastScannedHeight = targetHeight,
|
setState(Downloading)
|
||||||
lastDownloadedHeight = targetHeight,
|
if (null == currentNetworkBlockHeight) {
|
||||||
lastScanRange = (targetHeight + 1)..currentInfo.networkBlockHeight,
|
updateProgress(
|
||||||
lastDownloadRange = (targetHeight + 1)..currentInfo.networkBlockHeight
|
lastScannedHeight = targetHeight,
|
||||||
)
|
lastDownloadedHeight = targetHeight,
|
||||||
_progress.send(0)
|
lastScanRange = null,
|
||||||
} else {
|
lastDownloadRange = null
|
||||||
updateProgress(
|
)
|
||||||
lastScannedHeight = targetHeight,
|
} else {
|
||||||
lastScanRange = (targetHeight + 1)..currentInfo.networkBlockHeight
|
updateProgress(
|
||||||
)
|
lastScannedHeight = targetHeight,
|
||||||
_progress.send(0)
|
lastDownloadedHeight = targetHeight,
|
||||||
val range = (targetHeight + 1)..lastScannedHeight
|
lastScanRange = (targetHeight + 1)..currentNetworkBlockHeight,
|
||||||
twig("We kept the cache blocks in place so we don't need to wait for the next scheduled download to rescan. Instead we will rescan and validate blocks ${range.first}..${range.last}")
|
lastDownloadRange = (targetHeight + 1)..currentNetworkBlockHeight
|
||||||
if (validateAndScanNewBlocks(range) == ERROR_CODE_NONE) enhanceTransactionDetails(range)
|
)
|
||||||
|
}
|
||||||
|
_progress.send(0)
|
||||||
|
} else {
|
||||||
|
if (null == currentNetworkBlockHeight) {
|
||||||
|
updateProgress(
|
||||||
|
lastScannedHeight = targetHeight,
|
||||||
|
lastScanRange = null
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
updateProgress(
|
||||||
|
lastScannedHeight = targetHeight,
|
||||||
|
lastScanRange = (targetHeight + 1)..currentNetworkBlockHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_progress.send(0)
|
||||||
|
|
||||||
|
if (null != lastScannedHeight) {
|
||||||
|
val range = (targetHeight + 1)..lastScannedHeight
|
||||||
|
twig("We kept the cache blocks in place so we don't need to wait for the next scheduled download to rescan. Instead we will rescan and validate blocks ${range.start}..${range.endInclusive}")
|
||||||
|
if (validateAndScanNewBlocks(range) == BlockProcessingResult.Success) {
|
||||||
|
enhanceTransactionDetails(range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** insightful function for debugging these critical errors */
|
/** insightful function for debugging these critical errors */
|
||||||
private suspend fun printValidationErrorInfo(errorHeight: Int, count: Int = 11) {
|
private suspend fun printValidationErrorInfo(errorHeight: BlockHeight, count: Int = 11) {
|
||||||
// Note: blocks are public information so it's okay to print them but, still, let's not unless we're debugging something
|
// Note: blocks are public information so it's okay to print them but, still, let's not unless we're debugging something
|
||||||
if (!BuildConfig.DEBUG) return
|
if (!BuildConfig.DEBUG) return
|
||||||
|
|
||||||
@@ -704,19 +810,25 @@ class CompactBlockProcessor(
|
|||||||
errorInfo = fetchValidationErrorInfo(errorHeight + 1)
|
errorInfo = fetchValidationErrorInfo(errorHeight + 1)
|
||||||
twig("The next block block: ${errorInfo.errorHeight} which had hash ${errorInfo.actualPrevHash} but the expected hash was ${errorInfo.expectedPrevHash}")
|
twig("The next block block: ${errorInfo.errorHeight} which had hash ${errorInfo.actualPrevHash} but the expected hash was ${errorInfo.expectedPrevHash}")
|
||||||
|
|
||||||
twig("=================== BLOCKS [$errorHeight..${errorHeight + count - 1}]: START ========")
|
twig("=================== BLOCKS [$errorHeight..${errorHeight.value + count - 1}]: START ========")
|
||||||
repeat(count) { i ->
|
repeat(count) { i ->
|
||||||
val height = errorHeight + i
|
val height = errorHeight + i
|
||||||
val block = downloader.compactBlockStore.findCompactBlock(height)
|
val block = downloader.compactBlockStore.findCompactBlock(height)
|
||||||
// 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.
|
// 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.
|
||||||
val hash = block?.hash?.toByteArray() ?: (repository as PagedTransactionRepository).findBlockHash(height)
|
val hash = block?.hash?.toByteArray()
|
||||||
twig("block: $height\thash=${hash?.toHexReversed()} \tprevHash=${block?.prevHash?.toByteArray()?.toHexReversed()}")
|
?: (repository as PagedTransactionRepository).findBlockHash(height)
|
||||||
|
twig(
|
||||||
|
"block: $height\thash=${hash?.toHexReversed()} \tprevHash=${
|
||||||
|
block?.prevHash?.toByteArray()?.toHexReversed()
|
||||||
|
}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
twig("=================== BLOCKS [$errorHeight..${errorHeight + count - 1}]: END ========")
|
twig("=================== BLOCKS [$errorHeight..${errorHeight.value + count - 1}]: END ========")
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchValidationErrorInfo(errorHeight: Int): ValidationErrorInfo {
|
private suspend fun fetchValidationErrorInfo(errorHeight: BlockHeight): ValidationErrorInfo {
|
||||||
val hash = (repository as PagedTransactionRepository).findBlockHash(errorHeight + 1)?.toHexReversed()
|
val hash = (repository as PagedTransactionRepository).findBlockHash(errorHeight + 1)
|
||||||
|
?.toHexReversed()
|
||||||
val prevHash = repository.findBlockHash(errorHeight)?.toHexReversed()
|
val prevHash = repository.findBlockHash(errorHeight)?.toHexReversed()
|
||||||
|
|
||||||
val compactBlock = downloader.compactBlockStore.findCompactBlock(errorHeight + 1)
|
val compactBlock = downloader.compactBlockStore.findCompactBlock(errorHeight + 1)
|
||||||
@@ -734,9 +846,9 @@ class CompactBlockProcessor(
|
|||||||
return onProcessorErrorListener?.invoke(throwable) ?: true
|
return onProcessorErrorListener?.invoke(throwable) ?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun determineLowerBound(errorHeight: Int): Int {
|
private fun determineLowerBound(errorHeight: BlockHeight): BlockHeight {
|
||||||
val offset = Math.min(MAX_REORG_SIZE, REWIND_DISTANCE * (consecutiveChainErrors.get() + 1))
|
val offset = min(MAX_REORG_SIZE, REWIND_DISTANCE * (consecutiveChainErrors.get() + 1))
|
||||||
return Math.max(errorHeight - offset, lowerBoundHeight).also {
|
return BlockHeight(max(errorHeight.value - offset, lowerBoundHeight.value)).also {
|
||||||
twig("offset = min($MAX_REORG_SIZE, $REWIND_DISTANCE * (${consecutiveChainErrors.get() + 1})) = $offset")
|
twig("offset = min($MAX_REORG_SIZE, $REWIND_DISTANCE * (${consecutiveChainErrors.get() + 1})) = $offset")
|
||||||
twig("lowerBound = max($errorHeight - $offset, $lowerBoundHeight) = $it")
|
twig("lowerBound = max($errorHeight - $offset, $lowerBoundHeight) = $it")
|
||||||
}
|
}
|
||||||
@@ -759,19 +871,28 @@ class CompactBlockProcessor(
|
|||||||
return deltaToNextInteral
|
return deltaToNextInteral
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun calculateBirthdayHeight(): Int {
|
suspend fun calculateBirthdayHeight(): BlockHeight {
|
||||||
var oldestTransactionHeight = 0
|
var oldestTransactionHeight: BlockHeight? = null
|
||||||
try {
|
try {
|
||||||
oldestTransactionHeight = repository.receivedTransactions.first().lastOrNull()?.minedHeight ?: lowerBoundHeight
|
val tempOldestTransactionHeight = repository.receivedTransactions
|
||||||
|
.first()
|
||||||
|
.lastOrNull()
|
||||||
|
?.minedBlockHeight
|
||||||
|
?: lowerBoundHeight
|
||||||
// to be safe adjust for reorgs (and generally a little cushion is good for privacy)
|
// to be safe adjust for reorgs (and generally a little cushion is good for privacy)
|
||||||
// so we round down to the nearest 100 and then subtract 100 to ensure that the result is always at least 100 blocks away
|
// so we round down to the nearest 100 and then subtract 100 to ensure that the result is always at least 100 blocks away
|
||||||
oldestTransactionHeight = ZcashSdk.MAX_REORG_SIZE.let { boundary ->
|
oldestTransactionHeight = BlockHeight.new(
|
||||||
oldestTransactionHeight.let { it - it.rem(boundary) - boundary }
|
network,
|
||||||
}
|
tempOldestTransactionHeight.value - tempOldestTransactionHeight.value.rem(ZcashSdk.MAX_REORG_SIZE) - ZcashSdk.MAX_REORG_SIZE.toLong()
|
||||||
|
)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
twig("failed to calculate birthday due to: $t")
|
twig("failed to calculate birthday due to: $t")
|
||||||
}
|
}
|
||||||
return maxOf(lowerBoundHeight, oldestTransactionHeight, rustBackend.network.saplingActivationHeight)
|
return buildList<BlockHeight> {
|
||||||
|
add(lowerBoundHeight)
|
||||||
|
add(rustBackend.network.saplingActivationHeight)
|
||||||
|
oldestTransactionHeight?.let { add(it) }
|
||||||
|
}.maxOf { it }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -824,7 +945,8 @@ class CompactBlockProcessor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getUtxoCacheBalance(address: String): WalletBalance = rustBackend.getDownloadedUtxoBalance(address)
|
suspend fun getUtxoCacheBalance(address: String): WalletBalance =
|
||||||
|
rustBackend.getDownloadedUtxoBalance(address)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transmits the given state for this processor.
|
* Transmits the given state for this processor.
|
||||||
@@ -869,7 +991,7 @@ class CompactBlockProcessor(
|
|||||||
/**
|
/**
|
||||||
* [State] for when we are done decrypting blocks, for now.
|
* [State] for when we are done decrypting blocks, for now.
|
||||||
*/
|
*/
|
||||||
class Scanned(val scannedRange: IntRange) : Connected, Syncing, State()
|
class Scanned(val scannedRange: ClosedRange<BlockHeight>?) : Connected, Syncing, State()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [State] for when transaction details are being retrieved. This typically means the wallet
|
* [State] for when transaction details are being retrieved. This typically means the wallet
|
||||||
@@ -912,11 +1034,11 @@ class CompactBlockProcessor(
|
|||||||
* @param lastScanRange inclusive range to scan.
|
* @param lastScanRange inclusive range to scan.
|
||||||
*/
|
*/
|
||||||
data class ProcessorInfo(
|
data class ProcessorInfo(
|
||||||
val networkBlockHeight: Int = -1,
|
val networkBlockHeight: BlockHeight?,
|
||||||
val lastScannedHeight: Int = -1,
|
val lastScannedHeight: BlockHeight?,
|
||||||
val lastDownloadedHeight: Int = -1,
|
val lastDownloadedHeight: BlockHeight?,
|
||||||
val lastDownloadRange: IntRange = 0..-1, // empty range
|
val lastDownloadRange: ClosedRange<BlockHeight>?,
|
||||||
val lastScanRange: IntRange = 0..-1 // empty range
|
val lastScanRange: ClosedRange<BlockHeight>?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -924,19 +1046,24 @@ class CompactBlockProcessor(
|
|||||||
*
|
*
|
||||||
* @return false when all values match their defaults.
|
* @return false when all values match their defaults.
|
||||||
*/
|
*/
|
||||||
val hasData get() = networkBlockHeight != -1 ||
|
val hasData
|
||||||
lastScannedHeight != -1 ||
|
get() = networkBlockHeight != null ||
|
||||||
lastDownloadedHeight != -1 ||
|
lastScannedHeight != null ||
|
||||||
lastDownloadRange != 0..-1 ||
|
lastDownloadedHeight != null ||
|
||||||
lastScanRange != 0..-1
|
lastDownloadRange != null ||
|
||||||
|
lastScanRange != null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether this instance is actively downloading compact blocks.
|
* Determines whether this instance is actively downloading compact blocks.
|
||||||
*
|
*
|
||||||
* @return true when there are more than zero blocks remaining to download.
|
* @return true when there are more than zero blocks remaining to download.
|
||||||
*/
|
*/
|
||||||
val isDownloading: Boolean get() = !lastDownloadRange.isEmpty() &&
|
val isDownloading: Boolean
|
||||||
lastDownloadedHeight < lastDownloadRange.last
|
get() =
|
||||||
|
lastDownloadedHeight != null &&
|
||||||
|
lastDownloadRange != null &&
|
||||||
|
!lastDownloadRange.isEmpty() &&
|
||||||
|
lastDownloadedHeight < lastDownloadRange.endInclusive
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether this instance is actively scanning or validating compact blocks.
|
* Determines whether this instance is actively scanning or validating compact blocks.
|
||||||
@@ -944,32 +1071,39 @@ class CompactBlockProcessor(
|
|||||||
* @return true when downloading has completed and there are more than zero blocks remaining
|
* @return true when downloading has completed and there are more than zero blocks remaining
|
||||||
* to be scanned.
|
* to be scanned.
|
||||||
*/
|
*/
|
||||||
val isScanning: Boolean get() = !isDownloading &&
|
val isScanning: Boolean
|
||||||
!lastScanRange.isEmpty() &&
|
get() =
|
||||||
lastScannedHeight < lastScanRange.last
|
!isDownloading &&
|
||||||
|
lastScannedHeight != null &&
|
||||||
|
lastScanRange != null &&
|
||||||
|
!lastScanRange.isEmpty() &&
|
||||||
|
lastScannedHeight < lastScanRange.endInclusive
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The amount of scan progress from 0 to 100.
|
* The amount of scan progress from 0 to 100.
|
||||||
*/
|
*/
|
||||||
val scanProgress get() = when {
|
val scanProgress
|
||||||
lastScannedHeight <= -1 -> 0
|
get() = when {
|
||||||
lastScanRange.isEmpty() -> 100
|
lastScannedHeight == null -> 0
|
||||||
lastScannedHeight >= lastScanRange.last -> 100
|
lastScanRange == null -> 100
|
||||||
else -> {
|
lastScannedHeight >= lastScanRange.endInclusive -> 100
|
||||||
// when lastScannedHeight == lastScanRange.first, we have scanned one block, thus the offsets
|
else -> {
|
||||||
val blocksScanned = (lastScannedHeight - lastScanRange.first + 1).coerceAtLeast(0)
|
// when lastScannedHeight == lastScanRange.first, we have scanned one block, thus the offsets
|
||||||
// we scan the range inclusively so 100..100 is one block to scan, thus the offset
|
val blocksScanned =
|
||||||
val numberOfBlocks = lastScanRange.last - lastScanRange.first + 1
|
(lastScannedHeight.value - lastScanRange.start.value + 1).coerceAtLeast(0)
|
||||||
// take the percentage then convert and round
|
// we scan the range inclusively so 100..100 is one block to scan, thus the offset
|
||||||
((blocksScanned.toFloat() / numberOfBlocks) * 100.0f).let { percent ->
|
val numberOfBlocks =
|
||||||
percent.coerceAtMost(100.0f).roundToInt()
|
lastScanRange.endInclusive.value - lastScanRange.start.value + 1
|
||||||
|
// take the percentage then convert and round
|
||||||
|
((blocksScanned.toFloat() / numberOfBlocks) * 100.0f).let { percent ->
|
||||||
|
percent.coerceAtMost(100.0f).roundToInt()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ValidationErrorInfo(
|
data class ValidationErrorInfo(
|
||||||
val errorHeight: Int,
|
val errorHeight: BlockHeight,
|
||||||
val hash: String?,
|
val hash: String?,
|
||||||
val expectedPrevHash: String?,
|
val expectedPrevHash: String?,
|
||||||
val actualPrevHash: String?
|
val actualPrevHash: String?
|
||||||
@@ -1007,10 +1141,12 @@ class CompactBlockProcessor(
|
|||||||
}
|
}
|
||||||
twig("$name MUTEX: withLock complete", -1)
|
twig("$name MUTEX: withLock complete", -1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
companion object {
|
|
||||||
const val ERROR_CODE_NONE = -1
|
private fun max(a: BlockHeight?, b: BlockHeight) = if (null == a) {
|
||||||
const val ERROR_CODE_RECONNECT = 20
|
b
|
||||||
const val ERROR_CODE_FAILED_ENHANCE = 40
|
} else if (a.value > b.value) {
|
||||||
}
|
a
|
||||||
|
} else {
|
||||||
|
b
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import androidx.room.Entity
|
|||||||
|
|
||||||
@Entity(primaryKeys = ["height"], tableName = "compactblocks")
|
@Entity(primaryKeys = ["height"], tableName = "compactblocks")
|
||||||
data class CompactBlockEntity(
|
data class CompactBlockEntity(
|
||||||
val height: Int,
|
val height: Long,
|
||||||
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
||||||
val data: ByteArray
|
val data: ByteArray
|
||||||
) {
|
) {
|
||||||
@@ -20,7 +20,7 @@ data class CompactBlockEntity(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = height
|
var result = height.hashCode()
|
||||||
result = 31 * result + data.contentHashCode()
|
result = 31 * result + data.contentHashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.room.ColumnInfo
|
|||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -81,8 +82,8 @@ data class PendingTransactionEntity(
|
|||||||
override val value: Long = -1,
|
override val value: Long = -1,
|
||||||
override val memo: ByteArray? = byteArrayOf(),
|
override val memo: ByteArray? = byteArrayOf(),
|
||||||
override val accountIndex: Int,
|
override val accountIndex: Int,
|
||||||
override val minedHeight: Int = -1,
|
override val minedHeight: Long = -1,
|
||||||
override val expiryHeight: Int = -1,
|
override val expiryHeight: Long = -1,
|
||||||
|
|
||||||
override val cancelled: Int = 0,
|
override val cancelled: Int = 0,
|
||||||
override val encodeAttempts: Int = -1,
|
override val encodeAttempts: Int = -1,
|
||||||
@@ -96,6 +97,10 @@ data class PendingTransactionEntity(
|
|||||||
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
||||||
override val rawTransactionId: ByteArray? = byteArrayOf()
|
override val rawTransactionId: ByteArray? = byteArrayOf()
|
||||||
) : PendingTransaction {
|
) : PendingTransaction {
|
||||||
|
|
||||||
|
val valueZatoshi: Zatoshi
|
||||||
|
get() = Zatoshi(value)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other !is PendingTransactionEntity) return false
|
if (other !is PendingTransactionEntity) return false
|
||||||
@@ -131,8 +136,8 @@ data class PendingTransactionEntity(
|
|||||||
result = 31 * result + value.hashCode()
|
result = 31 * result + value.hashCode()
|
||||||
result = 31 * result + (memo?.contentHashCode() ?: 0)
|
result = 31 * result + (memo?.contentHashCode() ?: 0)
|
||||||
result = 31 * result + accountIndex
|
result = 31 * result + accountIndex
|
||||||
result = 31 * result + minedHeight
|
result = 31 * result + minedHeight.hashCode()
|
||||||
result = 31 * result + expiryHeight
|
result = 31 * result + expiryHeight.hashCode()
|
||||||
result = 31 * result + cancelled
|
result = 31 * result + cancelled
|
||||||
result = 31 * result + encodeAttempts
|
result = 31 * result + encodeAttempts
|
||||||
result = 31 * result + submitAttempts
|
result = 31 * result + submitAttempts
|
||||||
@@ -159,7 +164,7 @@ data class ConfirmedTransaction(
|
|||||||
override val memo: ByteArray? = ByteArray(0),
|
override val memo: ByteArray? = ByteArray(0),
|
||||||
override val noteId: Long = 0L,
|
override val noteId: Long = 0L,
|
||||||
override val blockTimeInSeconds: Long = 0L,
|
override val blockTimeInSeconds: Long = 0L,
|
||||||
override val minedHeight: Int = -1,
|
override val minedHeight: Long = -1,
|
||||||
override val transactionIndex: Int,
|
override val transactionIndex: Int,
|
||||||
override val rawTransactionId: ByteArray = ByteArray(0),
|
override val rawTransactionId: ByteArray = ByteArray(0),
|
||||||
|
|
||||||
@@ -169,6 +174,13 @@ data class ConfirmedTransaction(
|
|||||||
override val raw: ByteArray? = byteArrayOf()
|
override val raw: ByteArray? = byteArrayOf()
|
||||||
) : MinedTransaction, SignedTransaction {
|
) : MinedTransaction, SignedTransaction {
|
||||||
|
|
||||||
|
val minedBlockHeight
|
||||||
|
get() = if (minedHeight == -1L) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
BlockHeight(minedHeight)
|
||||||
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other !is ConfirmedTransaction) return false
|
if (other !is ConfirmedTransaction) return false
|
||||||
@@ -200,7 +212,7 @@ data class ConfirmedTransaction(
|
|||||||
result = 31 * result + (memo?.contentHashCode() ?: 0)
|
result = 31 * result + (memo?.contentHashCode() ?: 0)
|
||||||
result = 31 * result + noteId.hashCode()
|
result = 31 * result + noteId.hashCode()
|
||||||
result = 31 * result + blockTimeInSeconds.hashCode()
|
result = 31 * result + blockTimeInSeconds.hashCode()
|
||||||
result = 31 * result + minedHeight
|
result = 31 * result + minedHeight.hashCode()
|
||||||
result = 31 * result + transactionIndex
|
result = 31 * result + transactionIndex
|
||||||
result = 31 * result + rawTransactionId.contentHashCode()
|
result = 31 * result + rawTransactionId.contentHashCode()
|
||||||
result = 31 * result + (toAddress?.hashCode() ?: 0)
|
result = 31 * result + (toAddress?.hashCode() ?: 0)
|
||||||
@@ -213,8 +225,12 @@ data class ConfirmedTransaction(
|
|||||||
val ConfirmedTransaction.valueInZatoshi
|
val ConfirmedTransaction.valueInZatoshi
|
||||||
get() = Zatoshi(value)
|
get() = Zatoshi(value)
|
||||||
|
|
||||||
data class EncodedTransaction(val txId: ByteArray, override val raw: ByteArray, val expiryHeight: Int?) :
|
data class EncodedTransaction(val txId: ByteArray, override val raw: ByteArray, val expiryHeight: Long?) :
|
||||||
SignedTransaction {
|
SignedTransaction {
|
||||||
|
|
||||||
|
val expiryBlockHeight
|
||||||
|
get() = expiryHeight?.let { BlockHeight(it) }
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other !is EncodedTransaction) return false
|
if (other !is EncodedTransaction) return false
|
||||||
@@ -229,7 +245,7 @@ data class EncodedTransaction(val txId: ByteArray, override val raw: ByteArray,
|
|||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = txId.contentHashCode()
|
var result = txId.contentHashCode()
|
||||||
result = 31 * result + raw.contentHashCode()
|
result = 31 * result + raw.contentHashCode()
|
||||||
result = 31 * result + (expiryHeight ?: 0)
|
result = 31 * result + (expiryHeight?.hashCode() ?: 0)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,7 +276,7 @@ interface SignedTransaction {
|
|||||||
* one list for things like history. A mined tx should have all properties, except possibly a memo.
|
* one list for things like history. A mined tx should have all properties, except possibly a memo.
|
||||||
*/
|
*/
|
||||||
interface MinedTransaction : Transaction {
|
interface MinedTransaction : Transaction {
|
||||||
val minedHeight: Int
|
val minedHeight: Long
|
||||||
val noteId: Long
|
val noteId: Long
|
||||||
val blockTimeInSeconds: Long
|
val blockTimeInSeconds: Long
|
||||||
val transactionIndex: Int
|
val transactionIndex: Int
|
||||||
@@ -273,8 +289,8 @@ interface PendingTransaction : SignedTransaction, Transaction {
|
|||||||
override val memo: ByteArray?
|
override val memo: ByteArray?
|
||||||
val toAddress: String
|
val toAddress: String
|
||||||
val accountIndex: Int
|
val accountIndex: Int
|
||||||
val minedHeight: Int
|
val minedHeight: Long // apparently this can be -1 as an uninitialized value
|
||||||
val expiryHeight: Int
|
val expiryHeight: Long // apparently this can be -1 as an uninitialized value
|
||||||
val cancelled: Int
|
val cancelled: Int
|
||||||
val encodeAttempts: Int
|
val encodeAttempts: Int
|
||||||
val submitAttempts: Int
|
val submitAttempts: Int
|
||||||
@@ -333,16 +349,16 @@ fun PendingTransaction.isSubmitted(): Boolean {
|
|||||||
return submitAttempts > 0
|
return submitAttempts > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun PendingTransaction.isExpired(latestHeight: Int?, saplingActivationHeight: Int): Boolean {
|
fun PendingTransaction.isExpired(latestHeight: BlockHeight?, saplingActivationHeight: BlockHeight): Boolean {
|
||||||
// TODO: test for off-by-one error here. Should we use <= or <
|
// TODO: test for off-by-one error here. Should we use <= or <
|
||||||
if (latestHeight == null || latestHeight < saplingActivationHeight || expiryHeight < saplingActivationHeight) return false
|
if (latestHeight == null || latestHeight.value < saplingActivationHeight.value || expiryHeight < saplingActivationHeight.value) return false
|
||||||
return expiryHeight < latestHeight
|
return expiryHeight < latestHeight.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we don't have info on a pendingtx after 100 blocks then it's probably safe to stop polling!
|
// if we don't have info on a pendingtx after 100 blocks then it's probably safe to stop polling!
|
||||||
fun PendingTransaction.isLongExpired(latestHeight: Int?, saplingActivationHeight: Int): Boolean {
|
fun PendingTransaction.isLongExpired(latestHeight: BlockHeight?, saplingActivationHeight: BlockHeight): Boolean {
|
||||||
if (latestHeight == null || latestHeight < saplingActivationHeight || expiryHeight < saplingActivationHeight) return false
|
if (latestHeight == null || latestHeight.value < saplingActivationHeight.value || expiryHeight < saplingActivationHeight.value) return false
|
||||||
return (latestHeight - expiryHeight) > 100
|
return (latestHeight.value - expiryHeight) > 100
|
||||||
}
|
}
|
||||||
|
|
||||||
fun PendingTransaction.isMarkedForDeletion(): Boolean {
|
fun PendingTransaction.isMarkedForDeletion(): Boolean {
|
||||||
@@ -369,10 +385,10 @@ fun PendingTransaction.isSafeToDiscard(): Boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun PendingTransaction.isPending(currentHeight: Int = -1): Boolean {
|
fun PendingTransaction.isPending(currentHeight: BlockHeight?): Boolean {
|
||||||
// not mined and not expired and successfully created
|
// not mined and not expired and successfully created
|
||||||
return !isSubmitSuccess() && minedHeight == -1 &&
|
return !isSubmitSuccess() && minedHeight == -1L &&
|
||||||
(expiryHeight == -1 || expiryHeight > currentHeight) && raw != null
|
(expiryHeight == -1L || expiryHeight > (currentHeight?.value ?: 0L)) && raw != null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun PendingTransaction.isSubmitSuccess(): Boolean {
|
fun PendingTransaction.isSubmitSuccess(): Boolean {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package cash.z.ecc.android.sdk.exception
|
package cash.z.ecc.android.sdk.exception
|
||||||
|
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
import cash.z.ecc.android.sdk.internal.model.Checkpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.wallet.sdk.rpc.Service
|
import cash.z.wallet.sdk.rpc.Service
|
||||||
import io.grpc.Status
|
import io.grpc.Status
|
||||||
import io.grpc.Status.Code.UNAVAILABLE
|
import io.grpc.Status.Code.UNAVAILABLE
|
||||||
@@ -15,8 +17,7 @@ open class SdkException(message: String, cause: Throwable?) : RuntimeException(m
|
|||||||
* Exceptions thrown in the Rust layer of the SDK. We may not always be able to surface details about this
|
* Exceptions thrown in the Rust layer of the SDK. We may not always be able to surface details about this
|
||||||
* exception so it's important for the SDK to provide helpful messages whenever these errors are encountered.
|
* exception so it's important for the SDK to provide helpful messages whenever these errors are encountered.
|
||||||
*/
|
*/
|
||||||
sealed class RustLayerException(message: String, cause: Throwable? = null) :
|
sealed class RustLayerException(message: String, cause: Throwable? = null) : SdkException(message, cause) {
|
||||||
SdkException(message, cause) {
|
|
||||||
class BalanceException(cause: Throwable) : RustLayerException(
|
class BalanceException(cause: Throwable) : RustLayerException(
|
||||||
"Error while requesting the current balance over " +
|
"Error while requesting the current balance over " +
|
||||||
"JNI. This might mean that the database has been corrupted and needs to be rebuilt. Verify that " +
|
"JNI. This might mean that the database has been corrupted and needs to be rebuilt. Verify that " +
|
||||||
@@ -28,13 +29,11 @@ sealed class RustLayerException(message: String, cause: Throwable? = null) :
|
|||||||
/**
|
/**
|
||||||
* User-facing exceptions thrown by the transaction repository.
|
* User-facing exceptions thrown by the transaction repository.
|
||||||
*/
|
*/
|
||||||
sealed class RepositoryException(message: String, cause: Throwable? = null) :
|
sealed class RepositoryException(message: String, cause: Throwable? = null) : SdkException(message, cause) {
|
||||||
SdkException(message, cause) {
|
|
||||||
object FalseStart : RepositoryException(
|
object FalseStart : RepositoryException(
|
||||||
"The channel is closed. Note that once a repository has stopped it " +
|
"The channel is closed. Note that once a repository has stopped it " +
|
||||||
"cannot be restarted. Verify that the repository is not being restarted."
|
"cannot be restarted. Verify that the repository is not being restarted."
|
||||||
)
|
)
|
||||||
|
|
||||||
object Unprepared : RepositoryException(
|
object Unprepared : RepositoryException(
|
||||||
"Unprepared repository: Data cannot be accessed before the repository is prepared." +
|
"Unprepared repository: Data cannot be accessed before the repository is prepared." +
|
||||||
" Ensure that things have been properly initialized. If you see this error it most" +
|
" Ensure that things have been properly initialized. If you see this error it most" +
|
||||||
@@ -49,13 +48,11 @@ sealed class RepositoryException(message: String, cause: Throwable? = null) :
|
|||||||
* High-level exceptions thrown by the synchronizer, which do not fall within the umbrella of a
|
* High-level exceptions thrown by the synchronizer, which do not fall within the umbrella of a
|
||||||
* child component.
|
* child component.
|
||||||
*/
|
*/
|
||||||
sealed class SynchronizerException(message: String, cause: Throwable? = null) :
|
sealed class SynchronizerException(message: String, cause: Throwable? = null) : SdkException(message, cause) {
|
||||||
SdkException(message, cause) {
|
|
||||||
object FalseStart : SynchronizerException(
|
object FalseStart : SynchronizerException(
|
||||||
"This synchronizer was already started. Multiple calls to start are not" +
|
"This synchronizer was already started. Multiple calls to start are not" +
|
||||||
"allowed and once a synchronizer has stopped it cannot be restarted."
|
"allowed and once a synchronizer has stopped it cannot be restarted."
|
||||||
)
|
)
|
||||||
|
|
||||||
object NotYetStarted : SynchronizerException(
|
object NotYetStarted : SynchronizerException(
|
||||||
"The synchronizer has not yet started. Verify that" +
|
"The synchronizer has not yet started. Verify that" +
|
||||||
" start has been called prior to this operation and that the coroutineScope is not" +
|
" start has been called prior to this operation and that the coroutineScope is not" +
|
||||||
@@ -66,16 +63,12 @@ sealed class SynchronizerException(message: String, cause: Throwable? = null) :
|
|||||||
/**
|
/**
|
||||||
* Potentially user-facing exceptions that occur while processing compact blocks.
|
* Potentially user-facing exceptions that occur while processing compact blocks.
|
||||||
*/
|
*/
|
||||||
sealed class CompactBlockProcessorException(message: String, cause: Throwable? = null) :
|
sealed class CompactBlockProcessorException(message: String, cause: Throwable? = null) : SdkException(message, cause) {
|
||||||
SdkException(message, cause) {
|
|
||||||
class DataDbMissing(path: String) : CompactBlockProcessorException(
|
class DataDbMissing(path: String) : CompactBlockProcessorException(
|
||||||
"No data db file found at path $path. Verify " +
|
"No data db file found at path $path. Verify " +
|
||||||
"that the data DB has been initialized via `rustBackend.initDataDb(path)`"
|
"that the data DB has been initialized via `rustBackend.initDataDb(path)`"
|
||||||
)
|
)
|
||||||
|
open class ConfigurationException(message: String, cause: Throwable?) : CompactBlockProcessorException(message, cause)
|
||||||
open class ConfigurationException(message: String, cause: Throwable?) :
|
|
||||||
CompactBlockProcessorException(message, cause)
|
|
||||||
|
|
||||||
class FileInsteadOfPath(fileName: String) : ConfigurationException(
|
class FileInsteadOfPath(fileName: String) : ConfigurationException(
|
||||||
"Invalid Path: the given path appears to be a" +
|
"Invalid Path: the given path appears to be a" +
|
||||||
" file name instead of a path: $fileName. The RustBackend expects the absolutePath to the database rather" +
|
" file name instead of a path: $fileName. The RustBackend expects the absolutePath to the database rather" +
|
||||||
@@ -83,137 +76,100 @@ sealed class CompactBlockProcessorException(message: String, cause: Throwable? =
|
|||||||
" So pass in context.getDatabasePath(dbFileName).absolutePath instead of just dbFileName alone.",
|
" So pass in context.getDatabasePath(dbFileName).absolutePath instead of just dbFileName alone.",
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
|
||||||
class FailedReorgRepair(message: String) : CompactBlockProcessorException(message)
|
class FailedReorgRepair(message: String) : CompactBlockProcessorException(message)
|
||||||
class FailedDownload(cause: Throwable? = null) : CompactBlockProcessorException(
|
class FailedDownload(cause: Throwable? = null) : CompactBlockProcessorException(
|
||||||
"Error while downloading blocks. This most " +
|
"Error while downloading blocks. This most " +
|
||||||
"likely means the server is down or slow to respond. See logs for details.",
|
"likely means the server is down or slow to respond. See logs for details.",
|
||||||
cause
|
cause
|
||||||
)
|
)
|
||||||
|
|
||||||
class FailedScan(cause: Throwable? = null) : CompactBlockProcessorException(
|
class FailedScan(cause: Throwable? = null) : CompactBlockProcessorException(
|
||||||
"Error while scanning blocks. This most " +
|
"Error while scanning blocks. This most " +
|
||||||
"likely means a block was missed or a reorg was mishandled. See logs for details.",
|
"likely means a block was missed or a reorg was mishandled. See logs for details.",
|
||||||
cause
|
cause
|
||||||
)
|
)
|
||||||
|
class Disconnected(cause: Throwable? = null) : CompactBlockProcessorException("Disconnected Error. Unable to download blocks due to ${cause?.message}", cause)
|
||||||
class Disconnected(cause: Throwable? = null) : CompactBlockProcessorException(
|
|
||||||
"Disconnected Error. Unable to download blocks due to ${cause?.message}",
|
|
||||||
cause
|
|
||||||
)
|
|
||||||
|
|
||||||
object Uninitialized : CompactBlockProcessorException(
|
object Uninitialized : CompactBlockProcessorException(
|
||||||
"Cannot process blocks because the wallet has not been" +
|
"Cannot process blocks because the wallet has not been" +
|
||||||
" initialized. Verify that the seed phrase was properly created or imported. If so, then this problem" +
|
" initialized. Verify that the seed phrase was properly created or imported. If so, then this problem" +
|
||||||
" can be fixed by re-importing the wallet."
|
" can be fixed by re-importing the wallet."
|
||||||
)
|
)
|
||||||
|
|
||||||
object NoAccount : CompactBlockProcessorException(
|
object NoAccount : CompactBlockProcessorException(
|
||||||
"Attempting to scan without an account. This is probably a setup error or a race condition."
|
"Attempting to scan without an account. This is probably a setup error or a race condition."
|
||||||
)
|
)
|
||||||
|
|
||||||
open class EnhanceTransactionError(message: String, val height: Int, cause: Throwable) :
|
open class EnhanceTransactionError(message: String, val height: BlockHeight?, cause: Throwable) : CompactBlockProcessorException(message, cause) {
|
||||||
CompactBlockProcessorException(message, cause) {
|
class EnhanceTxDownloadError(height: BlockHeight?, cause: Throwable) : EnhanceTransactionError("Error while attempting to download a transaction to enhance", height, cause)
|
||||||
class EnhanceTxDownloadError(height: Int, cause: Throwable) : EnhanceTransactionError(
|
class EnhanceTxDecryptError(height: BlockHeight?, cause: Throwable) : EnhanceTransactionError("Error while attempting to decrypt and store a transaction to enhance", height, cause)
|
||||||
"Error while attempting to download a transaction to enhance",
|
|
||||||
height,
|
|
||||||
cause
|
|
||||||
)
|
|
||||||
|
|
||||||
class EnhanceTxDecryptError(height: Int, cause: Throwable) : EnhanceTransactionError(
|
|
||||||
"Error while attempting to decrypt and store a transaction to enhance",
|
|
||||||
height,
|
|
||||||
cause
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MismatchedNetwork(clientNetwork: String?, serverNetwork: String?) :
|
class MismatchedNetwork(clientNetwork: String?, serverNetwork: String?) : CompactBlockProcessorException(
|
||||||
CompactBlockProcessorException(
|
"Incompatible server: this client expects a server using $clientNetwork but it was $serverNetwork! Try updating the client or switching servers."
|
||||||
"Incompatible server: this client expects a server using $clientNetwork but it was $serverNetwork! Try updating the client or switching servers."
|
)
|
||||||
)
|
|
||||||
|
|
||||||
class MismatchedBranch(clientBranch: String?, serverBranch: String?, networkName: String?) :
|
class MismatchedBranch(clientBranch: String?, serverBranch: String?, networkName: String?) : CompactBlockProcessorException(
|
||||||
CompactBlockProcessorException(
|
"Incompatible server: this client expects a server following consensus branch $clientBranch on $networkName but it was $serverBranch! Try updating the client or switching servers."
|
||||||
"Incompatible server: this client expects a server following consensus branch $clientBranch on $networkName but it was $serverBranch! Try updating the client or switching servers."
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exceptions related to the wallet's birthday.
|
* Exceptions related to the wallet's birthday.
|
||||||
*/
|
*/
|
||||||
sealed class BirthdayException(message: String, cause: Throwable? = null) :
|
sealed class BirthdayException(message: String, cause: Throwable? = null) : SdkException(message, cause) {
|
||||||
SdkException(message, cause) {
|
|
||||||
object UninitializedBirthdayException : BirthdayException(
|
object UninitializedBirthdayException : BirthdayException(
|
||||||
"Error the birthday cannot be" +
|
"Error the birthday cannot be" +
|
||||||
" accessed before it is initialized. Verify that the new, import or open functions" +
|
" accessed before it is initialized. Verify that the new, import or open functions" +
|
||||||
" have been called on the initializer."
|
" have been called on the initializer."
|
||||||
)
|
)
|
||||||
|
|
||||||
class MissingBirthdayFilesException(directory: String) : BirthdayException(
|
class MissingBirthdayFilesException(directory: String) : BirthdayException(
|
||||||
"Cannot initialize wallet because no birthday files were found in the $directory directory."
|
"Cannot initialize wallet because no birthday files were found in the $directory directory."
|
||||||
)
|
)
|
||||||
|
class ExactBirthdayNotFoundException internal constructor(birthday: BlockHeight, nearestMatch: Checkpoint? = null) : BirthdayException(
|
||||||
class ExactBirthdayNotFoundException(height: Int, nearestMatch: Int? = null) :
|
"Unable to find birthday that exactly matches $birthday.${
|
||||||
BirthdayException(
|
if (nearestMatch != null) {
|
||||||
"Unable to find birthday that exactly matches $height.${
|
" An exact match was request but the nearest match found was ${nearestMatch.height}."
|
||||||
if (nearestMatch != null) {
|
} else ""
|
||||||
" An exact match was request but the nearest match found was $nearestMatch."
|
}"
|
||||||
} else {
|
)
|
||||||
""
|
class BirthdayFileNotFoundException(directory: String, height: BlockHeight?) : BirthdayException(
|
||||||
}
|
|
||||||
}"
|
|
||||||
)
|
|
||||||
|
|
||||||
class BirthdayFileNotFoundException(directory: String, height: Int?) : BirthdayException(
|
|
||||||
"Unable to find birthday file for $height verify that $directory/$height.json exists."
|
"Unable to find birthday file for $height verify that $directory/$height.json exists."
|
||||||
)
|
)
|
||||||
|
class MalformattedBirthdayFilesException(directory: String, file: String, cause: Throwable?) : BirthdayException(
|
||||||
class MalformattedBirthdayFilesException(directory: String, file: String, cause: Throwable?) :
|
"Failed to parse file $directory/$file verify that it is formatted as #####.json, " +
|
||||||
BirthdayException(
|
"where the first portion is an Int representing the height of the tree contained in the file",
|
||||||
"Failed to parse file $directory/$file verify that it is formatted as #####.json, " +
|
cause
|
||||||
"where the first portion is an Int representing the height of the tree contained in the file",
|
)
|
||||||
cause
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exceptions thrown by the initializer.
|
* Exceptions thrown by the initializer.
|
||||||
*/
|
*/
|
||||||
sealed class InitializerException(message: String, cause: Throwable? = null) :
|
sealed class InitializerException(message: String, cause: Throwable? = null) : SdkException(message, cause) {
|
||||||
SdkException(message, cause) {
|
class FalseStart(cause: Throwable?) : InitializerException("Failed to initialize accounts due to: $cause", cause)
|
||||||
class FalseStart(cause: Throwable?) :
|
|
||||||
InitializerException("Failed to initialize accounts due to: $cause", cause)
|
|
||||||
|
|
||||||
class AlreadyInitializedException(cause: Throwable, dbPath: String) : InitializerException(
|
class AlreadyInitializedException(cause: Throwable, dbPath: String) : InitializerException(
|
||||||
"Failed to initialize the blocks table" +
|
"Failed to initialize the blocks table" +
|
||||||
" because it already exists in $dbPath",
|
" because it already exists in $dbPath",
|
||||||
cause
|
cause
|
||||||
)
|
)
|
||||||
|
|
||||||
object MissingBirthdayException : InitializerException(
|
object MissingBirthdayException : InitializerException(
|
||||||
"Expected a birthday for this wallet but failed to find one. This usually means that " +
|
"Expected a birthday for this wallet but failed to find one. This usually means that " +
|
||||||
"wallet setup did not happen correctly. A workaround might be to interpret the " +
|
"wallet setup did not happen correctly. A workaround might be to interpret the " +
|
||||||
"birthday, based on the contents of the wallet data but it is probably better " +
|
"birthday, based on the contents of the wallet data but it is probably better " +
|
||||||
"not to mask this error because the root issue should be addressed."
|
"not to mask this error because the root issue should be addressed."
|
||||||
)
|
)
|
||||||
|
|
||||||
object MissingViewingKeyException : InitializerException(
|
object MissingViewingKeyException : InitializerException(
|
||||||
"Expected a unified viewingKey for this wallet but failed to find one. This usually means" +
|
"Expected a unified viewingKey for this wallet but failed to find one. This usually means" +
|
||||||
" that wallet setup happened incorrectly. A workaround might be to derive the" +
|
" that wallet setup happened incorrectly. A workaround might be to derive the" +
|
||||||
" unified viewingKey from the seed or seedPhrase, if they exist, but it is probably" +
|
" unified viewingKey from the seed or seedPhrase, if they exist, but it is probably" +
|
||||||
" better not to mask this error because the root issue should be addressed."
|
" better not to mask this error because the root issue should be addressed."
|
||||||
)
|
)
|
||||||
|
class MissingAddressException(description: String, cause: Throwable? = null) : InitializerException(
|
||||||
class MissingAddressException(description: String, cause: Throwable? = null) :
|
"Expected a $description address for this wallet but failed to find one. This usually" +
|
||||||
InitializerException(
|
" means that wallet setup happened incorrectly. If this problem persists, a" +
|
||||||
"Expected a $description address for this wallet but failed to find one. This usually" +
|
" workaround might be to go to settings and WIPE the wallet and rescan. Doing so" +
|
||||||
" means that wallet setup happened incorrectly. If this problem persists, a" +
|
" will restore any missing address information. Meanwhile, please report that" +
|
||||||
" workaround might be to go to settings and WIPE the wallet and rescan. Doing so" +
|
" this happened so that the root issue can be uncovered and corrected." +
|
||||||
" will restore any missing address information. Meanwhile, please report that" +
|
if (cause != null) "\nCaused by: $cause" else ""
|
||||||
" this happened so that the root issue can be uncovered and corrected." +
|
)
|
||||||
if (cause != null) "\nCaused by: $cause" else ""
|
|
||||||
)
|
|
||||||
|
|
||||||
object DatabasePathException :
|
object DatabasePathException :
|
||||||
InitializerException(
|
InitializerException(
|
||||||
"Critical failure to locate path for storing databases. Perhaps this device prevents" +
|
"Critical failure to locate path for storing databases. Perhaps this device prevents" +
|
||||||
@@ -221,11 +177,10 @@ sealed class InitializerException(message: String, cause: Throwable? = null) :
|
|||||||
" data."
|
" data."
|
||||||
)
|
)
|
||||||
|
|
||||||
class InvalidBirthdayHeightException(height: Int?, network: ZcashNetwork) :
|
class InvalidBirthdayHeightException(birthday: BlockHeight?, network: ZcashNetwork) : InitializerException(
|
||||||
InitializerException(
|
"Invalid birthday height of ${birthday?.value}. The birthday height must be at least the height of" +
|
||||||
"Invalid birthday height of $height. The birthday height must be at least the height of" +
|
" Sapling activation on ${network.networkName} (${network.saplingActivationHeight})."
|
||||||
" Sapling activation on ${network.networkName} (${network.saplingActivationHeight})."
|
)
|
||||||
)
|
|
||||||
|
|
||||||
object MissingDefaultBirthdayException : InitializerException(
|
object MissingDefaultBirthdayException : InitializerException(
|
||||||
"The birthday height is missing and it is unclear which value to use as a default."
|
"The birthday height is missing and it is unclear which value to use as a default."
|
||||||
@@ -235,15 +190,13 @@ sealed class InitializerException(message: String, cause: Throwable? = null) :
|
|||||||
/**
|
/**
|
||||||
* Exceptions thrown while interacting with lightwalletd.
|
* Exceptions thrown while interacting with lightwalletd.
|
||||||
*/
|
*/
|
||||||
sealed class LightWalletException(message: String, cause: Throwable? = null) :
|
sealed class LightWalletException(message: String, cause: Throwable? = null) : SdkException(message, cause) {
|
||||||
SdkException(message, cause) {
|
|
||||||
object InsecureConnection : LightWalletException(
|
object InsecureConnection : LightWalletException(
|
||||||
"Error: attempted to connect to lightwalletd" +
|
"Error: attempted to connect to lightwalletd" +
|
||||||
" with an insecure connection! Plaintext connections are only allowed when the" +
|
" with an insecure connection! Plaintext connections are only allowed when the" +
|
||||||
" resource value for 'R.bool.lightwalletd_allow_very_insecure_connections' is true" +
|
" resource value for 'R.bool.lightwalletd_allow_very_insecure_connections' is true" +
|
||||||
" because this choice should be explicit."
|
" because this choice should be explicit."
|
||||||
)
|
)
|
||||||
|
|
||||||
class ConsensusBranchException(sdkBranch: String, lwdBranch: String) :
|
class ConsensusBranchException(sdkBranch: String, lwdBranch: String) :
|
||||||
LightWalletException(
|
LightWalletException(
|
||||||
"Error: the lightwalletd server is using a consensus branch" +
|
"Error: the lightwalletd server is using a consensus branch" +
|
||||||
@@ -253,18 +206,11 @@ sealed class LightWalletException(message: String, cause: Throwable? = null) :
|
|||||||
" update the SDK to match lightwalletd or use a lightwalletd that matches the SDK."
|
" update the SDK to match lightwalletd or use a lightwalletd that matches the SDK."
|
||||||
)
|
)
|
||||||
|
|
||||||
open class ChangeServerException(message: String, cause: Throwable? = null) :
|
open class ChangeServerException(message: String, cause: Throwable? = null) : SdkException(message, cause) {
|
||||||
SdkException(message, cause) {
|
class ChainInfoNotMatching(val propertyNames: String, val expectedInfo: Service.LightdInfo, val actualInfo: Service.LightdInfo) : ChangeServerException(
|
||||||
class ChainInfoNotMatching(
|
|
||||||
val propertyNames: String,
|
|
||||||
val expectedInfo: Service.LightdInfo,
|
|
||||||
val actualInfo: Service.LightdInfo
|
|
||||||
) : ChangeServerException(
|
|
||||||
"Server change error: the $propertyNames values did not match."
|
"Server change error: the $propertyNames values did not match."
|
||||||
)
|
)
|
||||||
|
class StatusException(val status: Status, cause: Throwable? = null) : SdkException(status.toMessage(), cause) {
|
||||||
class StatusException(val status: Status, cause: Throwable? = null) :
|
|
||||||
SdkException(status.toMessage(), cause) {
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun Status.toMessage(): String {
|
private fun Status.toMessage(): String {
|
||||||
return when (this.code) {
|
return when (this.code) {
|
||||||
@@ -282,29 +228,23 @@ sealed class LightWalletException(message: String, cause: Throwable? = null) :
|
|||||||
/**
|
/**
|
||||||
* Potentially user-facing exceptions thrown while encoding transactions.
|
* Potentially user-facing exceptions thrown while encoding transactions.
|
||||||
*/
|
*/
|
||||||
sealed class TransactionEncoderException(message: String, cause: Throwable? = null) :
|
sealed class TransactionEncoderException(message: String, cause: Throwable? = null) : SdkException(message, cause) {
|
||||||
SdkException(message, cause) {
|
class FetchParamsException(message: String) : TransactionEncoderException("Failed to fetch params due to: $message")
|
||||||
class FetchParamsException(message: String) :
|
|
||||||
TransactionEncoderException("Failed to fetch params due to: $message")
|
|
||||||
|
|
||||||
object MissingParamsException : TransactionEncoderException(
|
object MissingParamsException : TransactionEncoderException(
|
||||||
"Cannot send funds due to missing spend or output params and attempting to download them failed."
|
"Cannot send funds due to missing spend or output params and attempting to download them failed."
|
||||||
)
|
)
|
||||||
|
|
||||||
class TransactionNotFoundException(transactionId: Long) : TransactionEncoderException(
|
class TransactionNotFoundException(transactionId: Long) : TransactionEncoderException(
|
||||||
"Unable to find transactionId " +
|
"Unable to find transactionId " +
|
||||||
"$transactionId in the repository. This means the wallet created a transaction and then returned a row ID " +
|
"$transactionId in the repository. This means the wallet created a transaction and then returned a row ID " +
|
||||||
"that does not actually exist. This is a scenario where the wallet should have thrown an exception but failed " +
|
"that does not actually exist. This is a scenario where the wallet should have thrown an exception but failed " +
|
||||||
"to do so."
|
"to do so."
|
||||||
)
|
)
|
||||||
|
|
||||||
class TransactionNotEncodedException(transactionId: Long) : TransactionEncoderException(
|
class TransactionNotEncodedException(transactionId: Long) : TransactionEncoderException(
|
||||||
"The transaction returned by the wallet," +
|
"The transaction returned by the wallet," +
|
||||||
" with id $transactionId, does not have any raw data. This is a scenario where the wallet should have thrown" +
|
" with id $transactionId, does not have any raw data. This is a scenario where the wallet should have thrown" +
|
||||||
" an exception but failed to do so."
|
" an exception but failed to do so."
|
||||||
)
|
)
|
||||||
|
class IncompleteScanException(lastScannedHeight: BlockHeight) : TransactionEncoderException(
|
||||||
class IncompleteScanException(lastScannedHeight: Int) : TransactionEncoderException(
|
|
||||||
"Cannot" +
|
"Cannot" +
|
||||||
" create spending transaction because scanning is incomplete. We must scan up to the" +
|
" create spending transaction because scanning is incomplete. We must scan up to the" +
|
||||||
" latest height to know which consensus rules to apply. However, the last scanned" +
|
" latest height to know which consensus rules to apply. However, the last scanned" +
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
package cash.z.ecc.android.sdk.ext
|
package cash.z.ecc.android.sdk.ext
|
||||||
|
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class BatchMetrics(val range: IntRange, val batchSize: Int, private val onMetricComplete: ((BatchMetrics, Boolean) -> Unit)? = null) {
|
class BatchMetrics(val range: ClosedRange<BlockHeight>, val batchSize: Int, private val onMetricComplete: ((BatchMetrics, Boolean) -> Unit)? = null) {
|
||||||
private var completedBatches = 0
|
private var completedBatches = 0
|
||||||
private var rangeStartTime = 0L
|
private var rangeStartTime = 0L
|
||||||
private var batchStartTime = 0L
|
private var batchStartTime = 0L
|
||||||
private var batchEndTime = 0L
|
private var batchEndTime = 0L
|
||||||
private var rangeSize = range.last - range.first + 1
|
private var rangeSize = range.endInclusive.value - range.start.value + 1
|
||||||
private inline fun now() = System.currentTimeMillis()
|
private inline fun now() = System.currentTimeMillis()
|
||||||
private inline fun ips(blocks: Int, time: Long) = 1000.0f * blocks / time
|
private inline fun ips(blocks: Long, time: Long) = 1000.0f * blocks / time
|
||||||
|
|
||||||
val isComplete get() = completedBatches * batchSize >= rangeSize
|
val isComplete get() = completedBatches * batchSize >= rangeSize
|
||||||
val isBatchComplete get() = batchEndTime > batchStartTime
|
val isBatchComplete get() = batchEndTime > batchStartTime
|
||||||
val cumulativeItems get() = min(completedBatches * batchSize, rangeSize)
|
val cumulativeItems get() = min(completedBatches * batchSize.toLong(), rangeSize)
|
||||||
val cumulativeTime get() = (if (isComplete) batchEndTime else now()) - rangeStartTime
|
val cumulativeTime get() = (if (isComplete) batchEndTime else now()) - rangeStartTime
|
||||||
val batchTime get() = max(batchEndTime - batchStartTime, now() - batchStartTime)
|
val batchTime get() = max(batchEndTime - batchStartTime, now() - batchStartTime)
|
||||||
val batchItems get() = min(batchSize, batchSize - (completedBatches * batchSize - rangeSize))
|
val batchItems get() = min(batchSize.toLong(), batchSize - (completedBatches * batchSize - rangeSize))
|
||||||
val batchIps get() = ips(batchItems, batchTime)
|
val batchIps get() = ips(batchItems, batchTime)
|
||||||
val cumulativeIps get() = ips(cumulativeItems, cumulativeTime)
|
val cumulativeIps get() = ips(cumulativeItems, cumulativeTime)
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ import java.util.Locale
|
|||||||
*/
|
*/
|
||||||
enum class ConsensusBranchId(val displayName: String, val id: Long, val hexId: String) {
|
enum class ConsensusBranchId(val displayName: String, val id: Long, val hexId: String) {
|
||||||
// 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)
|
// 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)
|
||||||
SAPLING("Sapling", 0x76b8_09bb, "76b809bb");
|
SPROUT("Sprout", 0, "0"),
|
||||||
|
OVERWINTER("Overwinter", 0x5ba8_1b19, "5ba81b19"),
|
||||||
|
SAPLING("Sapling", 0x76b8_09bb, "76b809bb"),
|
||||||
|
BLOSSOM("Blossom", 0x2bb4_0e60, "2bb40e60"),
|
||||||
|
HEARTWOOD("Heartwood", 0xf5b9_230b, "f5b9230b"),
|
||||||
|
CANOPY("Canopy", 0xe9ff_75a6, "e9ff75a6");
|
||||||
|
|
||||||
override fun toString(): String = displayName
|
override fun toString(): String = displayName
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ object ZcashSdk {
|
|||||||
/**
|
/**
|
||||||
* Default size of batches of blocks to request from the compact block service.
|
* Default size of batches of blocks to request from the compact block service.
|
||||||
*/
|
*/
|
||||||
val DOWNLOAD_BATCH_SIZE = 100
|
// Because blocks are buffered in memory upon download and storage into SQLite, there is an upper bound
|
||||||
|
// above which OutOfMemoryError is thrown. Experimentally, this value is below 50 blocks.
|
||||||
|
// Back of the envelope calculation says the maximum block size is ~100kb.
|
||||||
|
const val DOWNLOAD_BATCH_SIZE = 10
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default size of batches of blocks to scan via librustzcash. The smaller this number the more granular information
|
* Default size of batches of blocks to scan via librustzcash. The smaller this number the more granular information
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package cash.z.ecc.android.sdk.internal
|
||||||
|
|
||||||
|
import cash.z.ecc.android.sdk.internal.model.Checkpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
// Version is not returned from the server, so version 1 is implied. A version is declared here
|
||||||
|
// to structure the parsing to be version-aware in the future.
|
||||||
|
internal val Checkpoint.Companion.VERSION_1
|
||||||
|
get() = 1
|
||||||
|
internal val Checkpoint.Companion.KEY_VERSION
|
||||||
|
get() = "version"
|
||||||
|
internal val Checkpoint.Companion.KEY_HEIGHT
|
||||||
|
get() = "height"
|
||||||
|
internal val Checkpoint.Companion.KEY_HASH
|
||||||
|
get() = "hash"
|
||||||
|
internal val Checkpoint.Companion.KEY_EPOCH_SECONDS
|
||||||
|
get() = "time"
|
||||||
|
internal val Checkpoint.Companion.KEY_TREE
|
||||||
|
get() = "saplingTree"
|
||||||
|
|
||||||
|
internal fun Checkpoint.Companion.from(zcashNetwork: ZcashNetwork, jsonString: String) =
|
||||||
|
from(zcashNetwork, JSONObject(jsonString))
|
||||||
|
|
||||||
|
private fun Checkpoint.Companion.from(
|
||||||
|
zcashNetwork: ZcashNetwork,
|
||||||
|
jsonObject: JSONObject
|
||||||
|
): Checkpoint {
|
||||||
|
when (val version = jsonObject.optInt(Checkpoint.KEY_VERSION, Checkpoint.VERSION_1)) {
|
||||||
|
Checkpoint.VERSION_1 -> {
|
||||||
|
val height = run {
|
||||||
|
val heightLong = jsonObject.getLong(Checkpoint.KEY_HEIGHT)
|
||||||
|
BlockHeight.new(zcashNetwork, heightLong)
|
||||||
|
}
|
||||||
|
val hash = jsonObject.getString(Checkpoint.KEY_HASH)
|
||||||
|
val epochSeconds = jsonObject.getLong(Checkpoint.KEY_EPOCH_SECONDS)
|
||||||
|
val tree = jsonObject.getString(Checkpoint.KEY_TREE)
|
||||||
|
|
||||||
|
return Checkpoint(height, hash, epochSeconds, tree)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw IllegalArgumentException("Unsupported version $version")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package cash.z.ecc.android.sdk.internal
|
||||||
|
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
|
||||||
|
internal fun ClosedRange<BlockHeight>?.isEmpty() = this?.isEmpty() ?: true
|
||||||
@@ -75,6 +75,7 @@ interface Twig {
|
|||||||
* @see [Twig.clip]
|
* @see [Twig.clip]
|
||||||
*/
|
*/
|
||||||
object Bush {
|
object Bush {
|
||||||
|
@Volatile
|
||||||
var trunk: Twig = SilentTwig()
|
var trunk: Twig = SilentTwig()
|
||||||
val leaves: MutableSet<Leaf> = CopyOnWriteArraySet<Leaf>()
|
val leaves: MutableSet<Leaf> = CopyOnWriteArraySet<Leaf>()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
package cash.z.ecc.android.sdk.internal
|
|
||||||
|
|
||||||
import cash.z.ecc.android.sdk.type.WalletBirthday
|
|
||||||
import org.json.JSONObject
|
|
||||||
|
|
||||||
// Version is not returned from the server, so version 1 is implied. A version is declared here
|
|
||||||
// to structure the parsing to be version-aware in the future.
|
|
||||||
internal val WalletBirthday.Companion.VERSION_1
|
|
||||||
get() = 1
|
|
||||||
internal val WalletBirthday.Companion.KEY_VERSION
|
|
||||||
get() = "version"
|
|
||||||
internal val WalletBirthday.Companion.KEY_HEIGHT
|
|
||||||
get() = "height"
|
|
||||||
internal val WalletBirthday.Companion.KEY_HASH
|
|
||||||
get() = "hash"
|
|
||||||
internal val WalletBirthday.Companion.KEY_EPOCH_SECONDS
|
|
||||||
get() = "time"
|
|
||||||
internal val WalletBirthday.Companion.KEY_TREE
|
|
||||||
get() = "saplingTree"
|
|
||||||
|
|
||||||
fun WalletBirthday.Companion.from(jsonString: String) = from(JSONObject(jsonString))
|
|
||||||
|
|
||||||
private fun WalletBirthday.Companion.from(jsonObject: JSONObject): WalletBirthday {
|
|
||||||
when (val version = jsonObject.optInt(WalletBirthday.KEY_VERSION, WalletBirthday.VERSION_1)) {
|
|
||||||
WalletBirthday.VERSION_1 -> {
|
|
||||||
val height = jsonObject.getInt(WalletBirthday.KEY_HEIGHT)
|
|
||||||
val hash = jsonObject.getString(WalletBirthday.KEY_HASH)
|
|
||||||
val epochSeconds = jsonObject.getLong(WalletBirthday.KEY_EPOCH_SECONDS)
|
|
||||||
val tree = jsonObject.getString(WalletBirthday.KEY_TREE)
|
|
||||||
|
|
||||||
return WalletBirthday(height, hash, epochSeconds, tree)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
throw IllegalArgumentException("Unsupported version $version")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,30 +7,34 @@ import cash.z.ecc.android.sdk.db.entity.CompactBlockEntity
|
|||||||
import cash.z.ecc.android.sdk.internal.SdkDispatchers
|
import cash.z.ecc.android.sdk.internal.SdkDispatchers
|
||||||
import cash.z.ecc.android.sdk.internal.SdkExecutors
|
import cash.z.ecc.android.sdk.internal.SdkExecutors
|
||||||
import cash.z.ecc.android.sdk.internal.db.CompactBlockDb
|
import cash.z.ecc.android.sdk.internal.db.CompactBlockDb
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.wallet.sdk.rpc.CompactFormats
|
import cash.z.wallet.sdk.rpc.CompactFormats
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of CompactBlockStore that persists information to a database in the given
|
* An implementation of CompactBlockStore that persists information to a database in the given
|
||||||
* path. This represents the "cache db" or local cache of compact blocks waiting to be scanned.
|
* path. This represents the "cache db" or local cache of compact blocks waiting to be scanned.
|
||||||
*/
|
*/
|
||||||
class CompactBlockDbStore private constructor(
|
class CompactBlockDbStore private constructor(
|
||||||
|
private val network: ZcashNetwork,
|
||||||
private val cacheDb: CompactBlockDb
|
private val cacheDb: CompactBlockDb
|
||||||
) : CompactBlockStore {
|
) : CompactBlockStore {
|
||||||
|
|
||||||
private val cacheDao = cacheDb.compactBlockDao()
|
private val cacheDao = cacheDb.compactBlockDao()
|
||||||
|
|
||||||
override suspend fun getLatestHeight(): Int = max(0, cacheDao.latestBlockHeight())
|
override suspend fun getLatestHeight(): BlockHeight? = runCatching {
|
||||||
|
BlockHeight.new(network, cacheDao.latestBlockHeight())
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
override suspend fun findCompactBlock(height: Int): CompactFormats.CompactBlock? =
|
override suspend fun findCompactBlock(height: BlockHeight): CompactFormats.CompactBlock? =
|
||||||
cacheDao.findCompactBlock(height)?.let { CompactFormats.CompactBlock.parseFrom(it) }
|
cacheDao.findCompactBlock(height.value)?.let { CompactFormats.CompactBlock.parseFrom(it) }
|
||||||
|
|
||||||
override suspend fun write(result: List<CompactFormats.CompactBlock>) =
|
override suspend fun write(result: Sequence<CompactFormats.CompactBlock>) =
|
||||||
cacheDao.insert(result.map { CompactBlockEntity(it.height.toInt(), it.toByteArray()) })
|
cacheDao.insert(result.map { CompactBlockEntity(it.height, it.toByteArray()) })
|
||||||
|
|
||||||
override suspend fun rewindTo(height: Int) =
|
override suspend fun rewindTo(height: BlockHeight) =
|
||||||
cacheDao.rewindTo(height)
|
cacheDao.rewindTo(height.value)
|
||||||
|
|
||||||
override suspend fun close() {
|
override suspend fun close() {
|
||||||
withContext(SdkDispatchers.DATABASE_IO) {
|
withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
@@ -43,10 +47,14 @@ class CompactBlockDbStore private constructor(
|
|||||||
* @param appContext the application context. This is used for creating the database.
|
* @param appContext the application context. This is used for creating the database.
|
||||||
* @property dbPath the absolute path to the database.
|
* @property dbPath the absolute path to the database.
|
||||||
*/
|
*/
|
||||||
fun new(appContext: Context, dbPath: String): CompactBlockDbStore {
|
fun new(
|
||||||
|
appContext: Context,
|
||||||
|
zcashNetwork: ZcashNetwork,
|
||||||
|
dbPath: String
|
||||||
|
): CompactBlockDbStore {
|
||||||
val cacheDb = createCompactBlockCacheDb(appContext.applicationContext, dbPath)
|
val cacheDb = createCompactBlockCacheDb(appContext.applicationContext, dbPath)
|
||||||
|
|
||||||
return CompactBlockDbStore(cacheDb)
|
return CompactBlockDbStore(zcashNetwork, cacheDb)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createCompactBlockCacheDb(
|
private fun createCompactBlockCacheDb(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import cash.z.ecc.android.sdk.internal.ext.retryUpTo
|
|||||||
import cash.z.ecc.android.sdk.internal.ext.tryWarn
|
import cash.z.ecc.android.sdk.internal.ext.tryWarn
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.wallet.sdk.rpc.Service
|
import cash.z.wallet.sdk.rpc.Service
|
||||||
import io.grpc.StatusRuntimeException
|
import io.grpc.StatusRuntimeException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -43,10 +44,9 @@ open class CompactBlockDownloader private constructor(val compactBlockStore: Com
|
|||||||
*
|
*
|
||||||
* @return the number of blocks that were returned in the results from the lightwalletService.
|
* @return the number of blocks that were returned in the results from the lightwalletService.
|
||||||
*/
|
*/
|
||||||
suspend fun downloadBlockRange(heightRange: IntRange): Int = withContext(IO) {
|
suspend fun downloadBlockRange(heightRange: ClosedRange<BlockHeight>): Int = withContext(IO) {
|
||||||
val result = lightWalletService.getBlockRange(heightRange)
|
val result = lightWalletService.getBlockRange(heightRange)
|
||||||
compactBlockStore.write(result)
|
compactBlockStore.write(result)
|
||||||
result.size
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,7 +55,7 @@ open class CompactBlockDownloader private constructor(val compactBlockStore: Com
|
|||||||
*
|
*
|
||||||
* @param height the height to which the data will rewind.
|
* @param height the height to which the data will rewind.
|
||||||
*/
|
*/
|
||||||
suspend fun rewindToHeight(height: Int) =
|
suspend fun rewindToHeight(height: BlockHeight) =
|
||||||
// TODO: cancel anything in flight
|
// TODO: cancel anything in flight
|
||||||
compactBlockStore.rewindTo(height)
|
compactBlockStore.rewindTo(height)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package cash.z.ecc.android.sdk.internal.block
|
package cash.z.ecc.android.sdk.internal.block
|
||||||
|
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.wallet.sdk.rpc.CompactFormats
|
import cash.z.wallet.sdk.rpc.CompactFormats
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,28 +12,29 @@ interface CompactBlockStore {
|
|||||||
*
|
*
|
||||||
* @return the latest block height.
|
* @return the latest block height.
|
||||||
*/
|
*/
|
||||||
suspend fun getLatestHeight(): Int
|
suspend fun getLatestHeight(): BlockHeight?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the compact block for the given height, if it exists.
|
* Fetch the compact block for the given height, if it exists.
|
||||||
*
|
*
|
||||||
* @return the compact block or null when it did not exist.
|
* @return the compact block or null when it did not exist.
|
||||||
*/
|
*/
|
||||||
suspend fun findCompactBlock(height: Int): CompactFormats.CompactBlock?
|
suspend fun findCompactBlock(height: BlockHeight): CompactFormats.CompactBlock?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write the given blocks to this store, which may be anything from an in-memory cache to a DB.
|
* Write the given blocks to this store, which may be anything from an in-memory cache to a DB.
|
||||||
*
|
*
|
||||||
* @param result the list of compact blocks to persist.
|
* @param result the list of compact blocks to persist.
|
||||||
|
* @return Number of blocks that were written.
|
||||||
*/
|
*/
|
||||||
suspend fun write(result: List<CompactFormats.CompactBlock>)
|
suspend fun write(result: Sequence<CompactFormats.CompactBlock>): Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove every block above the given height.
|
* Remove every block above the given height.
|
||||||
*
|
*
|
||||||
* @param height the target height to which to rewind.
|
* @param height the target height to which to rewind.
|
||||||
*/
|
*/
|
||||||
suspend fun rewindTo(height: Int)
|
suspend fun rewindTo(height: BlockHeight)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close any connections to the block store.
|
* Close any connections to the block store.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.room.Insert
|
|||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.Transaction
|
||||||
import cash.z.ecc.android.sdk.db.entity.CompactBlockEntity
|
import cash.z.ecc.android.sdk.db.entity.CompactBlockEntity
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -42,12 +43,24 @@ interface CompactBlockDao {
|
|||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insert(block: List<CompactBlockEntity>)
|
suspend fun insert(block: List<CompactBlockEntity>)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
suspend fun insert(blocks: Sequence<CompactBlockEntity>): Int {
|
||||||
|
var count = 0
|
||||||
|
|
||||||
|
blocks.forEach {
|
||||||
|
insert(it)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
@Query("DELETE FROM compactblocks WHERE height > :height")
|
@Query("DELETE FROM compactblocks WHERE height > :height")
|
||||||
suspend fun rewindTo(height: Int)
|
suspend fun rewindTo(height: Long)
|
||||||
|
|
||||||
@Query("SELECT MAX(height) FROM compactblocks")
|
@Query("SELECT MAX(height) FROM compactblocks")
|
||||||
suspend fun latestBlockHeight(): Int
|
suspend fun latestBlockHeight(): Long
|
||||||
|
|
||||||
@Query("SELECT data FROM compactblocks WHERE height = :height")
|
@Query("SELECT data FROM compactblocks WHERE height = :height")
|
||||||
suspend fun findCompactBlock(height: Int): ByteArray?
|
suspend fun findCompactBlock(height: Long): ByteArray?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,13 +198,13 @@ interface BlockDao {
|
|||||||
suspend fun count(): Int
|
suspend fun count(): Int
|
||||||
|
|
||||||
@Query("SELECT MAX(height) FROM blocks")
|
@Query("SELECT MAX(height) FROM blocks")
|
||||||
suspend fun lastScannedHeight(): Int
|
suspend fun lastScannedHeight(): Long
|
||||||
|
|
||||||
@Query("SELECT MIN(height) FROM blocks")
|
@Query("SELECT MIN(height) FROM blocks")
|
||||||
suspend fun firstScannedHeight(): Int
|
suspend fun firstScannedHeight(): Long
|
||||||
|
|
||||||
@Query("SELECT hash FROM BLOCKS WHERE height = :height")
|
@Query("SELECT hash FROM BLOCKS WHERE height = :height")
|
||||||
suspend fun findHashByHeight(height: Int): ByteArray?
|
suspend fun findHashByHeight(height: Long): ByteArray?
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -273,7 +273,7 @@ interface TransactionDao {
|
|||||||
LIMIT 1
|
LIMIT 1
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
suspend fun findMinedHeight(rawTransactionId: ByteArray): Int?
|
suspend fun findMinedHeight(rawTransactionId: ByteArray): Long?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query sent transactions that have been mined, sorted so the newest data is at the top.
|
* Query sent transactions that have been mined, sorted so the newest data is at the top.
|
||||||
@@ -418,7 +418,7 @@ interface TransactionDao {
|
|||||||
LIMIT :limit
|
LIMIT :limit
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
suspend fun findAllTransactionsByRange(blockRangeStart: Int, blockRangeEnd: Int = blockRangeStart, limit: Int = Int.MAX_VALUE): List<ConfirmedTransaction>
|
suspend fun findAllTransactionsByRange(blockRangeStart: Long, blockRangeEnd: Long = blockRangeStart, limit: Int = Int.MAX_VALUE): List<ConfirmedTransaction>
|
||||||
|
|
||||||
// Experimental: cleanup cancelled transactions
|
// Experimental: cleanup cancelled transactions
|
||||||
// This should probably be a rust call but there's not a lot of bandwidth for this
|
// This should probably be a rust call but there's not a lot of bandwidth for this
|
||||||
@@ -474,7 +474,7 @@ interface TransactionDao {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
suspend fun deleteExpired(lastHeight: Int): Int {
|
suspend fun deleteExpired(lastHeight: Long): Int {
|
||||||
var count = 0
|
var count = 0
|
||||||
findExpiredTxs(lastHeight).forEach { transactionId ->
|
findExpiredTxs(lastHeight).forEach { transactionId ->
|
||||||
if (removeInvalidOutboundTransaction(transactionId)) count++
|
if (removeInvalidOutboundTransaction(transactionId)) count++
|
||||||
@@ -537,5 +537,5 @@ interface TransactionDao {
|
|||||||
AND expiry_height < :lastheight
|
AND expiry_height < :lastheight
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
suspend fun findExpiredTxs(lastheight: Int): List<Long>
|
suspend fun findExpiredTxs(lastheight: Long): List<Long>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,10 +70,10 @@ interface PendingTransactionDao {
|
|||||||
suspend fun removeRawTransactionId(id: Long)
|
suspend fun removeRawTransactionId(id: Long)
|
||||||
|
|
||||||
@Query("UPDATE pending_transactions SET minedHeight = :minedHeight WHERE id = :id")
|
@Query("UPDATE pending_transactions SET minedHeight = :minedHeight WHERE id = :id")
|
||||||
suspend fun updateMinedHeight(id: Long, minedHeight: Int)
|
suspend fun updateMinedHeight(id: Long, minedHeight: Long)
|
||||||
|
|
||||||
@Query("UPDATE pending_transactions SET raw = :raw, rawTransactionId = :rawTransactionId, expiryHeight = :expiryHeight WHERE id = :id")
|
@Query("UPDATE pending_transactions SET raw = :raw, rawTransactionId = :rawTransactionId, expiryHeight = :expiryHeight WHERE id = :id")
|
||||||
suspend fun updateEncoding(id: Long, raw: ByteArray, rawTransactionId: ByteArray, expiryHeight: Int?)
|
suspend fun updateEncoding(id: Long, raw: ByteArray, rawTransactionId: ByteArray, expiryHeight: Long?)
|
||||||
|
|
||||||
@Query("UPDATE pending_transactions SET errorMessage = :errorMessage, errorCode = :errorCode WHERE id = :id")
|
@Query("UPDATE pending_transactions SET errorMessage = :errorMessage, errorCode = :errorCode WHERE id = :id")
|
||||||
suspend fun updateError(id: Long, errorMessage: String?, errorCode: Int?)
|
suspend fun updateError(id: Long, errorMessage: String?, errorCode: Int?)
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package cash.z.ecc.android.sdk.internal.model
|
||||||
|
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a checkpoint, which is used to speed sync times.
|
||||||
|
*
|
||||||
|
* @param height the height of the checkpoint.
|
||||||
|
* @param hash the hash of the block at [height].
|
||||||
|
* @param epochSeconds the time of the block at [height].
|
||||||
|
* @param tree the sapling tree corresponding to [height].
|
||||||
|
*/
|
||||||
|
internal data class Checkpoint(
|
||||||
|
val height: BlockHeight,
|
||||||
|
val hash: String,
|
||||||
|
// Note: this field does NOT match the name of the JSON, so will break with field-based JSON parsing
|
||||||
|
val epochSeconds: Long,
|
||||||
|
// Note: this field does NOT match the name of the JSON, so will break with field-based JSON parsing
|
||||||
|
val tree: String
|
||||||
|
) {
|
||||||
|
internal companion object
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
package cash.z.ecc.android.sdk.internal.service
|
package cash.z.ecc.android.sdk.internal.service
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import cash.z.ecc.android.sdk.R
|
|
||||||
import cash.z.ecc.android.sdk.annotation.OpenForTesting
|
import cash.z.ecc.android.sdk.annotation.OpenForTesting
|
||||||
import cash.z.ecc.android.sdk.exception.LightWalletException
|
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
|
||||||
import cash.z.wallet.sdk.rpc.CompactFormats
|
import cash.z.wallet.sdk.rpc.CompactFormats
|
||||||
import cash.z.wallet.sdk.rpc.CompactTxStreamerGrpc
|
import cash.z.wallet.sdk.rpc.CompactTxStreamerGrpc
|
||||||
import cash.z.wallet.sdk.rpc.Service
|
import cash.z.wallet.sdk.rpc.Service
|
||||||
@@ -15,69 +14,50 @@ import io.grpc.ConnectivityState
|
|||||||
import io.grpc.ManagedChannel
|
import io.grpc.ManagedChannel
|
||||||
import io.grpc.android.AndroidChannelBuilder
|
import io.grpc.android.AndroidChannelBuilder
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of LightwalletService using gRPC for requests to lightwalletd.
|
* Implementation of LightwalletService using gRPC for requests to lightwalletd.
|
||||||
*
|
*
|
||||||
* @property channel the channel to use for communicating with the lightwalletd server.
|
* @property channel the channel to use for communicating with the lightwalletd server.
|
||||||
* @property singleRequestTimeoutSec the timeout to use for non-streaming requests. When a new stub
|
* @property singleRequestTimeout the timeout to use for non-streaming requests. When a new stub
|
||||||
* is created, it will use a deadline that is after the given duration from now.
|
* is created, it will use a deadline that is after the given duration from now.
|
||||||
* @property streamingRequestTimeoutSec the timeout to use for streaming requests. When a new stub
|
* @property streamingRequestTimeout the timeout to use for streaming requests. When a new stub
|
||||||
* is created for streaming requests, it will use a deadline that is after the given duration from
|
* is created for streaming requests, it will use a deadline that is after the given duration from
|
||||||
* now.
|
* now.
|
||||||
*/
|
*/
|
||||||
@OpenForTesting
|
@OpenForTesting
|
||||||
class LightWalletGrpcService private constructor(
|
class LightWalletGrpcService private constructor(
|
||||||
|
context: Context,
|
||||||
|
private val lightWalletEndpoint: LightWalletEndpoint,
|
||||||
var channel: ManagedChannel,
|
var channel: ManagedChannel,
|
||||||
private val singleRequestTimeoutSec: Long = 10L,
|
private val singleRequestTimeout: Duration = 10.seconds,
|
||||||
private val streamingRequestTimeoutSec: Long = 90L
|
private val streamingRequestTimeout: Duration = 90.seconds
|
||||||
) : LightWalletService {
|
) : LightWalletService {
|
||||||
|
|
||||||
lateinit var connectionInfo: ConnectionInfo
|
private val applicationContext = context.applicationContext
|
||||||
|
|
||||||
constructor(
|
|
||||||
appContext: Context,
|
|
||||||
network: ZcashNetwork,
|
|
||||||
usePlaintext: Boolean =
|
|
||||||
appContext.resources.getBoolean(R.bool.lightwalletd_allow_very_insecure_connections)
|
|
||||||
) : this(appContext, network.defaultHost, network.defaultPort, true)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct an instance that corresponds to the given host and port.
|
|
||||||
*
|
|
||||||
* @param appContext the application context used to check whether TLS is required by this build
|
|
||||||
* flavor.
|
|
||||||
* @param host the host of the server to use.
|
|
||||||
* @param port the port of the server to use.
|
|
||||||
* @param usePlaintext whether to use TLS or plaintext for requests. Plaintext is dangerous so
|
|
||||||
* it requires jumping through a few more hoops.
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
appContext: Context,
|
|
||||||
host: String,
|
|
||||||
port: Int = ZcashNetwork.Mainnet.defaultPort,
|
|
||||||
usePlaintext: Boolean =
|
|
||||||
appContext.resources.getBoolean(R.bool.lightwalletd_allow_very_insecure_connections)
|
|
||||||
) : this(createDefaultChannel(appContext, host, port, true)) {
|
|
||||||
connectionInfo = ConnectionInfo(appContext.applicationContext, host, port, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* LightWalletService implementation */
|
/* LightWalletService implementation */
|
||||||
|
|
||||||
override fun getBlockRange(heightRange: IntRange): List<CompactFormats.CompactBlock> {
|
override fun getBlockRange(heightRange: ClosedRange<BlockHeight>): Sequence<CompactFormats.CompactBlock> {
|
||||||
if (heightRange.isEmpty()) return listOf()
|
if (heightRange.isEmpty()) {
|
||||||
|
return emptySequence()
|
||||||
|
}
|
||||||
|
|
||||||
return requireChannel().createStub(streamingRequestTimeoutSec)
|
return requireChannel().createStub(streamingRequestTimeout)
|
||||||
.getBlockRange(heightRange.toBlockRange()).toList()
|
.getBlockRange(heightRange.toBlockRange()).iterator().asSequence()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLatestBlockHeight(): Int {
|
override fun getLatestBlockHeight(): BlockHeight {
|
||||||
return requireChannel().createStub(singleRequestTimeoutSec)
|
return BlockHeight(
|
||||||
.getLatestBlock(Service.ChainSpec.newBuilder().build()).height.toInt()
|
requireChannel().createStub(singleRequestTimeout)
|
||||||
|
.getLatestBlock(Service.ChainSpec.newBuilder().build()).height
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getServerInfo(): Service.LightdInfo {
|
override fun getServerInfo(): Service.LightdInfo {
|
||||||
return requireChannel().createStub(singleRequestTimeoutSec)
|
return requireChannel().createStub(singleRequestTimeout)
|
||||||
.getLightdInfo(Service.Empty.newBuilder().build())
|
.getLightdInfo(Service.Empty.newBuilder().build())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,23 +89,20 @@ class LightWalletGrpcService private constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
override fun fetchUtxos(
|
override fun fetchUtxos(
|
||||||
tAddress: String,
|
tAddress: String,
|
||||||
startHeight: Int
|
startHeight: BlockHeight
|
||||||
): List<Service.GetAddressUtxosReply> {
|
): List<Service.GetAddressUtxosReply> {
|
||||||
val result = requireChannel().createStub().getAddressUtxos(
|
val result = requireChannel().createStub().getAddressUtxos(
|
||||||
Service.GetAddressUtxosArg.newBuilder().setAddress(tAddress)
|
Service.GetAddressUtxosArg.newBuilder().setAddress(tAddress)
|
||||||
.setStartHeight(startHeight.toLong()).build()
|
.setStartHeight(startHeight.value).build()
|
||||||
)
|
)
|
||||||
return result.addressUtxosList
|
return result.addressUtxosList
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
override fun getTAddressTransactions(
|
override fun getTAddressTransactions(
|
||||||
tAddress: String,
|
tAddress: String,
|
||||||
blockHeightRange: IntRange
|
blockHeightRange: ClosedRange<BlockHeight>
|
||||||
): List<Service.RawTransaction> {
|
): List<Service.RawTransaction> {
|
||||||
if (blockHeightRange.isEmpty() || tAddress.isBlank()) return listOf()
|
if (blockHeightRange.isEmpty() || tAddress.isBlank()) return listOf()
|
||||||
|
|
||||||
@@ -135,75 +112,38 @@ class LightWalletGrpcService private constructor(
|
|||||||
)
|
)
|
||||||
return result.toList()
|
return result.toList()
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
override fun reconnect() {
|
override fun reconnect() {
|
||||||
twig(
|
twig("closing existing channel and then reconnecting")
|
||||||
"closing existing channel and then reconnecting to ${connectionInfo.host}:" +
|
|
||||||
"${connectionInfo.port}?usePlaintext=${connectionInfo.usePlaintext}"
|
|
||||||
)
|
|
||||||
channel.shutdown()
|
channel.shutdown()
|
||||||
channel = createDefaultChannel(
|
channel = createDefaultChannel(applicationContext, lightWalletEndpoint)
|
||||||
connectionInfo.appContext,
|
|
||||||
connectionInfo.host,
|
|
||||||
connectionInfo.port,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// test code
|
// test code
|
||||||
var stateCount = 0
|
internal var stateCount = 0
|
||||||
var state: ConnectivityState? = null
|
internal var state: ConnectivityState? = null
|
||||||
private fun requireChannel(): ManagedChannel {
|
private fun requireChannel(): ManagedChannel {
|
||||||
state = channel.getState(false).let { new ->
|
state = channel.getState(false).let { new ->
|
||||||
if (state == new) stateCount++ else stateCount = 0
|
if (state == new) stateCount++ else stateCount = 0
|
||||||
new
|
new
|
||||||
}
|
}
|
||||||
channel.resetConnectBackoff()
|
channel.resetConnectBackoff()
|
||||||
twig("getting channel isShutdown: ${channel.isShutdown} isTerminated: ${channel.isTerminated} getState: $state stateCount: $stateCount", -1)
|
twig(
|
||||||
|
"getting channel isShutdown: ${channel.isShutdown} " +
|
||||||
|
"isTerminated: ${channel.isTerminated} " +
|
||||||
|
"getState: $state stateCount: $stateCount",
|
||||||
|
-1
|
||||||
|
)
|
||||||
return channel
|
return channel
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Utilities
|
|
||||||
//
|
|
||||||
|
|
||||||
private fun Channel.createStub(timeoutSec: Long = 60L) = CompactTxStreamerGrpc
|
|
||||||
.newBlockingStub(this)
|
|
||||||
.withDeadlineAfter(timeoutSec, TimeUnit.SECONDS)
|
|
||||||
|
|
||||||
private inline fun Int.toBlockHeight(): Service.BlockID =
|
|
||||||
Service.BlockID.newBuilder().setHeight(this.toLong()).build()
|
|
||||||
|
|
||||||
private inline fun IntRange.toBlockRange(): Service.BlockRange =
|
|
||||||
Service.BlockRange.newBuilder()
|
|
||||||
.setStart(first.toBlockHeight())
|
|
||||||
.setEnd(last.toBlockHeight())
|
|
||||||
.build()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function effectively parses streaming responses. Each call to next(), on the iterators
|
|
||||||
* returned from grpc, triggers a network call.
|
|
||||||
*/
|
|
||||||
private fun <T> Iterator<T>.toList(): List<T> =
|
|
||||||
mutableListOf<T>().apply {
|
|
||||||
while (hasNext()) {
|
|
||||||
this@apply += next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class ConnectionInfo(
|
|
||||||
val appContext: Context,
|
|
||||||
val host: String,
|
|
||||||
val port: Int,
|
|
||||||
val usePlaintext: Boolean
|
|
||||||
) {
|
|
||||||
override fun toString(): String {
|
|
||||||
return "$host:$port?usePlaintext=true"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
fun new(context: Context, lightWalletEndpoint: LightWalletEndpoint): LightWalletGrpcService {
|
||||||
|
val channel = createDefaultChannel(context, lightWalletEndpoint)
|
||||||
|
|
||||||
|
return LightWalletGrpcService(context, lightWalletEndpoint, channel)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience function for creating the default channel to be used for all connections. It
|
* Convenience function for creating the default channel to be used for all connections. It
|
||||||
* is important that this channel can handle transitioning from WiFi to Cellular connections
|
* is important that this channel can handle transitioning from WiFi to Cellular connections
|
||||||
@@ -211,27 +151,53 @@ class LightWalletGrpcService private constructor(
|
|||||||
*/
|
*/
|
||||||
fun createDefaultChannel(
|
fun createDefaultChannel(
|
||||||
appContext: Context,
|
appContext: Context,
|
||||||
host: String,
|
lightWalletEndpoint: LightWalletEndpoint
|
||||||
port: Int,
|
|
||||||
usePlaintext: Boolean
|
|
||||||
): ManagedChannel {
|
): ManagedChannel {
|
||||||
twig("Creating channel that will connect to $host:$port?usePlaintext=$usePlaintext")
|
twig(
|
||||||
|
"Creating channel that will connect to " +
|
||||||
|
"${lightWalletEndpoint.host}:${lightWalletEndpoint.port}" +
|
||||||
|
"/?usePlaintext=${!lightWalletEndpoint.isSecure}"
|
||||||
|
)
|
||||||
return AndroidChannelBuilder
|
return AndroidChannelBuilder
|
||||||
.forAddress(host, port)
|
.forAddress(lightWalletEndpoint.host, lightWalletEndpoint.port)
|
||||||
.context(appContext)
|
.context(appContext)
|
||||||
.enableFullStreamDecompression()
|
.enableFullStreamDecompression()
|
||||||
.apply {
|
.apply {
|
||||||
if (usePlaintext) {
|
usePlaintext()
|
||||||
if (!appContext.resources.getBoolean(
|
/*
|
||||||
R.bool.lightwalletd_allow_very_insecure_connections
|
if (lightWalletEndpoint.isSecure) {
|
||||||
)
|
|
||||||
) throw LightWalletException.InsecureConnection
|
|
||||||
usePlaintext()
|
|
||||||
} else {
|
|
||||||
useTransportSecurity()
|
useTransportSecurity()
|
||||||
|
} else {
|
||||||
|
twig("WARNING: Using insecure channel")
|
||||||
|
usePlaintext()
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Channel.createStub(timeoutSec: Duration = 60.seconds) = CompactTxStreamerGrpc
|
||||||
|
.newBlockingStub(this)
|
||||||
|
.withDeadlineAfter(timeoutSec.inWholeSeconds, TimeUnit.SECONDS)
|
||||||
|
|
||||||
|
private fun BlockHeight.toBlockHeight(): Service.BlockID =
|
||||||
|
Service.BlockID.newBuilder().setHeight(value).build()
|
||||||
|
|
||||||
|
private fun ClosedRange<BlockHeight>.toBlockRange(): Service.BlockRange =
|
||||||
|
Service.BlockRange.newBuilder()
|
||||||
|
.setStart(start.toBlockHeight())
|
||||||
|
.setEnd(endInclusive.toBlockHeight())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function effectively parses streaming responses. Each call to next(), on the iterators
|
||||||
|
* returned from grpc, triggers a network call.
|
||||||
|
*/
|
||||||
|
private fun <T> Iterator<T>.toList(): List<T> =
|
||||||
|
mutableListOf<T>().apply {
|
||||||
|
while (hasNext()) {
|
||||||
|
this@apply += next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package cash.z.ecc.android.sdk.internal.service
|
package cash.z.ecc.android.sdk.internal.service
|
||||||
|
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.wallet.sdk.rpc.CompactFormats
|
import cash.z.wallet.sdk.rpc.CompactFormats
|
||||||
import cash.z.wallet.sdk.rpc.Service
|
import cash.z.wallet.sdk.rpc.Service
|
||||||
|
|
||||||
@@ -24,9 +25,7 @@ interface LightWalletService {
|
|||||||
*
|
*
|
||||||
* @return the UTXOs for the given address from the startHeight.
|
* @return the UTXOs for the given address from the startHeight.
|
||||||
*/
|
*/
|
||||||
/* THIS IS NOT SUPPORT IN HUSH LIGHTWALLETD
|
fun fetchUtxos(tAddress: String, startHeight: BlockHeight): List<Service.GetAddressUtxosReply>
|
||||||
fun fetchUtxos(tAddress: String, startHeight: Int): List<Service.GetAddressUtxosReply>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the given range of blocks.
|
* Return the given range of blocks.
|
||||||
@@ -37,14 +36,14 @@ interface LightWalletService {
|
|||||||
* @return a list of compact blocks for the given range
|
* @return a list of compact blocks for the given range
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
fun getBlockRange(heightRange: IntRange): List<CompactFormats.CompactBlock>
|
fun getBlockRange(heightRange: ClosedRange<BlockHeight>): Sequence<CompactFormats.CompactBlock>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the latest block height known to the service.
|
* Return the latest block height known to the service.
|
||||||
*
|
*
|
||||||
* @return the latest block height known to the service.
|
* @return the latest block height known to the service.
|
||||||
*/
|
*/
|
||||||
fun getLatestBlockHeight(): Int
|
fun getLatestBlockHeight(): BlockHeight
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return basic information about the server such as:
|
* Return basic information about the server such as:
|
||||||
@@ -72,9 +71,7 @@ interface LightWalletService {
|
|||||||
*
|
*
|
||||||
* @return a list of transactions that correspond to the given address for the given range.
|
* @return a list of transactions that correspond to the given address for the given range.
|
||||||
*/
|
*/
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
fun getTAddressTransactions(tAddress: String, blockHeightRange: ClosedRange<BlockHeight>): List<Service.RawTransaction>
|
||||||
fun getTAddressTransactions(tAddress: String, blockHeightRange: IntRange): List<Service.RawTransaction>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reconnect to the same or a different server. This is useful when the connection is
|
* Reconnect to the same or a different server. This is useful when the connection is
|
||||||
|
|||||||
@@ -12,12 +12,13 @@ import cash.z.ecc.android.sdk.internal.db.DerivedDataDb
|
|||||||
import cash.z.ecc.android.sdk.internal.ext.android.toFlowPagedList
|
import cash.z.ecc.android.sdk.internal.ext.android.toFlowPagedList
|
||||||
import cash.z.ecc.android.sdk.internal.ext.android.toRefreshable
|
import cash.z.ecc.android.sdk.internal.ext.android.toRefreshable
|
||||||
import cash.z.ecc.android.sdk.internal.ext.tryWarn
|
import cash.z.ecc.android.sdk.internal.ext.tryWarn
|
||||||
|
import cash.z.ecc.android.sdk.internal.model.Checkpoint
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
import cash.z.ecc.android.sdk.jni.RustBackend
|
import cash.z.ecc.android.sdk.jni.RustBackend
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
|
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
|
||||||
import cash.z.ecc.android.sdk.type.WalletBirthday
|
|
||||||
import kotlinx.coroutines.flow.emitAll
|
import kotlinx.coroutines.flow.emitAll
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@@ -28,7 +29,8 @@ import kotlinx.coroutines.withContext
|
|||||||
*
|
*
|
||||||
* @param pageSize transactions per page. This influences pre-fetch and memory configuration.
|
* @param pageSize transactions per page. This influences pre-fetch and memory configuration.
|
||||||
*/
|
*/
|
||||||
class PagedTransactionRepository private constructor(
|
internal class PagedTransactionRepository private constructor(
|
||||||
|
private val zcashNetwork: ZcashNetwork,
|
||||||
private val db: DerivedDataDb,
|
private val db: DerivedDataDb,
|
||||||
private val pageSize: Int
|
private val pageSize: Int
|
||||||
) : TransactionRepository {
|
) : TransactionRepository {
|
||||||
@@ -62,20 +64,20 @@ class PagedTransactionRepository private constructor(
|
|||||||
|
|
||||||
override fun invalidate() = allTransactionsFactory.refresh()
|
override fun invalidate() = allTransactionsFactory.refresh()
|
||||||
|
|
||||||
override suspend fun lastScannedHeight() = blocks.lastScannedHeight()
|
override suspend fun lastScannedHeight() = BlockHeight.new(zcashNetwork, blocks.lastScannedHeight())
|
||||||
|
|
||||||
override suspend fun firstScannedHeight() = blocks.firstScannedHeight()
|
override suspend fun firstScannedHeight() = BlockHeight.new(zcashNetwork, blocks.firstScannedHeight())
|
||||||
|
|
||||||
override suspend fun isInitialized() = blocks.count() > 0
|
override suspend fun isInitialized() = blocks.count() > 0
|
||||||
|
|
||||||
override suspend fun findEncodedTransactionById(txId: Long) =
|
override suspend fun findEncodedTransactionById(txId: Long) =
|
||||||
transactions.findEncodedTransactionById(txId)
|
transactions.findEncodedTransactionById(txId)
|
||||||
|
|
||||||
override suspend fun findNewTransactions(blockHeightRange: IntRange): List<ConfirmedTransaction> =
|
override suspend fun findNewTransactions(blockHeightRange: ClosedRange<BlockHeight>): List<ConfirmedTransaction> =
|
||||||
transactions.findAllTransactionsByRange(blockHeightRange.first, blockHeightRange.last)
|
transactions.findAllTransactionsByRange(blockHeightRange.start.value, blockHeightRange.endInclusive.value)
|
||||||
|
|
||||||
override suspend fun findMinedHeight(rawTransactionId: ByteArray) =
|
override suspend fun findMinedHeight(rawTransactionId: ByteArray) =
|
||||||
transactions.findMinedHeight(rawTransactionId)
|
transactions.findMinedHeight(rawTransactionId)?.let { BlockHeight.new(zcashNetwork, it) }
|
||||||
|
|
||||||
override suspend fun findMatchingTransactionId(rawTransactionId: ByteArray): Long? =
|
override suspend fun findMatchingTransactionId(rawTransactionId: ByteArray): Long? =
|
||||||
transactions.findMatchingTransactionId(rawTransactionId)
|
transactions.findMatchingTransactionId(rawTransactionId)
|
||||||
@@ -84,8 +86,8 @@ class PagedTransactionRepository private constructor(
|
|||||||
transactions.cleanupCancelledTx(rawTransactionId)
|
transactions.cleanupCancelledTx(rawTransactionId)
|
||||||
|
|
||||||
// let expired transactions linger in the UI for a little while
|
// let expired transactions linger in the UI for a little while
|
||||||
override suspend fun deleteExpired(lastScannedHeight: Int) =
|
override suspend fun deleteExpired(lastScannedHeight: BlockHeight) =
|
||||||
transactions.deleteExpired(lastScannedHeight - (ZcashSdk.EXPIRY_OFFSET / 2))
|
transactions.deleteExpired(lastScannedHeight.value - (ZcashSdk.EXPIRY_OFFSET / 2))
|
||||||
|
|
||||||
override suspend fun count() = transactions.count()
|
override suspend fun count() = transactions.count()
|
||||||
|
|
||||||
@@ -103,17 +105,18 @@ class PagedTransactionRepository private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: begin converting these into Data Access API. For now, just collect the desired operations and iterate/refactor, later
|
// TODO: begin converting these into Data Access API. For now, just collect the desired operations and iterate/refactor, later
|
||||||
suspend fun findBlockHash(height: Int): ByteArray? = blocks.findHashByHeight(height)
|
suspend fun findBlockHash(height: BlockHeight): ByteArray? = blocks.findHashByHeight(height.value)
|
||||||
suspend fun getTransactionCount(): Int = transactions.count()
|
suspend fun getTransactionCount(): Int = transactions.count()
|
||||||
|
|
||||||
// TODO: convert this into a wallet repository rather than "transaction repository"
|
// TODO: convert this into a wallet repository rather than "transaction repository"
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
suspend fun new(
|
internal suspend fun new(
|
||||||
appContext: Context,
|
appContext: Context,
|
||||||
|
zcashNetwork: ZcashNetwork,
|
||||||
pageSize: Int = 10,
|
pageSize: Int = 10,
|
||||||
rustBackend: RustBackend,
|
rustBackend: RustBackend,
|
||||||
birthday: WalletBirthday,
|
birthday: Checkpoint,
|
||||||
viewingKeys: List<UnifiedViewingKey>,
|
viewingKeys: List<UnifiedViewingKey>,
|
||||||
overwriteVks: Boolean = false
|
overwriteVks: Boolean = false
|
||||||
): PagedTransactionRepository {
|
): PagedTransactionRepository {
|
||||||
@@ -122,7 +125,7 @@ class PagedTransactionRepository private constructor(
|
|||||||
val db = buildDatabase(appContext.applicationContext, rustBackend.pathDataDb)
|
val db = buildDatabase(appContext.applicationContext, rustBackend.pathDataDb)
|
||||||
applyKeyMigrations(rustBackend, overwriteVks, viewingKeys)
|
applyKeyMigrations(rustBackend, overwriteVks, viewingKeys)
|
||||||
|
|
||||||
return PagedTransactionRepository(db, pageSize)
|
return PagedTransactionRepository(zcashNetwork, db, pageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,7 +158,7 @@ class PagedTransactionRepository private constructor(
|
|||||||
*/
|
*/
|
||||||
private suspend fun initMissingDatabases(
|
private suspend fun initMissingDatabases(
|
||||||
rustBackend: RustBackend,
|
rustBackend: RustBackend,
|
||||||
birthday: WalletBirthday,
|
birthday: Checkpoint,
|
||||||
viewingKeys: List<UnifiedViewingKey>
|
viewingKeys: List<UnifiedViewingKey>
|
||||||
) {
|
) {
|
||||||
maybeCreateDataDb(rustBackend)
|
maybeCreateDataDb(rustBackend)
|
||||||
@@ -178,20 +181,15 @@ class PagedTransactionRepository private constructor(
|
|||||||
*/
|
*/
|
||||||
private suspend fun maybeInitBlocksTable(
|
private suspend fun maybeInitBlocksTable(
|
||||||
rustBackend: RustBackend,
|
rustBackend: RustBackend,
|
||||||
birthday: WalletBirthday
|
checkpoint: Checkpoint
|
||||||
) {
|
) {
|
||||||
// TODO: consider converting these to typed exceptions in the welding layer
|
// TODO: consider converting these to typed exceptions in the welding layer
|
||||||
tryWarn(
|
tryWarn(
|
||||||
"Warning: did not initialize the blocks table. It probably was already initialized.",
|
"Warning: did not initialize the blocks table. It probably was already initialized.",
|
||||||
ifContains = "table is not empty"
|
ifContains = "table is not empty"
|
||||||
) {
|
) {
|
||||||
rustBackend.initBlocksTable(
|
rustBackend.initBlocksTable(checkpoint)
|
||||||
birthday.height,
|
twig("seeded the database with sapling tree at height ${checkpoint.height}")
|
||||||
birthday.hash,
|
|
||||||
birthday.time,
|
|
||||||
birthday.tree
|
|
||||||
)
|
|
||||||
twig("seeded the database with sapling tree at height ${birthday.height}")
|
|
||||||
}
|
}
|
||||||
twig("database file: ${rustBackend.pathDataDb}")
|
twig("database file: ${rustBackend.pathDataDb}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import cash.z.ecc.android.sdk.internal.db.PendingTransactionDao
|
|||||||
import cash.z.ecc.android.sdk.internal.db.PendingTransactionDb
|
import cash.z.ecc.android.sdk.internal.db.PendingTransactionDb
|
||||||
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
import cash.z.ecc.android.sdk.internal.service.LightWalletService
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
@@ -98,10 +99,10 @@ class PersistentTransactionManager(
|
|||||||
tx
|
tx
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun applyMinedHeight(pendingTx: PendingTransaction, minedHeight: Int) {
|
override suspend fun applyMinedHeight(pendingTx: PendingTransaction, minedHeight: BlockHeight) {
|
||||||
twig("a pending transaction has been mined!")
|
twig("a pending transaction has been mined!")
|
||||||
safeUpdate("updating mined height for pending tx id: ${pendingTx.id} to $minedHeight") {
|
safeUpdate("updating mined height for pending tx id: ${pendingTx.id} to $minedHeight") {
|
||||||
updateMinedHeight(pendingTx.id, minedHeight)
|
updateMinedHeight(pendingTx.id, minedHeight.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +116,7 @@ class PersistentTransactionManager(
|
|||||||
twig("beginning to encode transaction with : $encoder")
|
twig("beginning to encode transaction with : $encoder")
|
||||||
val encodedTx = encoder.createTransaction(
|
val encodedTx = encoder.createTransaction(
|
||||||
spendingKey,
|
spendingKey,
|
||||||
tx.value,
|
tx.valueZatoshi,
|
||||||
tx.toAddress,
|
tx.toAddress,
|
||||||
tx.memo,
|
tx.memo,
|
||||||
tx.accountIndex
|
tx.accountIndex
|
||||||
@@ -230,10 +231,8 @@ class PersistentTransactionManager(
|
|||||||
override suspend fun isValidShieldedAddress(address: String) =
|
override suspend fun isValidShieldedAddress(address: String) =
|
||||||
encoder.isValidShieldedAddress(address)
|
encoder.isValidShieldedAddress(address)
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
override suspend fun isValidTransparentAddress(address: String) =
|
override suspend fun isValidTransparentAddress(address: String) =
|
||||||
encoder.isValidTransparentAddress(address)
|
encoder.isValidTransparentAddress(address)
|
||||||
*/
|
|
||||||
|
|
||||||
override suspend fun cancel(pendingId: Long): Boolean {
|
override suspend fun cancel(pendingId: Long): Boolean {
|
||||||
return pendingTransactionDao {
|
return pendingTransactionDao {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package cash.z.ecc.android.sdk.internal.transaction
|
package cash.z.ecc.android.sdk.internal.transaction
|
||||||
|
|
||||||
import cash.z.ecc.android.sdk.db.entity.EncodedTransaction
|
import cash.z.ecc.android.sdk.db.entity.EncodedTransaction
|
||||||
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
|
||||||
interface TransactionEncoder {
|
interface TransactionEncoder {
|
||||||
/**
|
/**
|
||||||
@@ -9,7 +10,7 @@ interface TransactionEncoder {
|
|||||||
* exception ourselves (rather than using double-bangs for things).
|
* exception ourselves (rather than using double-bangs for things).
|
||||||
*
|
*
|
||||||
* @param spendingKey the key associated with the notes that will be spent.
|
* @param spendingKey the key associated with the notes that will be spent.
|
||||||
* @param zatoshi the amount of zatoshi to send.
|
* @param amount the amount of zatoshi to send.
|
||||||
* @param toAddress the recipient's address.
|
* @param toAddress the recipient's address.
|
||||||
* @param memo the optional memo to include as part of the transaction.
|
* @param memo the optional memo to include as part of the transaction.
|
||||||
* @param fromAccountIndex the optional account id to use. By default, the 1st account is used.
|
* @param fromAccountIndex the optional account id to use. By default, the 1st account is used.
|
||||||
@@ -18,7 +19,7 @@ interface TransactionEncoder {
|
|||||||
*/
|
*/
|
||||||
suspend fun createTransaction(
|
suspend fun createTransaction(
|
||||||
spendingKey: String,
|
spendingKey: String,
|
||||||
zatoshi: Long,
|
amount: Zatoshi,
|
||||||
toAddress: String,
|
toAddress: String,
|
||||||
memo: ByteArray? = byteArrayOf(),
|
memo: ByteArray? = byteArrayOf(),
|
||||||
fromAccountIndex: Int = 0
|
fromAccountIndex: Int = 0
|
||||||
@@ -48,9 +49,7 @@ interface TransactionEncoder {
|
|||||||
*
|
*
|
||||||
* @return true when the given address is a valid t-addr
|
* @return true when the given address is a valid t-addr
|
||||||
*/
|
*/
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
suspend fun isValidTransparentAddress(address: String): Boolean
|
suspend fun isValidTransparentAddress(address: String): Boolean
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the consensus branch that the encoder is using when making transactions.
|
* Return the consensus branch that the encoder is using when making transactions.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package cash.z.ecc.android.sdk.internal.transaction
|
package cash.z.ecc.android.sdk.internal.transaction
|
||||||
|
|
||||||
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ interface OutboundTransactionManager {
|
|||||||
* @param minedHeight the height at which the given transaction was mined, according to the data
|
* @param minedHeight the height at which the given transaction was mined, according to the data
|
||||||
* that has been processed from the blockchain.
|
* that has been processed from the blockchain.
|
||||||
*/
|
*/
|
||||||
suspend fun applyMinedHeight(pendingTx: PendingTransaction, minedHeight: Int)
|
suspend fun applyMinedHeight(pendingTx: PendingTransaction, minedHeight: BlockHeight)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a flow of information about the given id where a new pending transaction is emitted
|
* Generate a flow of information about the given id where a new pending transaction is emitted
|
||||||
@@ -94,9 +95,7 @@ interface OutboundTransactionManager {
|
|||||||
*
|
*
|
||||||
* @return true when the given address is a valid z-addr.
|
* @return true when the given address is a valid z-addr.
|
||||||
*/
|
*/
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
suspend fun isValidTransparentAddress(address: String): Boolean
|
suspend fun isValidTransparentAddress(address: String): Boolean
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to cancel a transaction.
|
* Attempt to cancel a transaction.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cash.z.ecc.android.sdk.internal.transaction
|
|||||||
|
|
||||||
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||||
import cash.z.ecc.android.sdk.db.entity.EncodedTransaction
|
import cash.z.ecc.android.sdk.db.entity.EncodedTransaction
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.ecc.android.sdk.type.UnifiedAddressAccount
|
import cash.z.ecc.android.sdk.type.UnifiedAddressAccount
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@@ -15,14 +16,14 @@ interface TransactionRepository {
|
|||||||
*
|
*
|
||||||
* @return the last height scanned by this repository.
|
* @return the last height scanned by this repository.
|
||||||
*/
|
*/
|
||||||
suspend fun lastScannedHeight(): Int
|
suspend fun lastScannedHeight(): BlockHeight
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The height of the first block in this repository. This is typically the checkpoint that was
|
* The height of the first block in this repository. This is typically the checkpoint that was
|
||||||
* used to initialize this wallet. If we overwrite this block, it breaks our ability to spend
|
* used to initialize this wallet. If we overwrite this block, it breaks our ability to spend
|
||||||
* funds.
|
* funds.
|
||||||
*/
|
*/
|
||||||
suspend fun firstScannedHeight(): Int
|
suspend fun firstScannedHeight(): BlockHeight
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true when this repository has been initialized and seeded with the initial checkpoint.
|
* Returns true when this repository has been initialized and seeded with the initial checkpoint.
|
||||||
@@ -51,7 +52,7 @@ interface TransactionRepository {
|
|||||||
*
|
*
|
||||||
* @return a list of transactions that were mined in the given range, inclusive.
|
* @return a list of transactions that were mined in the given range, inclusive.
|
||||||
*/
|
*/
|
||||||
suspend fun findNewTransactions(blockHeightRange: IntRange): List<ConfirmedTransaction>
|
suspend fun findNewTransactions(blockHeightRange: ClosedRange<BlockHeight>): List<ConfirmedTransaction>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the mined height that matches the given raw tx_id in bytes. This is useful for matching
|
* Find the mined height that matches the given raw tx_id in bytes. This is useful for matching
|
||||||
@@ -61,7 +62,7 @@ interface TransactionRepository {
|
|||||||
*
|
*
|
||||||
* @return the mined height of the given transaction, if it is known to this wallet.
|
* @return the mined height of the given transaction, if it is known to this wallet.
|
||||||
*/
|
*/
|
||||||
suspend fun findMinedHeight(rawTransactionId: ByteArray): Int?
|
suspend fun findMinedHeight(rawTransactionId: ByteArray): BlockHeight?
|
||||||
|
|
||||||
suspend fun findMatchingTransactionId(rawTransactionId: ByteArray): Long?
|
suspend fun findMatchingTransactionId(rawTransactionId: ByteArray): Long?
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ interface TransactionRepository {
|
|||||||
*/
|
*/
|
||||||
suspend fun cleanupCancelledTx(rawTransactionId: ByteArray): Boolean
|
suspend fun cleanupCancelledTx(rawTransactionId: ByteArray): Boolean
|
||||||
|
|
||||||
suspend fun deleteExpired(lastScannedHeight: Int): Int
|
suspend fun deleteExpired(lastScannedHeight: BlockHeight): Int
|
||||||
|
|
||||||
suspend fun count(): Int
|
suspend fun count(): Int
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import cash.z.ecc.android.sdk.internal.twig
|
|||||||
import cash.z.ecc.android.sdk.internal.twigTask
|
import cash.z.ecc.android.sdk.internal.twigTask
|
||||||
import cash.z.ecc.android.sdk.jni.RustBackend
|
import cash.z.ecc.android.sdk.jni.RustBackend
|
||||||
import cash.z.ecc.android.sdk.jni.RustBackendWelding
|
import cash.z.ecc.android.sdk.jni.RustBackendWelding
|
||||||
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class responsible for encoding a transaction in a consistent way. This bridges the gap by
|
* Class responsible for encoding a transaction in a consistent way. This bridges the gap by
|
||||||
@@ -18,7 +19,7 @@ import cash.z.ecc.android.sdk.jni.RustBackendWelding
|
|||||||
* @property repository the repository that stores information about the transactions being created
|
* @property repository the repository that stores information about the transactions being created
|
||||||
* such as the raw bytes and raw txId.
|
* such as the raw bytes and raw txId.
|
||||||
*/
|
*/
|
||||||
class WalletTransactionEncoder(
|
internal class WalletTransactionEncoder(
|
||||||
private val rustBackend: RustBackendWelding,
|
private val rustBackend: RustBackendWelding,
|
||||||
private val repository: TransactionRepository
|
private val repository: TransactionRepository
|
||||||
) : TransactionEncoder {
|
) : TransactionEncoder {
|
||||||
@@ -29,7 +30,7 @@ class WalletTransactionEncoder(
|
|||||||
* exception ourselves (rather than using double-bangs for things).
|
* exception ourselves (rather than using double-bangs for things).
|
||||||
*
|
*
|
||||||
* @param spendingKey the key associated with the notes that will be spent.
|
* @param spendingKey the key associated with the notes that will be spent.
|
||||||
* @param zatoshi the amount of zatoshi to send.
|
* @param amount the amount of zatoshi to send.
|
||||||
* @param toAddress the recipient's address.
|
* @param toAddress the recipient's address.
|
||||||
* @param memo the optional memo to include as part of the transaction.
|
* @param memo the optional memo to include as part of the transaction.
|
||||||
* @param fromAccountIndex the optional account id to use. By default, the 1st account is used.
|
* @param fromAccountIndex the optional account id to use. By default, the 1st account is used.
|
||||||
@@ -38,12 +39,12 @@ class WalletTransactionEncoder(
|
|||||||
*/
|
*/
|
||||||
override suspend fun createTransaction(
|
override suspend fun createTransaction(
|
||||||
spendingKey: String,
|
spendingKey: String,
|
||||||
zatoshi: Long,
|
amount: Zatoshi,
|
||||||
toAddress: String,
|
toAddress: String,
|
||||||
memo: ByteArray?,
|
memo: ByteArray?,
|
||||||
fromAccountIndex: Int
|
fromAccountIndex: Int
|
||||||
): EncodedTransaction {
|
): EncodedTransaction {
|
||||||
val transactionId = createSpend(spendingKey, zatoshi, toAddress, memo)
|
val transactionId = createSpend(spendingKey, amount, toAddress, memo)
|
||||||
return repository.findEncodedTransactionById(transactionId)
|
return repository.findEncodedTransactionById(transactionId)
|
||||||
?: throw TransactionEncoderException.TransactionNotFoundException(transactionId)
|
?: throw TransactionEncoderException.TransactionNotFoundException(transactionId)
|
||||||
}
|
}
|
||||||
@@ -77,10 +78,8 @@ class WalletTransactionEncoder(
|
|||||||
*
|
*
|
||||||
* @return true when the given address is a valid t-addr
|
* @return true when the given address is a valid t-addr
|
||||||
*/
|
*/
|
||||||
/* THIS IS NOT SUPPORTED BY HUSH LIGHTWALLETD
|
|
||||||
override suspend fun isValidTransparentAddress(address: String): Boolean =
|
override suspend fun isValidTransparentAddress(address: String): Boolean =
|
||||||
rustBackend.isValidTransparentAddr(address)
|
rustBackend.isValidTransparentAddr(address)
|
||||||
*/
|
|
||||||
|
|
||||||
override suspend fun getConsensusBranchId(): Long {
|
override suspend fun getConsensusBranchId(): Long {
|
||||||
val height = repository.lastScannedHeight()
|
val height = repository.lastScannedHeight()
|
||||||
@@ -95,7 +94,7 @@ class WalletTransactionEncoder(
|
|||||||
* the result in the database. On average, this call takes over 10 seconds.
|
* the result in the database. On average, this call takes over 10 seconds.
|
||||||
*
|
*
|
||||||
* @param spendingKey the key associated with the notes that will be spent.
|
* @param spendingKey the key associated with the notes that will be spent.
|
||||||
* @param zatoshi the amount of zatoshi to send.
|
* @param amount the amount of zatoshi to send.
|
||||||
* @param toAddress the recipient's address.
|
* @param toAddress the recipient's address.
|
||||||
* @param memo the optional memo to include as part of the transaction.
|
* @param memo the optional memo to include as part of the transaction.
|
||||||
* @param fromAccountIndex the optional account id to use. By default, the 1st account is used.
|
* @param fromAccountIndex the optional account id to use. By default, the 1st account is used.
|
||||||
@@ -105,13 +104,13 @@ class WalletTransactionEncoder(
|
|||||||
*/
|
*/
|
||||||
private suspend fun createSpend(
|
private suspend fun createSpend(
|
||||||
spendingKey: String,
|
spendingKey: String,
|
||||||
zatoshi: Long,
|
amount: Zatoshi,
|
||||||
toAddress: String,
|
toAddress: String,
|
||||||
memo: ByteArray? = byteArrayOf(),
|
memo: ByteArray? = byteArrayOf(),
|
||||||
fromAccountIndex: Int = 0
|
fromAccountIndex: Int = 0
|
||||||
): Long {
|
): Long {
|
||||||
return twigTask(
|
return twigTask(
|
||||||
"creating transaction to spend $zatoshi zatoshi to" +
|
"creating transaction to spend $amount zatoshi to" +
|
||||||
" ${toAddress.masked()} with memo $memo"
|
" ${toAddress.masked()} with memo $memo"
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
@@ -123,7 +122,7 @@ class WalletTransactionEncoder(
|
|||||||
fromAccountIndex,
|
fromAccountIndex,
|
||||||
spendingKey,
|
spendingKey,
|
||||||
toAddress,
|
toAddress,
|
||||||
zatoshi,
|
amount.value,
|
||||||
memo
|
memo
|
||||||
)
|
)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
package cash.z.ecc.android.sdk.jni
|
package cash.z.ecc.android.sdk.jni
|
||||||
|
|
||||||
import cash.z.ecc.android.sdk.exception.BirthdayException
|
|
||||||
import cash.z.ecc.android.sdk.ext.ZcashSdk.OUTPUT_PARAM_FILE_NAME
|
import cash.z.ecc.android.sdk.ext.ZcashSdk.OUTPUT_PARAM_FILE_NAME
|
||||||
import cash.z.ecc.android.sdk.ext.ZcashSdk.SPEND_PARAM_FILE_NAME
|
import cash.z.ecc.android.sdk.ext.ZcashSdk.SPEND_PARAM_FILE_NAME
|
||||||
import cash.z.ecc.android.sdk.internal.SdkDispatchers
|
import cash.z.ecc.android.sdk.internal.SdkDispatchers
|
||||||
import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
|
import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
|
||||||
|
import cash.z.ecc.android.sdk.internal.model.Checkpoint
|
||||||
import cash.z.ecc.android.sdk.internal.twig
|
import cash.z.ecc.android.sdk.internal.twig
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||||
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
|
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
|
||||||
import cash.z.ecc.android.sdk.type.WalletBalance
|
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -19,21 +20,13 @@ import java.io.File
|
|||||||
* not be called directly by code outside of the SDK. Instead, one of the higher-level components
|
* not be called directly by code outside of the SDK. Instead, one of the higher-level components
|
||||||
* should be used such as Wallet.kt or CompactBlockProcessor.kt.
|
* should be used such as Wallet.kt or CompactBlockProcessor.kt.
|
||||||
*/
|
*/
|
||||||
class RustBackend private constructor() : RustBackendWelding {
|
internal class RustBackend private constructor(
|
||||||
|
override val network: ZcashNetwork,
|
||||||
// Paths
|
val birthdayHeight: BlockHeight,
|
||||||
lateinit var pathDataDb: String
|
val pathDataDb: String,
|
||||||
internal set
|
val pathCacheDb: String,
|
||||||
lateinit var pathCacheDb: String
|
val pathParamsDir: String
|
||||||
internal set
|
) : RustBackendWelding {
|
||||||
lateinit var pathParamsDir: String
|
|
||||||
internal set
|
|
||||||
|
|
||||||
override lateinit var network: ZcashNetwork
|
|
||||||
|
|
||||||
internal var birthdayHeight: Int = -1
|
|
||||||
get() = if (field != -1) field else throw BirthdayException.UninitializedBirthdayException
|
|
||||||
private set
|
|
||||||
|
|
||||||
suspend fun clear(clearCacheDb: Boolean = true, clearDataDb: Boolean = true) {
|
suspend fun clear(clearCacheDb: Boolean = true, clearDataDb: Boolean = true) {
|
||||||
if (clearCacheDb) {
|
if (clearCacheDb) {
|
||||||
@@ -84,18 +77,15 @@ class RustBackend private constructor() : RustBackendWelding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun initBlocksTable(
|
override suspend fun initBlocksTable(
|
||||||
height: Int,
|
checkpoint: Checkpoint
|
||||||
hash: String,
|
|
||||||
time: Long,
|
|
||||||
saplingTree: String
|
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return withContext(SdkDispatchers.DATABASE_IO) {
|
return withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
initBlocksTable(
|
initBlocksTable(
|
||||||
pathDataDb,
|
pathDataDb,
|
||||||
height,
|
checkpoint.height.value,
|
||||||
hash,
|
checkpoint.hash,
|
||||||
time,
|
checkpoint.epochSeconds,
|
||||||
saplingTree,
|
checkpoint.tree,
|
||||||
networkId = network.id
|
networkId = network.id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -110,11 +100,9 @@ class RustBackend private constructor() : RustBackendWelding {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
override suspend fun getTransparentAddress(account: Int, index: Int): String {
|
override suspend fun getTransparentAddress(account: Int, index: Int): String {
|
||||||
throw NotImplementedError("TODO: implement this at the zcash_client_sqlite level. But for now, use DerivationTool, instead to derive addresses from seeds")
|
throw NotImplementedError("TODO: implement this at the zcash_client_sqlite level. But for now, use DerivationTool, instead to derive addresses from seeds")
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
override suspend fun getBalance(account: Int): Zatoshi {
|
override suspend fun getBalance(account: Int): Zatoshi {
|
||||||
val longValue = withContext(SdkDispatchers.DATABASE_IO) {
|
val longValue = withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
@@ -158,19 +146,28 @@ class RustBackend private constructor() : RustBackendWelding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun validateCombinedChain() = withContext(SdkDispatchers.DATABASE_IO) {
|
override suspend fun validateCombinedChain() = withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
validateCombinedChain(
|
val validationResult = validateCombinedChain(
|
||||||
pathCacheDb,
|
pathCacheDb,
|
||||||
pathDataDb,
|
pathDataDb,
|
||||||
networkId = network.id
|
networkId = network.id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (-1L == validationResult) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
BlockHeight.new(network, validationResult)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getNearestRewindHeight(height: Int): Int =
|
override suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight =
|
||||||
withContext(SdkDispatchers.DATABASE_IO) {
|
withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
getNearestRewindHeight(
|
BlockHeight.new(
|
||||||
pathDataDb,
|
network,
|
||||||
height,
|
getNearestRewindHeight(
|
||||||
networkId = network.id
|
pathDataDb,
|
||||||
|
height.value,
|
||||||
|
networkId = network.id
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,11 +176,11 @@ class RustBackend private constructor() : RustBackendWelding {
|
|||||||
*
|
*
|
||||||
* DELETE FROM blocks WHERE height > ?
|
* DELETE FROM blocks WHERE height > ?
|
||||||
*/
|
*/
|
||||||
override suspend fun rewindToHeight(height: Int) =
|
override suspend fun rewindToHeight(height: BlockHeight) =
|
||||||
withContext(SdkDispatchers.DATABASE_IO) {
|
withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
rewindToHeight(
|
rewindToHeight(
|
||||||
pathDataDb,
|
pathDataDb,
|
||||||
height,
|
height.value,
|
||||||
networkId = network.id
|
networkId = network.id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -266,7 +263,7 @@ class RustBackend private constructor() : RustBackendWelding {
|
|||||||
index: Int,
|
index: Int,
|
||||||
script: ByteArray,
|
script: ByteArray,
|
||||||
value: Long,
|
value: Long,
|
||||||
height: Int
|
height: BlockHeight
|
||||||
): Boolean = withContext(SdkDispatchers.DATABASE_IO) {
|
): Boolean = withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
putUtxo(
|
putUtxo(
|
||||||
pathDataDb,
|
pathDataDb,
|
||||||
@@ -275,19 +272,21 @@ class RustBackend private constructor() : RustBackendWelding {
|
|||||||
index,
|
index,
|
||||||
script,
|
script,
|
||||||
value,
|
value,
|
||||||
height,
|
height.value,
|
||||||
networkId = network.id
|
networkId = network.id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun clearUtxos(
|
override suspend fun clearUtxos(
|
||||||
tAddress: String,
|
tAddress: String,
|
||||||
aboveHeight: Int
|
aboveHeightInclusive: BlockHeight
|
||||||
): Boolean = withContext(SdkDispatchers.DATABASE_IO) {
|
): Boolean = withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
clearUtxos(
|
clearUtxos(
|
||||||
pathDataDb,
|
pathDataDb,
|
||||||
tAddress,
|
tAddress,
|
||||||
aboveHeight,
|
// The Kotlin API is inclusive, but the Rust API is exclusive.
|
||||||
|
// This can create invalid BlockHeights if the height is saplingActivationHeight.
|
||||||
|
aboveHeightInclusive.value - 1,
|
||||||
networkId = network.id
|
networkId = network.id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -313,13 +312,11 @@ class RustBackend private constructor() : RustBackendWelding {
|
|||||||
override fun isValidShieldedAddr(addr: String) =
|
override fun isValidShieldedAddr(addr: String) =
|
||||||
isValidShieldedAddress(addr, networkId = network.id)
|
isValidShieldedAddress(addr, networkId = network.id)
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORTED IN HUSH LIGHTWALLETD
|
|
||||||
override fun isValidTransparentAddr(addr: String) =
|
override fun isValidTransparentAddr(addr: String) =
|
||||||
isValidTransparentAddress(addr, networkId = network.id)
|
isValidTransparentAddress(addr, networkId = network.id)
|
||||||
*/
|
|
||||||
|
|
||||||
override fun getBranchIdForHeight(height: Int): Long =
|
override fun getBranchIdForHeight(height: BlockHeight): Long =
|
||||||
branchIdForHeight(height, networkId = network.id)
|
branchIdForHeight(height.value, networkId = network.id)
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * This is a proof-of-concept for doing Local RPC, where we are effectively using the JNI
|
// * This is a proof-of-concept for doing Local RPC, where we are effectively using the JNI
|
||||||
@@ -355,19 +352,17 @@ class RustBackend private constructor() : RustBackendWelding {
|
|||||||
dataDbPath: String,
|
dataDbPath: String,
|
||||||
paramsPath: String,
|
paramsPath: String,
|
||||||
zcashNetwork: ZcashNetwork,
|
zcashNetwork: ZcashNetwork,
|
||||||
birthdayHeight: Int? = null
|
birthdayHeight: BlockHeight
|
||||||
): RustBackend {
|
): RustBackend {
|
||||||
rustLibraryLoader.load()
|
rustLibraryLoader.load()
|
||||||
|
|
||||||
return RustBackend().apply {
|
return RustBackend(
|
||||||
pathCacheDb = cacheDbPath
|
zcashNetwork,
|
||||||
pathDataDb = dataDbPath
|
birthdayHeight,
|
||||||
|
pathDataDb = dataDbPath,
|
||||||
|
pathCacheDb = cacheDbPath,
|
||||||
pathParamsDir = paramsPath
|
pathParamsDir = paramsPath
|
||||||
network = zcashNetwork
|
)
|
||||||
if (birthdayHeight != null) {
|
|
||||||
this.birthdayHeight = birthdayHeight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -400,7 +395,7 @@ class RustBackend private constructor() : RustBackendWelding {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun initBlocksTable(
|
private external fun initBlocksTable(
|
||||||
dbDataPath: String,
|
dbDataPath: String,
|
||||||
height: Int,
|
height: Long,
|
||||||
hash: String,
|
hash: String,
|
||||||
time: Long,
|
time: Long,
|
||||||
saplingTree: String,
|
saplingTree: String,
|
||||||
@@ -417,10 +412,8 @@ class RustBackend private constructor() : RustBackendWelding {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun isValidShieldedAddress(addr: String, networkId: Int): Boolean
|
private external fun isValidShieldedAddress(addr: String, networkId: Int): Boolean
|
||||||
|
|
||||||
/* THIS IS NOT SUPPORT IN HUSH LIGHTWALLETD
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun isValidTransparentAddress(addr: String, networkId: Int): Boolean
|
private external fun isValidTransparentAddress(addr: String, networkId: Int): Boolean
|
||||||
*/
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun getBalance(dbDataPath: String, account: Int, networkId: Int): Long
|
private external fun getBalance(dbDataPath: String, account: Int, networkId: Int): Long
|
||||||
@@ -451,19 +444,19 @@ class RustBackend private constructor() : RustBackendWelding {
|
|||||||
dbCachePath: String,
|
dbCachePath: String,
|
||||||
dbDataPath: String,
|
dbDataPath: String,
|
||||||
networkId: Int
|
networkId: Int
|
||||||
): Int
|
): Long
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun getNearestRewindHeight(
|
private external fun getNearestRewindHeight(
|
||||||
dbDataPath: String,
|
dbDataPath: String,
|
||||||
height: Int,
|
height: Long,
|
||||||
networkId: Int
|
networkId: Int
|
||||||
): Int
|
): Long
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun rewindToHeight(
|
private external fun rewindToHeight(
|
||||||
dbDataPath: String,
|
dbDataPath: String,
|
||||||
height: Int,
|
height: Long,
|
||||||
networkId: Int
|
networkId: Int
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
@@ -519,7 +512,7 @@ class RustBackend private constructor() : RustBackendWelding {
|
|||||||
private external fun initLogs()
|
private external fun initLogs()
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun branchIdForHeight(height: Int, networkId: Int): Long
|
private external fun branchIdForHeight(height: Long, networkId: Int): Long
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun putUtxo(
|
private external fun putUtxo(
|
||||||
@@ -529,7 +522,7 @@ class RustBackend private constructor() : RustBackendWelding {
|
|||||||
index: Int,
|
index: Int,
|
||||||
script: ByteArray,
|
script: ByteArray,
|
||||||
value: Long,
|
value: Long,
|
||||||
height: Int,
|
height: Long,
|
||||||
networkId: Int
|
networkId: Int
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
@@ -537,7 +530,7 @@ class RustBackend private constructor() : RustBackendWelding {
|
|||||||
private external fun clearUtxos(
|
private external fun clearUtxos(
|
||||||
dbDataPath: String,
|
dbDataPath: String,
|
||||||
tAddress: String,
|
tAddress: String,
|
||||||
aboveHeight: Int,
|
aboveHeight: Long,
|
||||||
networkId: Int
|
networkId: Int
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package cash.z.ecc.android.sdk.jni
|
package cash.z.ecc.android.sdk.jni
|
||||||
|
|
||||||
|
import cash.z.ecc.android.sdk.internal.model.Checkpoint
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
|
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
|
||||||
import cash.z.ecc.android.sdk.type.WalletBalance
|
|
||||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contract defining the exposed capabilities of the Rust backend.
|
* Contract defining the exposed capabilities of the Rust backend.
|
||||||
@@ -11,7 +13,7 @@ import cash.z.ecc.android.sdk.type.ZcashNetwork
|
|||||||
* It is not documented because it is not intended to be used, directly.
|
* It is not documented because it is not intended to be used, directly.
|
||||||
* Instead, use the synchronizer or one of its subcomponents.
|
* Instead, use the synchronizer or one of its subcomponents.
|
||||||
*/
|
*/
|
||||||
interface RustBackendWelding {
|
internal interface RustBackendWelding {
|
||||||
|
|
||||||
val network: ZcashNetwork
|
val network: ZcashNetwork
|
||||||
|
|
||||||
@@ -36,21 +38,21 @@ interface RustBackendWelding {
|
|||||||
|
|
||||||
suspend fun initAccountsTable(vararg keys: UnifiedViewingKey): Boolean
|
suspend fun initAccountsTable(vararg keys: UnifiedViewingKey): Boolean
|
||||||
|
|
||||||
suspend fun initBlocksTable(height: Int, hash: String, time: Long, saplingTree: String): Boolean
|
suspend fun initBlocksTable(checkpoint: Checkpoint): Boolean
|
||||||
|
|
||||||
suspend fun initDataDb(): Boolean
|
suspend fun initDataDb(): Boolean
|
||||||
|
|
||||||
fun isValidShieldedAddr(addr: String): Boolean
|
fun isValidShieldedAddr(addr: String): Boolean
|
||||||
|
|
||||||
//fun isValidTransparentAddr(addr: String): Boolean
|
fun isValidTransparentAddr(addr: String): Boolean
|
||||||
|
|
||||||
suspend fun getShieldedAddress(account: Int = 0): String
|
suspend fun getShieldedAddress(account: Int = 0): String
|
||||||
|
|
||||||
//suspend fun getTransparentAddress(account: Int = 0, index: Int = 0): String
|
suspend fun getTransparentAddress(account: Int = 0, index: Int = 0): String
|
||||||
|
|
||||||
suspend fun getBalance(account: Int = 0): Zatoshi
|
suspend fun getBalance(account: Int = 0): Zatoshi
|
||||||
|
|
||||||
fun getBranchIdForHeight(height: Int): Long
|
fun getBranchIdForHeight(height: BlockHeight): Long
|
||||||
|
|
||||||
suspend fun getReceivedMemoAsUtf8(idNote: Long): String
|
suspend fun getReceivedMemoAsUtf8(idNote: Long): String
|
||||||
|
|
||||||
@@ -60,13 +62,16 @@ interface RustBackendWelding {
|
|||||||
|
|
||||||
// fun parseTransactionDataList(tdl: LocalRpcTypes.TransactionDataList): LocalRpcTypes.TransparentTransactionList
|
// fun parseTransactionDataList(tdl: LocalRpcTypes.TransactionDataList): LocalRpcTypes.TransparentTransactionList
|
||||||
|
|
||||||
suspend fun getNearestRewindHeight(height: Int): Int
|
suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight
|
||||||
|
|
||||||
suspend fun rewindToHeight(height: Int): Boolean
|
suspend fun rewindToHeight(height: BlockHeight): Boolean
|
||||||
|
|
||||||
suspend fun scanBlocks(limit: Int = -1): Boolean
|
suspend fun scanBlocks(limit: Int = -1): Boolean
|
||||||
|
|
||||||
suspend fun validateCombinedChain(): Int
|
/**
|
||||||
|
* @return Null if successful. If an error occurs, the height will be the height where the error was detected.
|
||||||
|
*/
|
||||||
|
suspend fun validateCombinedChain(): BlockHeight?
|
||||||
|
|
||||||
suspend fun putUtxo(
|
suspend fun putUtxo(
|
||||||
tAddress: String,
|
tAddress: String,
|
||||||
@@ -74,10 +79,10 @@ interface RustBackendWelding {
|
|||||||
index: Int,
|
index: Int,
|
||||||
script: ByteArray,
|
script: ByteArray,
|
||||||
value: Long,
|
value: Long,
|
||||||
height: Int
|
height: BlockHeight
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
suspend fun clearUtxos(tAddress: String, aboveHeight: Int = network.saplingActivationHeight - 1): Boolean
|
suspend fun clearUtxos(tAddress: String, aboveHeightInclusive: BlockHeight = BlockHeight(network.saplingActivationHeight.value)): Boolean
|
||||||
|
|
||||||
suspend fun getDownloadedUtxoBalance(address: String): WalletBalance
|
suspend fun getDownloadedUtxoBalance(address: String): WalletBalance
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package cash.z.ecc.android.sdk.model
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import cash.z.ecc.android.sdk.tool.CheckpointTool
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a block height, which is a UInt32. SDK clients use this class to represent the "birthday" of a wallet.
|
||||||
|
*
|
||||||
|
* New instances are constructed using the [new] factory method.
|
||||||
|
*
|
||||||
|
* @param value The block height. Must be in range of a UInt32.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* For easier compatibility with Java clients, this class represents the height value as a Long with
|
||||||
|
* assertions to ensure that it is a 32-bit unsigned integer.
|
||||||
|
*/
|
||||||
|
data class BlockHeight internal constructor(val value: Long) : Comparable<BlockHeight> {
|
||||||
|
init {
|
||||||
|
require(UINT_RANGE.contains(value)) { "Height $value is outside of allowed range $UINT_RANGE" }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun compareTo(other: BlockHeight): Int = value.compareTo(other.value)
|
||||||
|
|
||||||
|
operator fun plus(other: BlockHeight) = BlockHeight(value + other.value)
|
||||||
|
|
||||||
|
operator fun plus(other: Int): BlockHeight {
|
||||||
|
if (other < 0) {
|
||||||
|
throw IllegalArgumentException("Cannot add negative value $other to BlockHeight")
|
||||||
|
}
|
||||||
|
|
||||||
|
return BlockHeight(value + other.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun plus(other: Long): BlockHeight {
|
||||||
|
if (other < 0) {
|
||||||
|
throw IllegalArgumentException("Cannot add negative value $other to BlockHeight")
|
||||||
|
}
|
||||||
|
|
||||||
|
return BlockHeight(value + other)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val UINT_RANGE = 0.toLong()..UInt.MAX_VALUE.toLong()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param zcashNetwork Network to use for the block height.
|
||||||
|
* @param blockHeight The block height. Must be in range of a UInt32 AND must be greater than the network's sapling activation height.
|
||||||
|
*/
|
||||||
|
fun new(zcashNetwork: ZcashNetwork, blockHeight: Long): BlockHeight {
|
||||||
|
require(blockHeight >= zcashNetwork.saplingActivationHeight.value) {
|
||||||
|
"Height $blockHeight is below sapling activation height ${zcashNetwork.saplingActivationHeight}"
|
||||||
|
}
|
||||||
|
|
||||||
|
return BlockHeight(blockHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Useful when creating a new wallet to reduce sync times.
|
||||||
|
*
|
||||||
|
* @param zcashNetwork Network to use for the block height.
|
||||||
|
* @return The block height of the newest checkpoint known by the SDK.
|
||||||
|
*/
|
||||||
|
suspend fun ofLatestCheckpoint(context: Context, zcashNetwork: ZcashNetwork): BlockHeight {
|
||||||
|
return CheckpointTool.loadNearest(context, zcashNetwork, null).height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package cash.z.ecc.android.sdk.model
|
||||||
|
|
||||||
|
data class LightWalletEndpoint(val host: String, val port: Int, val isSecure: Boolean) {
|
||||||
|
companion object
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
@file:Suppress("ktlint:filename")
|
||||||
|
|
||||||
|
package cash.z.ecc.android.sdk.model
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a set of extension functions currently, because we expect them to change in the future.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun LightWalletEndpoint.Companion.defaultForNetwork(zcashNetwork: ZcashNetwork): LightWalletEndpoint {
|
||||||
|
return when (zcashNetwork.id) {
|
||||||
|
ZcashNetwork.Mainnet.id -> LightWalletEndpoint.Mainnet
|
||||||
|
ZcashNetwork.Testnet.id -> LightWalletEndpoint.Testnet
|
||||||
|
else -> error("Unknown network id: ${zcashNetwork.id}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a special localhost value on the Android emulator, which allows it to contact
|
||||||
|
* the localhost of the computer running the emulator.
|
||||||
|
*/
|
||||||
|
private const val COMPUTER_LOCALHOST = "10.0.2.2"
|
||||||
|
|
||||||
|
private const val DEFAULT_PORT = 9067
|
||||||
|
|
||||||
|
val LightWalletEndpoint.Companion.Mainnet
|
||||||
|
get() = LightWalletEndpoint(
|
||||||
|
"lite2.hushpool.is",
|
||||||
|
DEFAULT_PORT,
|
||||||
|
isSecure = false
|
||||||
|
)
|
||||||
|
|
||||||
|
val LightWalletEndpoint.Companion.Testnet
|
||||||
|
get() = LightWalletEndpoint(
|
||||||
|
"lite2.hushpool.is",
|
||||||
|
DEFAULT_PORT,
|
||||||
|
isSecure = false
|
||||||
|
)
|
||||||
|
|
||||||
|
val LightWalletEndpoint.Companion.Darkside
|
||||||
|
get() = LightWalletEndpoint(
|
||||||
|
COMPUTER_LOCALHOST,
|
||||||
|
DEFAULT_PORT,
|
||||||
|
isSecure = false
|
||||||
|
)
|
||||||