[#971] Refactor Rust FFI to separate module

---------

Co-authored-by: Honza <rychnovsky.honza@gmail.com>
This commit is contained in:
Carter Jernigan 2023-05-18 07:36:15 -04:00 committed by GitHub
parent ca0e69d97f
commit bc19797125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
89 changed files with 1215 additions and 916 deletions

View File

@ -357,6 +357,9 @@ jobs:
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS: androiddebugkey
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS_PASSWORD: android
run: |
# Due to issues with the Rust integration, building the release APK requires running the task twice to
# ensure the native libraries are bundled in the APK
./gradlew assembleRelease
./gradlew assembleRelease
- name: Collect Artifacts
timeout-minutes: 1

View File

@ -0,0 +1,64 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name=":backend-lib:connectedAndroidTest" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-sdk.backend-lib.androidTest" />
<option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" />
<option name="TEST_NAME_REGEX" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" />
<option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483645" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="locale-debug-48db7" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Callstack Sample" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,64 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name=":lightwallet-client-lib:connectedAndroidTest" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-sdk.lightwallet-client-lib.androidTest" />
<option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" />
<option name="TEST_NAME_REGEX" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" />
<option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483645" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="locale-debug-48db7" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Callstack Sample" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,64 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name=":sdk-incubator-lib:connectedAndroidTest" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-sdk.sdk-incubator-lib.androidTest" />
<option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" />
<option name="TEST_NAME_REGEX" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" />
<option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483645" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="locale-debug-48db7" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Callstack Sample" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,7 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="demo-app-benchmark-test:connectedBenchmarkAndroidTest"
type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-sdk.demo-app-benchmark-test" />
<configuration default="false" name="demo-app-benchmark-test:connectedBenchmarkAndroidTest" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-sdk.demo-app-benchmark-test.main" />
<option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
@ -16,8 +15,8 @@
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483645" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="locale-debug-48db7" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
@ -25,6 +24,8 @@
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
@ -32,14 +33,21 @@
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java />
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />

View File

@ -1,20 +1,22 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name=":darkside-test-lib:connectedAndroidTest" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-sdk.darkside-test-lib" />
<module name="zcash-android-sdk.darkside-test-lib.androidTest" />
<option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" />
<option name="TEST_NAME_REGEX" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" />
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" />
<option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
<option name="FORCE_STOP_RUNNING_APP" value="true" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483645" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="api-9130115880275692386-873230" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="locale-debug-48db7" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
@ -22,6 +24,8 @@
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
@ -29,20 +33,27 @@
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java />
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Sample Java Methods" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Callstack Sample" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>

View File

@ -5,9 +5,9 @@
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" />
<option name="TEST_NAME_REGEX" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" />
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" />
<option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
@ -16,6 +16,7 @@
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483645" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="locale-debug-48db7" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
@ -23,6 +24,8 @@
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
@ -30,14 +33,21 @@
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java />
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />

View File

@ -1,25 +1,20 @@
Change Log
==========
# Change Log
## Unreleased
- The SDK's `CompactBlockProcessor` switched from processing **all blocks in one run** mechanism to **batched blocks**
processing. This was necessary for the sync state's parallelization. Example of syncing of the latest
100 blocks:
- Previously: _Download 100 blocks -> Validate 100 blocks -> Scan 100 blocks -> SYNCED_
- Now: _10x (Download 10 blocks -> Validate 10 blocks -> Scan 10 blocks) -> SYNCED_
- `Synchronizer.progress` now returns `Flow<PercentDecimal>` instead of `Flow<Int>`. PercentDecimal is a type-safe
model.
Use `PercentDecimal.toPercentage()` to get a number within 0-100% scale.
- `Synchronizer.status` now provides a new `SYNCING` state, which covers all three previous `DOWNLOADING`,
`VALIDATING`, and `SCANNING` states, which were eliminated in favor of `SYNCING` state.
## 1.17.0-beta01
- Synchronizer APIs for listing sent and received transactions have been removed.
- Synchronizer APIs for listing pending transactions have been removed, along with the `PendingTransaction` object.
- `Synchronizer.clearedTransactions` has been renamed to `Synchronizer.transactions` and includes sent, received, and pending transactions.
- Transparent fund balances are now displayed almost immediately
- Synchronization of shielded balances and transaction history is about 30% faster
- Disk space usage is reduced by about 90%
- `Synchronizer.status` has been simplified by combining `DOWNLOADING`, `VALIDATING`, and `SCANNING` states into a single `SYNCING` state.
- `Synchronizer.progress` now returns `Flow<PercentDecimal>` instead of `Flow<Int>`. PercentDecimal is a type-safe model. Use `PercentDecimal.toPercentage()` to get a number within 0-100% scale.
- `Synchronizer.clearedTransactions` has been renamed to `Synchronizer.transactions` and includes sent, received, and pending transactions. Synchronizer APIs for listing sent, received, and pending transactions have been removed. Clients can determine whether a transaction is sent, received, or pending by filtering the `TransactionOverview` objects returned by `Synchronizer.transactions`
- `Synchronizer.send()` and `shieldFunds()` are now `suspend` functions with `Long` return values representing the ID of the newly created transaction. Errors are reported by thrown exceptions.
- `DerivationTool` is now an interface, rather than an `object`, which makes it easier to inject alternative implementations into tests. To adapt to the new API, replace calls to `DerivationTool.methodName()` with `DerivationTool.getInstance().methodName()`.
- `DerivationTool` methods are no longer suspending, which should make it easier to call them in various situations. Obtaining a `DerivationTool` instance via `DerivationTool.getInstance()` frontloads the need for a suspending call.
- `DerivationTool.deriveUnifiedFullViewingKeys()` no longer has a default argument for `numberOfAccounts`. Clients should now pass `DerivationTool.DEFAULT_NUMBER_OF_ACCOUNTS` as the value. Note that the SDK does not currently have proper support for multiple accounts.
- The SDK's internals for connecting with librustzcash have been refactored to a separate Gradle module `backend-lib` (and therefore a separate artifact) which is a transitive dependency of the Zcash Android SDK. SDK consumers that use Gradle dependency locks may notice this difference, but otherwise it should be mostly an invisible change.
## 1.16.0-beta01
(This version was only deployed as a snapshot and not released on Maven Central)
### Changed
- The minimum supported version of Android is now API level 27.
@ -27,7 +22,7 @@ processing. This was necessary for the sync state's parallelization. Example of
### Changed
- A new package `sdk-incubator-lib` is now available as a public API. This package contains experimental APIs that may be promoted to the SDK in the future. The APIs in this package are not guaranteed to be stable, and may change at any time.
- `Synchronizer.refreshUtxos` now takes `Account` type as first parameter instead of transparent address of type
`String`, and thus it downloads all UTXOs for the given account addresses.
`String`, and thus it downloads all UTXOs for the given account addresses. The Account object provides a default `0` index Account with `Account.DEFAULT`.
## 1.14.0-beta01
### Changed
@ -41,6 +36,7 @@ processing. This was necessary for the sync state's parallelization. Example of
- The new networking module now provides a `LightWalletClient` for asynchronous calls.
- Most unary calls respond with the new `Response` class and its subclasses. Streaming calls will be updated
with the Response class later.
- SDK clients should avoid using generated GRPC objects, as these are an internal implementation detail and are in process of being removed from the public API. Any clients using GRPC objects will find these have been repackaged from `cash.z.wallet.sdk.rpc` to `cash.z.wallet.sdk.internal.rpc` to signal they are not a public API.
## 1.12.0-beta01
### Changed
@ -122,58 +118,48 @@ processing. This was necessary for the sync state's parallelization. Example of
- `DerivationTool.deriveUnifiedViewingKeys` (use `DerivationTool.deriveUnifiedFullViewingKey` instead)
- `DerivationTool.validateUnifiedViewingKey`
Version 1.9.0-beta05
------------------------------------
## Version 1.9.0-beta05
- The minimum version of Android supported is now API 21
- Fixed R8/ProGuard consumer rule, which eliminates a runtime crash for minified apps
Version 1.9.0-beta04
------------------------------------
## Version 1.9.0-beta04
- The SDK now stores sapling param files in `no_backup/co.electricoin.zcash` folder instead of the `cache/params`
folder. Besides that, `SaplingParamTool` also does validation of downloaded sapling param file hash and size.
**No action required from client app**.
Version 1.9.0-beta03
------------------------------------
## Version 1.9.0-beta03
- No changes; this release is a test of a new deployment process
Version 1.9.0-beta02
------------------------------------
## Version 1.9.0-beta02
- The SDK now stores database files in `no_backup/co.electricoin.zcash` folder instead of the `database` folder. **No action required from client app**.
Version 1.9.0-beta01
------------------------------------
## Version 1.9.0-beta01
- Split `ZcashNetwork` into `ZcashNetwork` and `LightWalletEndpoint` to decouple network and server configuration
- Gradle 7.5.1
- Updated checkpoints
Version 1.8.0-beta01
------------------------------------
## Version 1.8.0-beta01
- Enabled automated unit tests run on the CI server
- Added `BlockHeight` typesafe object to represent block heights
- Significantly reduced memory usage, fixing potential OutOfMemoryError during block download
- Kotlin 1.7.10
- Updated checkpoints
Version 1.7.0-beta01
------------------------------------
## 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
- Updated checkpoints for Mainnet and Testnet
- Fix: SDK can now be used on Intel x86_64 emulators
- Prevent R8 warnings for apps consuming the SDK
Version 1.5.0-beta01
------------------------------------
## Version 1.5.0-beta01
- New: Transactions can be created after NU5 activation.
- New: Support for receiving v5 transactions.
- Known issues: The SDK will not run on Intel 64-bit API 31+ emulators. Workarounds include: testing on a physical device, using an older 32-bit API version Intel emulator, or using an ARM emulator.
Version 1.4.0-beta01
------------------------------------
## Version 1.4.0-beta01
- Main entrypoint to the SDK has changed. See [MIGRATIONS.md](MIGRATIONS.md)
- The minimum version of Android supported is now API 19
- Updated checkpoints for Mainnet and Testnet
@ -182,83 +168,68 @@ Version 1.4.0-beta01
- Updated dependencies, including Kotlin 1.6.21, Coroutines 1.6.1, GRPC 1.46.0, Okio 3.1.0, NDK 23
- Known issues: The SDK will not run on Intel 64-bit API 31+ emulators. Workarounds include: testing on a physical device, using an older 32-bit API version Intel emulator, or using an ARM emulator.
Version 1.3.0-beta20
------------------------------------
## Version 1.3.0-beta20
- New: Updated checkpoints for Mainnet and Testnet
Version 1.3.0-beta19
------------------------------------
## Version 1.3.0-beta19
- New: Updated checkpoints for Mainnet and Testnet
- Fix: Repackaged internal classes to a new `internal` package name
- Fix: Testnet checkpoints have been corrected
- Updated dependencies
Version 1.3.0-beta18
------------------------------------
## Version 1.3.0-beta18
- Fix: Corrected logic when calculating birthdates for wallets with zero received notes.
Version 1.3.0-beta17
------------------------------------
## Version 1.3.0-beta17
- Fix: Autoshielding confirmation count error so funds are available after 10 confirmations.
- New: Allow developers to enable Rust logs.
- New: Accept GZIP compression from lightwalletd.
- New: Reduce the UTXO retry time.
Version 1.3.0-beta16
------------------------------------
## Version 1.3.0-beta16
- Fix: Gracefully handle failures while fetching UTXOs.
- New: Expose StateFlows for balances.
- New: Make it easier to subscribe to transactions.
- New: Cleanup default logs.
- New: Convenience functions for WalletBalance objects.
Version 1.3.0-beta15
------------------------------------
## Version 1.3.0-beta15
- Fix: Increase reconnection attempts on failed app restart.
- New: Updated checkpoints for testnet and mainnet.
Version 1.3.0-beta14
------------------------------------
## Version 1.3.0-beta14
- New: Add separate flows for sapling, orchard and tranparent balances.
- Fix: Continue troubleshooting and fixing server disconnects.
- Updated dependencies.
Version 1.3.0-beta12
------------------------------------
## Version 1.3.0-beta12
- New: Expose network height as StateFlow.
- Fix: Reconnect to lightwalletd when a service exception occurs.
Version 1.3.0-beta11
------------------------------------
## Version 1.3.0-beta11
- Fix: Remove unused flag that was breaking new wallet creation for some wallets.
Version 1.3.0-beta10
------------------------------------
## Version 1.3.0-beta10
- Fix: Make it safe to call the new prepare function more than once.
Version 1.3.0-beta09
------------------------------------
## Version 1.3.0-beta09
- New: Add quick rewind feature, which makes it easy to rescan blocks after an upgrade.
- Fix: Repair complex data migration bug that caused crashes on upgrades.
Version 1.3.0-beta08
------------------------------------
## Version 1.3.0-beta08
- Fix: Disable librustzcash logs by default.
Version 1.3.0-beta07
------------------------------------
## Version 1.3.0-beta07
- Fix: Address issues with key migration, allowing wallets to reset viewing keys, when needed.
Version 1.3.0-beta06
------------------------------------
## Version 1.3.0-beta06
- Fix: Repair publishing so that AARs work on Windows machines [issue #222].
- Fix: Incorrect BranchId on 32-bit devics [issue #224].
- Fix: Rescan should not go beyond the wallet checkpoint.
- New: Drop Android Jetifier since it is no longer used.
- Updated checkpoints, improved tests (added Test Suites) and better error messages.
Version 1.3.0-beta05
------------------------------------
## Version 1.3.0-beta05
- Major: Consolidate product flavors into one library for the SDK instead of two.
- Major: Integrates with latest Librustzcash including full Data Access API support.
- Major: Move off of JCenter and onto Maven Central.
@ -283,44 +254,36 @@ Version 1.3.0-beta05
- New: Derive sapling activation height from the active network.
- New: Latest checkpoints for mainnet and testnet.
Version 1.2.1-beta04
------------------------------------
## Version 1.2.1-beta04
- New: Updated to latest versions of grpc, grpc-okhttp and protoc
- Fix: Addresses root issue of Android 11 crash on SSL sockets
Version 1.2.1-beta03
------------------------------------
## Version 1.2.1-beta03
- New: Implements ZIP-313, reducing the default fee from 10,000 to 1,000 zats.
- Fix: 80% reduction in build warnings from 90 -> 18 and improved docs [Credit: @herou].
Version 1.2.1-beta02
------------------------------------
## Version 1.2.1-beta02
- New: Improve birthday configuration and config functions.
- Fix: Broken layout in demo app transaction list.
Version 1.2.1-beta01
------------------------------------
## Version 1.2.1-beta01
- New: Added latest checkpoints for testnet and mainnet.
- New: Added display name for Canopy.
- New: Update to the latest lightwalletd service definition.
- Fix: Convert Initializer.Builder to Initializer.Config to simplify the constructors.
Version 1.2.0-beta01
------------------------------------
## Version 1.2.0-beta01
- New: Added ability to erase initializer data.
- Fix: Updated to latest librustzcash, fixing send functionality on Canopy.
Version 1.1.0-beta10
------------------------------------
## Version 1.1.0-beta10
- New: Modified visibility on a few things to facilitate partner integrations.
Version 1.1.0-beta08
------------------------------------
## Version 1.1.0-beta08
- Fix: Publishing has been corrected by jcenter's support team.
- New: Minor improvement to initializer
Version 1.1.0-beta05
------------------------------------
## Version 1.1.0-beta05
- New: Synchronizer can now be started with just a viewing key.
- New: Initializer improvements.
- New: Added tool for loading checkpoints.
@ -330,8 +293,7 @@ Version 1.1.0-beta05
- Fix: Broken testnet demo app.
- Fix: Publishing configuration.
Version 1.1.0-beta04
------------------------------------
## Version 1.1.0-beta04
- New: Add support for canopy on testnet.
- New: Change the default lightwalletd server.
- New: Add lightwalletd service for fetching t-addr transactions.
@ -340,8 +302,7 @@ Version 1.1.0-beta04
- New: Added new checkpoints.
- Fix: Minor enhancements.
Version 1.1.0-beta03
------------------------------------
## Version 1.1.0-beta03
- New: Add robust support for transaction cancellation.
- New: Update to latest version of librustzcash.
- New: Expand test support.

View File

@ -1,27 +1,7 @@
Troubleshooting Migrations
==========
Migration to Version 1.17
---------------------------------
Synchronizer APIs for listing sent and received transactions have been removed. Clients should use `Synchronizer.transactions`, filtering on the field `TransactionOverview.isSentTransaction`.
Synchronizer APIs for pending transactions have been removed. Clients should use `Synchronizer.transactions`, filtering on the field `TransactionOverview.transactionState`
The Synchronizer APIs for sending transactions (`send()` and `shieldFunds()`) are now suspend functions that return `Long` which contains the ID of the newly created transaction. To monitor for changes to pending transactions, observe `Synchronizer.transactions`. If a failure occurs, `send()` and `shieldFunds()` throw exceptions.
Migration to Version 1.15
---------------------------------
The updated `Synchronizer.refreshUtxos` is now supposed to be called with `Account` parameter instead of `String` address parameter. The Account object provides a default `0` index Account with `Account.DEFAULT`.
Migration to Version 1.13
---------------------------------
Update usages of `z.cash.ecc.android.sdk.model.LightWalletEndpoint` to `co.electriccoin.lightwallet.client.model.LightWalletEndpoint`.
SDK clients should avoid using generated GRPC objects, as these are an internal implementation detail and are in process of being removed from the public API. Any clients using GRPC objects will find these have been repackaged from `cash.z.wallet.sdk.rpc` to `cash.z.wallet.sdk.internal.rpc` to signal they are not a public API.
Migration to Version 1.12
---------------------------------
`TransactionOverview`, `Transaction.Sent`, and `Transaction.Received` have been updated to reflect that `minedHeight` is nullable.
Note: Going forward, migrations will be incorporated into the CHANGELOG.md.
Migration to Version 1.11
---------------------------------

View File

@ -0,0 +1,124 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("zcash-sdk.android-conventions")
id("org.jetbrains.dokka")
id("org.mozilla.rust-android-gradle.rust-android")
id("wtf.emulator.gradle")
id("zcash-sdk.emulator-wtf-conventions")
id("maven-publish")
id("signing")
id("zcash-sdk.publishing-conventions")
}
// Publishing information
val myVersion = project.property("LIBRARY_VERSION").toString()
val myArtifactId = "zcash-android-backend"
publishing {
publications {
publications.withType<MavenPublication>().all {
artifactId = myArtifactId
}
}
}
android {
namespace = "cash.z.ecc.android.backend"
useLibrary("android.test.runner")
defaultConfig {
consumerProguardFiles("proguard-consumer.txt")
}
buildTypes {
getByName("debug").apply {
// test builds exceed the dex limit because they pull in large test libraries
isMinifyEnabled = false
}
getByName("release").apply {
isMinifyEnabled = project.property("IS_MINIFY_SDK_ENABLED").toString().toBoolean()
proguardFiles.addAll(
listOf(
getDefaultProguardFile("proguard-android-optimize.txt"),
File("proguard-project.txt")
)
)
}
create("benchmark") {
// We provide the extra benchmark build type just for benchmarking purposes
initWith(buildTypes.getByName("release"))
matchingFallbacks += listOf("release")
}
}
lint {
baseline = File("lint-baseline.xml")
}
}
cargo {
module = "."
libname = "zcashwalletsdk"
targets = listOf(
"arm",
"arm64",
"x86",
"x86_64"
)
val minSdkVersion = project.property("ANDROID_MIN_SDK_VERSION").toString().toInt()
apiLevels = mapOf(
"arm" to minSdkVersion,
"arm64" to minSdkVersion,
"x86" to minSdkVersion,
"x86_64" to minSdkVersion,
)
profile = "release"
prebuiltToolchains = true
}
dependencies {
api(projects.lightwalletClientLib)
implementation(libs.androidx.annotation)
// Kotlin
implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.android)
androidTestImplementation(libs.androidx.multidex)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.androidx.test.junit)
androidTestImplementation(libs.androidx.test.core)
androidTestImplementation(libs.kotlin.test)
androidTestImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.zcashwalletplgn)
androidTestImplementation(libs.bip39)
}
tasks {
/*
* The Mozilla Rust Gradle plugin caches the native build data under the "target" directory,
* which does not normally get deleted during a clean. The following task and dependency solves
* that.
*/
getByName<Delete>("clean").dependsOn(create<Delete>("cleanRustBuildOutput") {
delete("target")
})
}
project.afterEvaluate {
val cargoTask = tasks.getByName("cargoBuild")
tasks.getByName("javaPreCompileDebug").dependsOn(cargoTask)
tasks.getByName("javaPreCompileRelease").dependsOn(cargoTask)
}
fun MinimalExternalModuleDependency.asCoordinateString() =
"${module.group}:${module.name}:${versionConstraint.displayName}"

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 7.4.0" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0)" variant="all" version="7.4.0">
</issues>

4
backend-lib/lint.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
</lint>

View File

@ -0,0 +1,3 @@
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}

View File

@ -0,0 +1,18 @@
# This improves obfuscation and moves non-public classes to their own namespace.
-repackageclasses 'cash.z.ecc.android.sdk.internal'
# This makes it easier to autocomplete methods in an IDE using this obfuscated library.
-keepparameternames
# The ProGuard manual recommends keeping these attributes for libraries.
-keepattributes EnclosingMethod,InnerClasses,Signature,Exceptions,*Annotation*
# Ensure that stacktraces are reversible.
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
# Keep the public interface of the library.
-keep public class cash.z.ecc.android.sdk.internal.Backend { public protected *; }
-keep public class cash.z.ecc.android.sdk.internal.Derivation { public protected *; }
-keep public class cash.z.ecc.android.sdk.internal.jni.RustBackend { public protected *; }
-keep public class cash.z.ecc.android.sdk.internal.model.* { public protected *; }

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- For code coverage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:name="androidx.multidex.MultiDexApplication" />
</manifest>

View File

@ -0,0 +1,26 @@
package cash.z.ecc.android.sdk.internal.jni
import cash.z.ecc.android.bip39.Mnemonics
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import kotlin.test.assertContentEquals
class RustDerivationToolTest {
companion object {
private const val SEED_PHRASE =
"kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree shock scan alpha just spot fluid toilet view dinner"
}
@Test
@OptIn(ExperimentalCoroutinesApi::class)
fun create_spending_key_does_not_mutate_passed_bytes() = runTest {
val bytesOne = Mnemonics.MnemonicCode(SEED_PHRASE).toEntropy()
val bytesTwo = Mnemonics.MnemonicCode(SEED_PHRASE).toEntropy()
RustDerivationTool.new().deriveUnifiedSpendingKey(bytesOne, networkId = 1, accountIndex = 0)
assertContentEquals(bytesTwo, bytesOne)
}
}

View File

@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -1,9 +1,7 @@
package cash.z.ecc.android.sdk.jni
package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
import java.io.File
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
/**
* Contract defining the exposed capabilities of the Rust backend.
@ -14,11 +12,9 @@ import java.io.File
// TODO [#920]: Tweak RustBackend public APIs to have void return values
// TODO [#920]: https://github.com/zcash/zcash-android-wallet-sdk/issues/920
@Suppress("TooManyFunctions")
internal interface Backend {
interface Backend {
val network: ZcashNetwork
val saplingParamDir: File
val networkId: Int
suspend fun initBlockMetaDb(): Int
@ -53,7 +49,7 @@ internal interface Backend {
suspend fun initDataDb(seed: ByteArray?): Int
suspend fun createAccount(seed: ByteArray): UnifiedSpendingKeyJni
suspend fun createAccount(seed: ByteArray): JniUnifiedSpendingKey
fun isValidShieldedAddr(addr: String): Boolean
@ -112,6 +108,6 @@ internal interface Backend {
index: Int,
script: ByteArray,
value: Long,
height: BlockHeight
height: Long
): Boolean
}

View File

@ -0,0 +1,44 @@
package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
interface Derivation {
fun deriveUnifiedAddress(
viewingKey: String,
networkId: Int
): String
fun deriveUnifiedAddress(
seed: ByteArray,
networkId: Int,
accountIndex: Int
): String
fun deriveUnifiedSpendingKey(
seed: ByteArray,
networkId: Int,
accountIndex: Int
): JniUnifiedSpendingKey
/**
* @return a unified full viewing key.
*/
fun deriveUnifiedFullViewingKey(
usk: JniUnifiedSpendingKey,
networkId: Int
): String
/**
* @param numberOfAccounts Use [DEFAULT_NUMBER_OF_ACCOUNTS] to derive a single key.
* @return an array of unified full viewing keys, one for each account.
*/
fun deriveUnifiedFullViewingKeys(
seed: ByteArray,
networkId: Int,
numberOfAccounts: Int
): Array<String>
companion object {
const val DEFAULT_NUMBER_OF_ACCOUNTS = 1
}
}

View File

@ -16,7 +16,7 @@ internal object SdkExecutors {
val DATABASE_IO = Executors.newSingleThreadExecutor()
}
internal object SdkDispatchers {
object SdkDispatchers {
/**
* Dispatcher used for database IO that's shared with the Rust native library.
*/

View File

@ -9,15 +9,15 @@ import java.io.FileInputStream
import java.security.DigestInputStream
import java.security.MessageDigest
internal suspend fun File.canWriteSuspend() = withContext(Dispatchers.IO) { canWrite() }
suspend fun File.canWriteSuspend() = withContext(Dispatchers.IO) { canWrite() }
suspend fun File.createNewFileSuspend() = withContext(Dispatchers.IO) { createNewFile() }
internal suspend fun File.deleteSuspend() = withContext(Dispatchers.IO) { delete() }
suspend fun File.deleteSuspend() = withContext(Dispatchers.IO) { delete() }
suspend fun File.deleteRecursivelySuspend() = withContext(Dispatchers.IO) { deleteRecursively() }
internal suspend fun File.existsSuspend() = withContext(Dispatchers.IO) { exists() }
suspend fun File.existsSuspend() = withContext(Dispatchers.IO) { exists() }
suspend fun File.inputStreamSuspend(): FileInputStream = withContext(Dispatchers.IO) { inputStream() }
@ -27,11 +27,11 @@ suspend fun File.listFilesSuspend(): Array<File>? = withContext(Dispatchers.IO)
suspend fun File.listSuspend(): Array<String>? = withContext(Dispatchers.IO) { list() }
internal suspend fun File.mkdirsSuspend() = withContext(Dispatchers.IO) { mkdirs() }
suspend fun File.mkdirsSuspend() = withContext(Dispatchers.IO) { mkdirs() }
suspend fun File.readBytesSuspend() = withContext(Dispatchers.IO) { readBytes() }
internal suspend fun File.renameToSuspend(dest: File) = withContext(Dispatchers.IO) { renameTo(dest) }
suspend fun File.renameToSuspend(dest: File) = withContext(Dispatchers.IO) { renameTo(dest) }
suspend fun File.writeBytesSuspend(byteArray: ByteArray) = withContext(Dispatchers.IO) { writeBytes(byteArray) }

View File

@ -1,12 +1,10 @@
package cash.z.ecc.android.sdk.jni
package cash.z.ecc.android.sdk.internal.jni
import cash.z.ecc.android.sdk.internal.Twig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.system.measureTimeMillis
/**
* Loads a native library once. This class is thread-safe.
@ -39,14 +37,7 @@ internal class NativeLibraryLoader(private val libraryName: String) {
private suspend fun loadNativeLibrary() {
runCatching {
Twig.debug { "Loading native library $libraryName" }
val loadTimeMillis = measureTimeMillis {
loadLibrarySuspend(libraryName)
}
Twig.debug { "Loading native library took $loadTimeMillis milliseconds" }
loadLibrarySuspend(libraryName)
isLoaded.set(true)
}.onFailure {
// Fail fast, because this is not a recoverable error

View File

@ -1,13 +1,11 @@
package cash.z.ecc.android.sdk.jni
package cash.z.ecc.android.sdk.internal.jni
import cash.z.ecc.android.sdk.internal.SaplingParamTool
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.SdkDispatchers
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.ext.deleteRecursivelySuspend
import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
import kotlinx.coroutines.withContext
import java.io.File
@ -17,12 +15,12 @@ import java.io.File
* should be used such as Wallet.kt or CompactBlockProcessor.kt.
*/
@Suppress("TooManyFunctions")
internal class RustBackend private constructor(
override val network: ZcashNetwork,
val birthdayHeight: BlockHeight,
val dataDbFile: File,
val fsBlockDbRoot: File,
override val saplingParamDir: File
class RustBackend private constructor(
override val networkId: Int,
private val dataDbFile: File,
private val fsBlockDbRoot: File,
private val saplingSpendFile: File,
private val saplingOutputFile: File,
) : Backend {
/**
@ -38,16 +36,14 @@ internal class RustBackend private constructor(
var cacheClearResult = true
var dataClearResult = true
if (clearCache) {
Twig.debug { "Deleting the cache files..." }
fsBlockDbRoot.deleteRecursivelySuspend().also { result ->
Twig.debug { "Deletion of the cache files ${if (result) "succeeded" else "failed"}!" }
// Twig.debug { "Deletion of the cache files ${if (result) "succeeded" else "failed"}!" }
cacheClearResult = result
}
}
if (clearDataDb) {
Twig.debug { "Deleting the data database..." }
dataDbFile.deleteSuspend().also { result ->
Twig.debug { "Deletion of the data database ${if (result) "succeeded" else "failed"}!" }
// Twig.debug { "Deletion of the data database ${if (result) "succeeded" else "failed"}!" }
dataClearResult = result
}
}
@ -68,16 +64,16 @@ internal class RustBackend private constructor(
initDataDb(
dataDbFile.absolutePath,
seed,
networkId = network.id
networkId = networkId
)
}
override suspend fun createAccount(seed: ByteArray): UnifiedSpendingKeyJni {
override suspend fun createAccount(seed: ByteArray): JniUnifiedSpendingKey {
return withContext(SdkDispatchers.DATABASE_IO) {
createAccount(
dataDbFile.absolutePath,
seed,
networkId = network.id
networkId = networkId
)
}
}
@ -90,7 +86,7 @@ internal class RustBackend private constructor(
initAccountsTableWithKeys(
dataDbFile.absolutePath,
keys,
networkId = network.id
networkId = networkId
)
}
}
@ -108,7 +104,7 @@ internal class RustBackend private constructor(
checkpointHash,
checkpointTime,
checkpointSaplingTree,
networkId = network.id
networkId = networkId
)
}
}
@ -118,7 +114,7 @@ internal class RustBackend private constructor(
getCurrentAddress(
dataDbFile.absolutePath,
account,
networkId = network.id
networkId = networkId
)
}
@ -131,7 +127,7 @@ internal class RustBackend private constructor(
listTransparentReceivers(
dbDataPath = dataDbFile.absolutePath,
account = account,
networkId = network.id
networkId = networkId
).asList()
}
}
@ -141,7 +137,7 @@ internal class RustBackend private constructor(
getBalance(
dataDbFile.absolutePath,
account,
networkId = network.id
networkId = networkId
)
}
@ -153,7 +149,7 @@ internal class RustBackend private constructor(
getVerifiedBalance(
dbDataPath = dataDbFile.absolutePath,
account = account,
networkId = network.id
networkId = networkId
)
}
@ -165,7 +161,7 @@ internal class RustBackend private constructor(
getReceivedMemoAsUtf8(
dataDbFile.absolutePath,
idNote,
networkId = network.id
networkId = networkId
)
}
@ -174,7 +170,7 @@ internal class RustBackend private constructor(
getSentMemoAsUtf8(
dataDbFile.absolutePath,
idNote,
networkId = network.id
networkId = networkId
)
}
@ -219,7 +215,7 @@ internal class RustBackend private constructor(
dbCachePath = fsBlockDbRoot.absolutePath,
dbDataPath = dataDbFile.absolutePath,
limit = limit ?: -1,
networkId = network.id
networkId = networkId
)
if (-1L == validationResult) {
@ -234,7 +230,7 @@ internal class RustBackend private constructor(
getVerifiedTransparentBalance(
dataDbFile.absolutePath,
address,
networkId = network.id
networkId = networkId
)
}
@ -243,7 +239,7 @@ internal class RustBackend private constructor(
getTotalTransparentBalance(
dataDbFile.absolutePath,
address,
networkId = network.id
networkId = networkId
)
}
@ -252,7 +248,7 @@ internal class RustBackend private constructor(
getNearestRewindHeight(
dataDbFile.absolutePath,
height,
networkId = network.id
networkId = networkId
)
}
@ -266,7 +262,7 @@ internal class RustBackend private constructor(
rewindToHeight(
dataDbFile.absolutePath,
height,
networkId = network.id
networkId = networkId
)
}
@ -276,7 +272,7 @@ internal class RustBackend private constructor(
fsBlockDbRoot.absolutePath,
dataDbFile.absolutePath,
limit ?: -1,
networkId = network.id
networkId = networkId
)
}
}
@ -286,7 +282,7 @@ internal class RustBackend private constructor(
decryptAndStoreTransaction(
dataDbFile.absolutePath,
tx,
networkId = network.id
networkId = networkId
)
}
@ -303,9 +299,9 @@ internal class RustBackend private constructor(
to,
value,
memo ?: ByteArray(0),
File(saplingParamDir, SaplingParamTool.SPEND_PARAM_FILE_NAME).absolutePath,
File(saplingParamDir, SaplingParamTool.OUTPUT_PARAM_FILE_NAME).absolutePath,
networkId = network.id,
spendParamsPath = saplingSpendFile.absolutePath,
outputParamsPath = saplingOutputFile.absolutePath,
networkId = networkId,
useZip317Fees = IS_USE_ZIP_317_FEES
)
}
@ -315,15 +311,14 @@ internal class RustBackend private constructor(
unifiedSpendingKey: ByteArray,
memo: ByteArray?
): Long {
Twig.debug { "TMP: shieldToAddress with db path: $dataDbFile, ${memo?.size}" }
return withContext(SdkDispatchers.DATABASE_IO) {
shieldToAddress(
dataDbFile.absolutePath,
unifiedSpendingKey,
memo ?: ByteArray(0),
File(saplingParamDir, SaplingParamTool.SPEND_PARAM_FILE_NAME).absolutePath,
File(saplingParamDir, SaplingParamTool.OUTPUT_PARAM_FILE_NAME).absolutePath,
networkId = network.id,
spendParamsPath = saplingSpendFile.absolutePath,
outputParamsPath = saplingOutputFile.absolutePath,
networkId = networkId,
useZip317Fees = IS_USE_ZIP_317_FEES
)
}
@ -335,7 +330,7 @@ internal class RustBackend private constructor(
index: Int,
script: ByteArray,
value: Long,
height: BlockHeight
height: Long
): Boolean = withContext(SdkDispatchers.DATABASE_IO) {
putUtxo(
dataDbFile.absolutePath,
@ -344,22 +339,22 @@ internal class RustBackend private constructor(
index,
script,
value,
height.value,
networkId = network.id
height,
networkId = networkId
)
}
override fun isValidShieldedAddr(addr: String) =
isValidShieldedAddress(addr, networkId = network.id)
isValidShieldedAddress(addr, networkId = networkId)
override fun isValidTransparentAddr(addr: String) =
isValidTransparentAddress(addr, networkId = network.id)
isValidTransparentAddress(addr, networkId = networkId)
override fun isValidUnifiedAddr(addr: String) =
isValidUnifiedAddress(addr, networkId = network.id)
isValidUnifiedAddress(addr, networkId = networkId)
override fun getBranchIdForHeight(height: Long): Long =
branchIdForHeight(height, networkId = network.id)
branchIdForHeight(height, networkId = networkId)
// /**
// * This is a proof-of-concept for doing Local RPC, where we are effectively using the JNI
@ -400,21 +395,21 @@ internal class RustBackend private constructor(
* Loads the library and initializes path variables. Although it is best to only call this
* function once, it is idempotent.
*/
suspend fun init(
suspend fun new(
fsBlockDbRoot: File,
dataDbFile: File,
saplingParamsDir: File,
zcashNetwork: ZcashNetwork,
birthdayHeight: BlockHeight
saplingSpendFile: File,
saplingOutputFile: File,
zcashNetworkId: Int,
): RustBackend {
loadLibrary()
return RustBackend(
zcashNetwork,
birthdayHeight,
zcashNetworkId,
dataDbFile = dataDbFile,
fsBlockDbRoot = fsBlockDbRoot,
saplingParamDir = saplingParamsDir
saplingSpendFile = saplingSpendFile,
saplingOutputFile = saplingOutputFile
)
}
@ -450,7 +445,7 @@ internal class RustBackend private constructor(
): Boolean
@JvmStatic
private external fun createAccount(dbDataPath: String, seed: ByteArray, networkId: Int): UnifiedSpendingKeyJni
private external fun createAccount(dbDataPath: String, seed: ByteArray, networkId: Int): JniUnifiedSpendingKey
@JvmStatic
private external fun getCurrentAddress(
@ -468,7 +463,7 @@ internal class RustBackend private constructor(
@JvmStatic
private external fun listTransparentReceivers(dbDataPath: String, account: Int, networkId: Int): Array<String>
internal fun validateUnifiedSpendingKey(bytes: ByteArray) =
fun validateUnifiedSpendingKey(bytes: ByteArray) =
isValidSpendingKey(bytes)
@JvmStatic

View File

@ -0,0 +1,78 @@
package cash.z.ecc.android.sdk.internal.jni
import cash.z.ecc.android.sdk.internal.Derivation
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
class RustDerivationTool private constructor() : Derivation {
override fun deriveUnifiedFullViewingKeys(
seed: ByteArray,
networkId: Int,
numberOfAccounts: Int
): Array<String> =
deriveUnifiedFullViewingKeysFromSeed(seed, numberOfAccounts, networkId = networkId)
override fun deriveUnifiedFullViewingKey(
usk: JniUnifiedSpendingKey,
networkId: Int
): String =
deriveUnifiedFullViewingKey(usk.bytes, networkId = networkId)
override fun deriveUnifiedSpendingKey(
seed: ByteArray,
networkId: Int,
accountIndex: Int
): JniUnifiedSpendingKey = deriveSpendingKey(seed, accountIndex, networkId = networkId)
override fun deriveUnifiedAddress(seed: ByteArray, networkId: Int, accountIndex: Int): String =
deriveUnifiedAddressFromSeed(seed, accountIndex = accountIndex, networkId = networkId)
/**
* Given a Unified Full Viewing Key string, return the associated Unified Address.
*
* @param viewingKey the viewing key to use for deriving the address. The viewing key is tied to
* a specific account so no account index is required.
*
* @return the address that corresponds to the viewing key.
*/
override fun deriveUnifiedAddress(
viewingKey: String,
networkId: Int
): String =
deriveUnifiedAddressFromViewingKey(viewingKey, networkId = networkId)
companion object {
suspend fun new(): Derivation {
RustBackend.loadLibrary()
return RustDerivationTool()
}
@JvmStatic
private external fun deriveSpendingKey(
seed: ByteArray,
account: Int,
networkId: Int
): JniUnifiedSpendingKey
@JvmStatic
private external fun deriveUnifiedFullViewingKeysFromSeed(
seed: ByteArray,
numberOfAccounts: Int,
networkId: Int
): Array<String>
@JvmStatic
private external fun deriveUnifiedFullViewingKey(usk: ByteArray, networkId: Int): String
@JvmStatic
private external fun deriveUnifiedAddressFromSeed(
seed: ByteArray,
accountIndex: Int,
networkId: Int
): String
@JvmStatic
private external fun deriveUnifiedAddressFromViewingKey(key: String, networkId: Int): String
}
}

View File

@ -37,7 +37,7 @@ class JniBlockMeta(
companion object {
private val UINT_RANGE = 0.toLong()..UInt.MAX_VALUE.toLong()
internal fun new(block: CompactBlockUnsafe): JniBlockMeta {
fun new(block: CompactBlockUnsafe): JniBlockMeta {
return JniBlockMeta(
height = block.height,
hash = block.hash,

View File

@ -1,4 +1,4 @@
package cash.z.ecc.android.sdk.jni
package cash.z.ecc.android.sdk.internal.model
import androidx.annotation.Keep
@ -12,7 +12,7 @@ import androidx.annotation.Keep
* export/import, or backup purposes.
*/
@Keep
class UnifiedSpendingKeyJni(
class JniUnifiedSpendingKey(
val account: Int,
/**
* The binary encoding of the [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending
@ -22,27 +22,17 @@ class UnifiedSpendingKeyJni(
* inherently unstable, and only intended to be passed between the SDK and the storage
* backend. Wallets **MUST NOT** allow this encoding to be exported or imported.
*/
private val bytes: ByteArray
val bytes: ByteArray
) {
/**
* The binary encoding of the [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending
* Key for [account].
*
* This encoding **MUST NOT** be exposed to users. It is an internal encoding that is
* inherently unstable, and only intended to be passed between the SDK and the storage
* backend. Wallets **MUST NOT** allow this encoding to be exported or imported.
*/
fun copyBytes() = bytes.copyOf()
// Override to prevent leaking key to logs
override fun toString() = "UnifiedSpendingKeyJni(account=$account)"
override fun toString() = "JniUnifiedSpendingKey(account=$account, bytes=***)"
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as UnifiedSpendingKeyJni
other as JniUnifiedSpendingKey
if (account != other.account) return false
if (!bytes.contentEquals(other.bytes)) return false

View File

@ -95,7 +95,7 @@ fn block_db(env: &JNIEnv<'_>, fsblockdb_root: JString<'_>) -> Result<FsBlockDb,
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initOnLoad(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initOnLoad(
_env: JNIEnv<'_>,
_: JClass<'_>,
) {
@ -134,7 +134,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initOnLoad(
///
/// Returns 0 if successful, or -1 otherwise.
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initBlockMetaDb(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initBlockMetaDb(
env: JNIEnv<'_>,
_: JClass<'_>,
fsblockdb_root: JString<'_>,
@ -160,7 +160,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initBlockMe
/// Returns 0 if successful, 1 if the seed must be provided in order to execute the requested
/// migrations, or -1 otherwise.
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initDataDb(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initDataDb(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -197,7 +197,7 @@ fn encode_usk(
let encoded = SecretVec::new(usk.to_bytes(Era::Orchard));
let bytes = env.byte_array_from_slice(encoded.expose_secret())?;
let output = env.new_object(
"cash/z/ecc/android/sdk/jni/UnifiedSpendingKeyJni",
"cash/z/ecc/android/sdk/internal/model/JniUnifiedSpendingKey",
"(I[B)V",
&[
JValue::Int(u32::from(account) as i32),
@ -241,7 +241,7 @@ fn decode_usk(env: &JNIEnv<'_>, usk: jbyteArray) -> Result<UnifiedSpendingKey, f
///
/// [ZIP 316]: https://zips.z.cash/zip-0316
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createAccount(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createAccount(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -268,7 +268,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createAccou
/// This should only be used in special cases for implementing wallet recovery; prefer
/// `RustBackend.createAccount` for normal account creation purposes.
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initAccountsTableWithKeys(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initAccountsTableWithKeys(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -315,7 +315,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initAccount
/// of the [`UnifiedSpendingKey`] for the newly created account. The caller should store
/// the returned spending key in a secure fashion.
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveSpendingKey(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveSpendingKey(
env: JNIEnv<'_>,
_: JClass<'_>,
seed: jbyteArray,
@ -340,7 +340,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveS
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveUnifiedFullViewingKeysFromSeed(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedFullViewingKeysFromSeed(
env: JNIEnv<'_>,
_: JClass<'_>,
seed: jbyteArray,
@ -379,7 +379,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveU
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveUnifiedAddressFromSeed(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedAddressFromSeed(
env: JNIEnv<'_>,
_: JClass<'_>,
seed: jbyteArray,
@ -413,7 +413,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveU
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveUnifiedAddressFromViewingKey(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedAddressFromViewingKey(
env: JNIEnv<'_>,
_: JClass<'_>,
ufvk_string: JString<'_>,
@ -445,7 +445,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveU
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveUnifiedFullViewingKey(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedFullViewingKey(
env: JNIEnv<'_>,
_: JClass<'_>,
usk: jbyteArray,
@ -467,7 +467,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_tool_DerivationTool_deriveU
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initBlocksTable(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initBlocksTable(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -509,7 +509,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_initBlocksT
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getCurrentAddress(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getCurrentAddress(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -553,7 +553,7 @@ impl zcash_address::TryFromRawAddress for UnifiedAddressParser {
/// Returns the transparent receiver within the given Unified Address, if any.
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getTransparentReceiverForUnifiedAddress(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getTransparentReceiverForUnifiedAddress(
env: JNIEnv<'_>,
_: JClass<'_>,
ua: JString<'_>,
@ -593,7 +593,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getTranspar
/// Returns the Sapling receiver within the given Unified Address, if any.
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getSaplingReceiverForUnifiedAddress(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getSaplingReceiverForUnifiedAddress(
env: JNIEnv<'_>,
_: JClass<'_>,
ua: JString<'_>,
@ -623,7 +623,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getSaplingR
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidSpendingKey(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidSpendingKey(
env: JNIEnv<'_>,
_: JClass<'_>,
usk: jbyteArray,
@ -636,7 +636,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidSpen
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidShieldedAddress(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidShieldedAddress(
env: JNIEnv<'_>,
_: JClass<'_>,
addr: JString<'_>,
@ -658,7 +658,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidShie
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidTransparentAddress(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidTransparentAddress(
env: JNIEnv<'_>,
_: JClass<'_>,
addr: JString<'_>,
@ -680,7 +680,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidTran
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidUnifiedAddress(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidUnifiedAddress(
env: JNIEnv<'_>,
_: JClass<'_>,
addr: JString<'_>,
@ -702,7 +702,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_isValidUnif
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getBalance(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getBalance(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -740,7 +740,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getBalance(
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getVerifiedTransparentBalance(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getVerifiedTransparentBalance(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -778,7 +778,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getVerified
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getTotalTransparentBalance(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getTotalTransparentBalance(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -820,7 +820,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getTotalTra
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getVerifiedBalance(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getVerifiedBalance(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -852,7 +852,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getVerified
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getReceivedMemoAsUtf8(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getReceivedMemoAsUtf8(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -879,7 +879,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getReceived
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getSentMemoAsUtf8(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getSentMemoAsUtf8(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -946,7 +946,7 @@ fn decode_blockmeta(env: &JNIEnv<'_>, obj: JObject<'_>) -> Result<BlockMeta, fai
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_writeBlockMetadata(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_writeBlockMetadata(
env: JNIEnv<'_>,
_: JClass<'_>,
db_cache: JString<'_>,
@ -978,7 +978,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_writeBlockM
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getLatestHeight(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getLatestHeight(
env: JNIEnv<'_>,
_: JClass<'_>,
fsblockdb_root: JString<'_>,
@ -1000,7 +1000,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getLatestHe
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_findBlockMetadata(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_findBlockMetadata(
env: JNIEnv<'_>,
_: JClass<'_>,
fsblockdb_root: JString<'_>,
@ -1023,7 +1023,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_findBlockMe
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_rewindBlockMetadataToHeight(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_rewindBlockMetadataToHeight(
env: JNIEnv<'_>,
_: JClass<'_>,
fsblockdb_root: JString<'_>,
@ -1046,7 +1046,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_rewindBlock
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_validateCombinedChain(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_validateCombinedChain(
env: JNIEnv<'_>,
_: JClass<'_>,
db_cache: JString<'_>,
@ -1084,7 +1084,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_validateCom
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getNearestRewindHeight(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getNearestRewindHeight(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -1120,7 +1120,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_getNearestR
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_rewindToHeight(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_rewindToHeight(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -1143,7 +1143,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_rewindToHei
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_scanBlocks(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_scanBlocks(
env: JNIEnv<'_>,
_: JClass<'_>,
db_cache: JString<'_>,
@ -1171,7 +1171,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_scanBlocks(
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_putUtxo(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_putUtxo(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -1218,7 +1218,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_putUtxo(
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_decryptAndStoreTransaction(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_decryptAndStoreTransaction(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -1271,7 +1271,7 @@ fn zip317_helper<Ctx, DbT, R>(
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createToAddress(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createToAddress(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -1363,7 +1363,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_createToAdd
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_shieldToAddress(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_shieldToAddress(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -1454,7 +1454,7 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_shieldToAdd
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_branchIdForHeight(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_branchIdForHeight(
env: JNIEnv<'_>,
_: JClass<'_>,
height: jlong,
@ -1499,7 +1499,7 @@ fn parse_network(value: u32) -> Result<Network, failure::Error> {
/// - Call [`zcashlc_free_keys`] to free the memory associated with the returned pointer
/// when done using it.
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_jni_RustBackend_listTransparentReceivers(
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_listTransparentReceivers(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,

View File

@ -58,7 +58,7 @@ class TestWallet(
private val context = InstrumentationRegistry.getInstrumentation().context
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
private val shieldedSpendingKey =
runBlocking { DerivationTool.deriveUnifiedSpendingKey(seed, network = network, account) }
runBlocking { DerivationTool.getInstance().deriveUnifiedSpendingKey(seed, network = network, account) }
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
context,
network,

View File

@ -178,7 +178,7 @@ class SampleCodeTest {
val amount = 0.123.convertZecToZatoshi()
val address = "ztestsapling1tklsjr0wyw0d58f3p7wufvrj2cyfv6q6caumyueadq8qvqt8lda6v6tpx474rfru9y6u75u7qnw"
val memo = "Test Transaction"
val spendingKey = DerivationTool.deriveUnifiedSpendingKey(seed, ZcashNetwork.Mainnet, Account.DEFAULT)
val spendingKey = DerivationTool.getInstance().deriveUnifiedSpendingKey(seed, ZcashNetwork.Mainnet, Account.DEFAULT)
synchronizer.sendToAddress(spendingKey, amount, address, memo)
}

View File

@ -134,7 +134,7 @@ internal fun ComposeActivity.Navigation() {
} else {
Transactions(
synchronizer = synchronizer,
onBack = { navController.popBackStackJustOnce(SEND) }
onBack = { navController.popBackStackJustOnce(TRANSACTIONS) }
)
}
}

View File

@ -84,7 +84,7 @@ class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
override fun onActionButtonClicked() {
viewLifecycleOwner.lifecycleScope.launch {
copyToClipboard(
DerivationTool.deriveUnifiedAddress(
DerivationTool.getInstance().deriveUnifiedAddress(
viewingKey.encoding,
ZcashNetwork.fromResources(requireApplicationContext())
),

View File

@ -69,7 +69,7 @@ class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
setOnClickListener {
lifecycleScope.launch {
sharedViewModel.synchronizerFlow.value?.shieldFunds(
DerivationTool.deriveUnifiedSpendingKey(
DerivationTool.getInstance().deriveUnifiedSpendingKey(
seed,
network,
Account.DEFAULT

View File

@ -47,14 +47,14 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@Suppress("MagicNumber")
val spendingKey = DerivationTool.deriveUnifiedSpendingKey(
val spendingKey = DerivationTool.getInstance().deriveUnifiedSpendingKey(
seed,
ZcashNetwork.fromResources(requireApplicationContext()),
Account(5)
)
// derive the key that allows you to view but not spend transactions
val viewingKey = DerivationTool.deriveUnifiedFullViewingKey(
val viewingKey = DerivationTool.getInstance().deriveUnifiedFullViewingKey(
spendingKey,
ZcashNetwork.fromResources(requireApplicationContext())
)
@ -91,9 +91,10 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
override fun onActionButtonClicked() {
lifecycleScope.launch {
copyToClipboard(
DerivationTool.deriveUnifiedFullViewingKeys(
DerivationTool.getInstance().deriveUnifiedFullViewingKeys(
seed,
ZcashNetwork.fromResources(requireApplicationContext())
ZcashNetwork.fromResources(requireApplicationContext()),
DerivationTool.DEFAULT_NUMBER_OF_ACCOUNTS
).first().encoding,
"UnifiedFullViewingKey copied to clipboard!"
)

View File

@ -90,7 +90,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
val bip39Seed = withContext(Dispatchers.IO) {
Mnemonics.MnemonicCode(it.seedPhrase.joinToString()).toSeed()
}
DerivationTool.deriveUnifiedSpendingKey(
DerivationTool.getInstance().deriveUnifiedSpendingKey(
seed = bip39Seed,
network = it.network,
account = Account.DEFAULT

View File

@ -10,6 +10,7 @@ Thankfully, the only thing an app developer has to be concerned with is the foll
# Modules
The SDK is broken down into several logical components, implemented as Gradle modules. At a high level, the modularization is:
* backend-lib — Native library interfaces. This is internal to the SDK.
* sdk-lib — Compiles all of the modules together for the SDK.
* sdk-incubator-lib — Incubator for new APIs that may eventually be promoted to the SDK. Classes are packaged to match the SDK, so that moving them will not necessarily change the API. While SDK clients can use classes in sdk-incubator-lib, they should anticipate a greater amount of public API churn.
* lightwallet-client-lib — Provides a set of Kotlin APIs for interacting with lightwalletd over the network.
@ -18,6 +19,7 @@ The SDK is broken down into several logical components, implemented as Gradle mo
```mermaid
flowchart TB;
backendLib[[backend-lib]] --> sdkLib[[sdk-lib]];
lightwalletClientLib[[lightwallet-client-lib]] --> sdkLib[[sdk-lib]];
sdkLib[[sdk-lib]] --> demoApp[[demo-app]];
sdkLib[[sdk-lib]] --> sdkIncubatorLib[[sdk-incubator-lib]];
@ -28,9 +30,11 @@ The SDK is broken down into several logical components, implemented as Gradle mo
# Data model
Before diving into some of the module specifics, it is helpful to provide some context on the data model representations as data flows between the different modules of this repository. There are multiple data representations, including:
1. Network — The wire representation for calls to and from the Lightwalletd server. These are generated by `protoc` at compile time. These are not generally a public API.
2. Unsafe — The representation provided as the output from `lightwallet-client-lib`. These values are not necessarily validated, hence the smurf naming with the suffix `Unsafe`. These are not generally a public API for clients of the SDK.
3. SDK — Objects exposed as a public API for clients of the SDK.
1. Primitive — The JNI generally requires primitive values (e.g. `Int`, `Long`, `String`, `ByteArray`). These are not generally part of the public API.
2. JNI — Objects that may cross the JNI boundary. These are not generally part of the public API.
3. Network — The wire representation for calls to and from the Lightwalletd server. These are generated by `protoc` at compile time. These are not generally a public API.
4. Unsafe — The representation provided as the output from `lightwallet-client-lib`. These values are not necessarily validated, hence the smurf naming with the suffix `Unsafe`. These are not generally a public API for clients of the SDK.
5. SDK — Objects exposed as a public API for clients of the SDK.
# lightwallet-client-lib
This library is a work-in-progress.

View File

@ -115,7 +115,8 @@ dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.android)
// TODO [#673]: Make `implementation` https://github.com/zcash/zcash-android-wallet-sdk/issues/673
// TODO [#1030]: Make sdk-lib gRPC objects free (and then make this `implementation`)
// TODO [#1030]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1030
api(libs.bundles.grpc)
// Tests

View File

@ -1,48 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="5" by="lint 4.2.1" client="gradle" variant="all" version="4.2.1">
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(unlessContains.toLowerCase()) == true)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="29"
column="29"/>
</issue>
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(unlessContains.toLowerCase()) == true)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="29"
column="68"/>
</issue>
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(ifContains.toLowerCase()) == false)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="33"
column="33"/>
</issue>
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(ifContains.toLowerCase()) == false)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="33"
column="68"/>
</issue>
<issues format="6" by="lint 7.4.0" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0)" variant="all" version="7.4.0">
</issues>

View File

@ -1,7 +1,3 @@
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
# https://github.com/grpc/grpc-java/blob/master/android/proguard-rules.txt
-keepclassmembers class io.grpc.okhttp.OkHttpChannelBuilder {
io.grpc.okhttp.OkHttpChannelBuilder forTarget(java.lang.String);
@ -10,6 +6,9 @@
io.grpc.okhttp.OkHttpChannelBuilder transportExecutor(java.util.concurrent.Executor);
}
# gRPC related - https://github.com/grpc/grpc-java/issues/6612
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
# Prevent OKHttp from causing warnings for consumers of the SDK
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket

View File

@ -46,7 +46,7 @@ class CompactBlockUnsafe(
}
}
private data class CompactBlockOutputsCounts(
data class CompactBlockOutputsCounts(
val saplingOutputsCount: UInt,
val orchardActionsCount: UInt
)

View File

@ -20,7 +20,11 @@ sealed class WalletFixture {
seed: String = seedPhrase,
network: ZcashNetwork,
account: Account = Account.DEFAULT
) = DerivationTool.deriveUnifiedSpendingKey(Mnemonics.MnemonicCode(seed).toEntropy(), network, account)
) = DerivationTool.getInstance().deriveUnifiedSpendingKey(
Mnemonics.MnemonicCode(seed).toEntropy(),
network,
account
)
@Suppress("MaxLineLength")
object Ben : WalletFixture() {
@ -34,9 +38,11 @@ sealed class WalletFixture {
ZcashNetwork.ID_TESTNET -> {
BlockHeight.new(zcashNetwork, 2170000L)
}
ZcashNetwork.ID_MAINNET -> {
BlockHeight.new(zcashNetwork, 1935000L)
}
else -> error("Unknown network $zcashNetwork")
}
@ -50,6 +56,7 @@ sealed class WalletFixture {
transparent = "tmP3uLtGx5GPddkq8a6ddmXhqJJ3vy6tpTE"
)
}
ZcashNetwork.ID_MAINNET -> {
Addresses(
unified =
@ -59,6 +66,7 @@ sealed class WalletFixture {
transparent = "t1JP7PHu72xHztsZiwH6cye4yvC9Prb3EvQ"
)
}
else -> error("Unknown network $zcashNetwork")
}
}
@ -76,9 +84,11 @@ sealed class WalletFixture {
ZcashNetwork.ID_TESTNET -> {
BlockHeight.new(zcashNetwork, 2170000L)
}
ZcashNetwork.ID_MAINNET -> {
BlockHeight.new(zcashNetwork, 1935000L)
}
else -> error("Unknown network $zcashNetwork")
}
@ -92,6 +102,7 @@ sealed class WalletFixture {
transparent = "tmCxJG72RWN66xwPtNgu4iKHpyysGrc7rEg"
)
}
ZcashNetwork.ID_MAINNET -> {
Addresses(
unified =
@ -101,6 +112,7 @@ sealed class WalletFixture {
transparent = "t1duiEGg7b39nfQee3XaTY4f5McqfyJKhBi"
)
}
else -> error("Unknown network $zcashNetwork")
}
}

View File

@ -5,7 +5,6 @@ plugins {
id("org.jetbrains.kotlin.plugin.allopen")
id("org.jetbrains.dokka")
id("org.mozilla.rust-android-gradle.rust-android")
id("wtf.emulator.gradle")
id("zcash-sdk.emulator-wtf-conventions")
@ -84,28 +83,9 @@ tasks.dokkaHtml.configure {
}
}
cargo {
module = "."
libname = "zcashwalletsdk"
targets = listOf(
"arm",
"arm64",
"x86",
"x86_64"
)
apiLevels = mapOf(
"arm" to 16,
"arm64" to 21,
"x86" to 16,
"x86_64" to 21,
)
profile = "release"
prebuiltToolchains = true
}
dependencies {
api(projects.lightwalletClientLib)
implementation(projects.backendLib)
implementation(libs.androidx.annotation)
implementation(libs.androidx.appcompat)
@ -149,22 +129,5 @@ dependencies {
androidTestImplementation(libs.bip39)
}
tasks {
/*
* The Mozilla Rust Gradle plugin caches the native build data under the "target" directory,
* which does not normally get deleted during a clean. The following task and dependency solves
* that.
*/
getByName<Delete>("clean").dependsOn(create<Delete>("cleanRustBuildOutput") {
delete("target")
})
}
project.afterEvaluate {
val cargoTask = tasks.getByName("cargoBuild")
tasks.getByName("javaPreCompileDebug").dependsOn(cargoTask)
tasks.getByName("javaPreCompileRelease").dependsOn(cargoTask)
}
fun MinimalExternalModuleDependency.asCoordinateString() =
"${module.group}:${module.name}:${versionConstraint.displayName}"

View File

@ -1,48 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="5" by="lint 4.2.1" client="gradle" variant="all" version="4.2.1">
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(unlessContains.toLowerCase()) == true)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="29"
column="29"/>
</issue>
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(unlessContains.toLowerCase()) == true)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="29"
column="68"/>
</issue>
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(ifContains.toLowerCase()) == false)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="33"
column="33"/>
</issue>
<issue
id="DefaultLocale"
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
errorLine1=" (t.message?.toLowerCase()?.contains(ifContains.toLowerCase()) == false)"
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/java/cash/z/ecc/android/sdk/ext/Ext.kt"
line="33"
column="68"/>
</issue>
<issues format="6" by="lint 7.4.0" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0)" variant="all" version="7.4.0">
</issues>

View File

@ -1,24 +0,0 @@
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
# https://github.com/grpc/grpc-java/blob/master/android/proguard-rules.txt
-keepclassmembers class io.grpc.okhttp.OkHttpChannelBuilder {
io.grpc.okhttp.OkHttpChannelBuilder forTarget(java.lang.String);
io.grpc.okhttp.OkHttpChannelBuilder scheduledExecutorService(java.util.concurrent.ScheduledExecutorService);
io.grpc.okhttp.OkHttpChannelBuilder sslSocketFactory(javax.net.ssl.SSLSocketFactory);
io.grpc.okhttp.OkHttpChannelBuilder transportExecutor(java.util.concurrent.Executor);
}
# gRPC related - https://github.com/grpc/grpc-java/issues/6612
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
# Prevent OKHttp from causing warnings for consumers of the SDK
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
-dontwarn org.conscrypt.Conscrypt
-dontwarn org.conscrypt.Conscrypt$Version
-dontwarn org.conscrypt.ConscryptHostnameVerifier
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE

View File

@ -19,6 +19,5 @@
-keep public class cash.z.ecc.android.sdk.db.entity.* { public protected *; }
-keep public class cash.z.ecc.android.sdk.exception.* { public protected *; }
-keep public class cash.z.ecc.android.sdk.ext.* { public protected *; }
-keep public class cash.z.ecc.android.sdk.jni.* { public protected *; }
-keep public class cash.z.ecc.android.sdk.tool.* { public protected *; }
-keep public class cash.z.ecc.android.sdk.type.* { public protected *; }

View File

@ -1,8 +1,9 @@
package cash.z.ecc.android.sdk.db
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.internal.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.internal.ext.existsSuspend
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.test.getAppContext
import cash.z.ecc.fixture.DatabaseCacheFilesRootFixture
import cash.z.ecc.fixture.DatabaseNameFixture
@ -124,28 +125,28 @@ class DatabaseCoordinatorTest {
DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_DATA_NAME)
)
assertTrue(originalDbFile.existsSuspend())
assertTrue(originalDbJournalFile.existsSuspend())
assertTrue(originalDbWalFile.existsSuspend())
assertTrue(originalDbFile.exists())
assertTrue(originalDbJournalFile.exists())
assertTrue(originalDbWalFile.exists())
assertFalse(expectedDbFile.existsSuspend())
assertFalse(expectedDbJournalFile.existsSuspend())
assertFalse(expectedDbWalFile.existsSuspend())
assertFalse(expectedDbFile.exists())
assertFalse(expectedDbJournalFile.exists())
assertFalse(expectedDbWalFile.exists())
dbCoordinator.dataDbFile(
DatabaseNameFixture.TEST_DB_NETWORK,
DatabaseNameFixture.TEST_DB_ALIAS
).also { resultFile ->
assertTrue(resultFile.existsSuspend())
assertTrue(resultFile.exists())
assertEquals(expectedDbFile.absolutePath, resultFile.absolutePath)
assertTrue(expectedDbFile.existsSuspend())
assertTrue(expectedDbJournalFile.existsSuspend())
assertTrue(expectedDbWalFile.existsSuspend())
assertTrue(expectedDbFile.exists())
assertTrue(expectedDbJournalFile.exists())
assertTrue(expectedDbWalFile.exists())
assertFalse(originalDbFile.existsSuspend())
assertFalse(originalDbJournalFile.existsSuspend())
assertFalse(originalDbWalFile.existsSuspend())
assertFalse(originalDbFile.exists())
assertFalse(originalDbJournalFile.exists())
assertFalse(originalDbWalFile.exists())
}
}
@ -186,14 +187,14 @@ class DatabaseCoordinatorTest {
fileName = DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_DATA_NAME)
)
assertTrue(dbFile.existsSuspend())
assertTrue(dbJournalFile.existsSuspend())
assertTrue(dbWalFile.existsSuspend())
assertTrue(dbFile.exists())
assertTrue(dbJournalFile.exists())
assertTrue(dbWalFile.exists())
dbCoordinator.deleteDatabases(DatabaseNameFixture.TEST_DB_NETWORK, DatabaseNameFixture.TEST_DB_ALIAS).also {
assertFalse(dbFile.existsSuspend())
assertFalse(dbJournalFile.existsSuspend())
assertFalse(dbWalFile.existsSuspend())
assertFalse(dbFile.exists())
assertFalse(dbJournalFile.exists())
assertFalse(dbWalFile.exists())
}
}
@ -276,13 +277,13 @@ class DatabaseCoordinatorTest {
)
// check all files in place
assertTrue(olderLegacyDbFile.existsSuspend())
assertTrue(olderLegacyDbJournalFile.existsSuspend())
assertTrue(olderLegacyDbWalFile.existsSuspend())
assertTrue(olderLegacyDbFile.exists())
assertTrue(olderLegacyDbJournalFile.exists())
assertTrue(olderLegacyDbWalFile.exists())
assertTrue(newerLegacyDbFile.existsSuspend())
assertTrue(newerLegacyDbJournalFile.existsSuspend())
assertTrue(newerLegacyDbWalFile.existsSuspend())
assertTrue(newerLegacyDbFile.exists())
assertTrue(newerLegacyDbJournalFile.exists())
assertTrue(newerLegacyDbWalFile.exists())
// once we access the latest file system blocks storage root directory, all the legacy database files should
// be removed
@ -290,13 +291,41 @@ class DatabaseCoordinatorTest {
network = DatabaseNameFixture.TEST_DB_NETWORK,
alias = DatabaseNameFixture.TEST_DB_ALIAS
).also {
assertFalse(olderLegacyDbFile.existsSuspend())
assertFalse(olderLegacyDbJournalFile.existsSuspend())
assertFalse(olderLegacyDbWalFile.existsSuspend())
assertFalse(olderLegacyDbFile.exists())
assertFalse(olderLegacyDbJournalFile.exists())
assertFalse(olderLegacyDbWalFile.exists())
assertFalse(newerLegacyDbFile.existsSuspend())
assertFalse(newerLegacyDbJournalFile.existsSuspend())
assertFalse(newerLegacyDbWalFile.existsSuspend())
assertFalse(newerLegacyDbFile.exists())
assertFalse(newerLegacyDbJournalFile.exists())
assertFalse(newerLegacyDbWalFile.exists())
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
@SmallTest
fun data_db_path() = runTest {
val coordinator = DatabaseCoordinator.getInstance(ApplicationProvider.getApplicationContext())
val dataDbFile = coordinator.dataDbFile(ZcashNetwork.Testnet, "TestWallet")
assertTrue(
"Invalid DataDB file",
dataDbFile.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_DATA_NAME}"
)
)
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
@SmallTest
fun cache_path() = runTest {
val coordinator = DatabaseCoordinator.getInstance(ApplicationProvider.getApplicationContext())
val cache = coordinator.fsBlockDbRoot(ZcashNetwork.Testnet, "TestWallet")
assertTrue(
"Invalid CacheDB file",
cache.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_FS_BLOCK_DB_ROOT_NAME}"
)
)
}
}

View File

@ -1,9 +1,10 @@
package cash.z.ecc.android.sdk.fixture
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.sdk.internal.deriveUnifiedSpendingKey
import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
object WalletFixture {
val NETWORK = ZcashNetwork.Mainnet
@ -14,5 +15,5 @@ object WalletFixture {
seed: String = SEED_PHRASE,
network: ZcashNetwork = NETWORK,
account: Account = Account.DEFAULT
) = DerivationTool.deriveUnifiedSpendingKey(Mnemonics.MnemonicCode(seed).toEntropy(), network, account)
) = RustDerivationTool.new().deriveUnifiedSpendingKey(Mnemonics.MnemonicCode(seed).toEntropy(), network, account)
}

View File

@ -1,48 +0,0 @@
package cash.z.ecc.android.sdk.integration
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.DefaultSynchronizerFactory
import cash.z.ecc.android.sdk.internal.SaplingParamTool
import cash.z.ecc.android.sdk.internal.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.util.TestWallet
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertTrue
import org.junit.Test
class SynchronizerFactoryTest {
@Test
@SmallTest
fun testFilePaths() {
val rustBackend = runBlocking {
val coordinator = DatabaseCoordinator.getInstance(ApplicationProvider.getApplicationContext())
DefaultSynchronizerFactory.defaultRustBackend(
ZcashNetwork.Testnet,
"TestWallet",
TestWallet.Backups.SAMPLE_WALLET.testnetBirthday,
SaplingParamTool.new(ApplicationProvider.getApplicationContext()),
coordinator
)
}
assertTrue(
"Invalid DataDB file",
rustBackend.dataDbFile.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_DATA_NAME}"
)
)
assertTrue(
"Invalid CacheDB file",
rustBackend.fsBlockDbRoot.absolutePath.endsWith(
"no_backup/co.electricoin.zcash/TestWallet_testnet_${DatabaseCoordinator.DB_FS_BLOCK_DB_ROOT_NAME}"
)
)
assertTrue(
"Invalid CacheDB params dir",
rustBackend.saplingParamDir.endsWith(
"no_backup/co.electricoin.zcash"
)
)
}
}

View File

@ -101,7 +101,7 @@ class TestnetIntegrationTest : ScopedTest() {
}
private suspend fun sendFunds(): Boolean {
val spendingKey = DerivationTool.deriveUnifiedSpendingKey(seed, synchronizer.network, Account.DEFAULT)
val spendingKey = DerivationTool.getInstance().deriveUnifiedSpendingKey(seed, synchronizer.network, Account.DEFAULT)
log("sending to address")
synchronizer.sendToAddress(
spendingKey,

View File

@ -2,6 +2,13 @@ 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.android.sdk.internal.model.ext.KEY_EPOCH_SECONDS
import cash.z.ecc.android.sdk.internal.model.ext.KEY_HASH
import cash.z.ecc.android.sdk.internal.model.ext.KEY_HEIGHT
import cash.z.ecc.android.sdk.internal.model.ext.KEY_TREE
import cash.z.ecc.android.sdk.internal.model.ext.KEY_VERSION
import cash.z.ecc.android.sdk.internal.model.ext.VERSION_1
import cash.z.ecc.android.sdk.internal.model.ext.from
import cash.z.ecc.fixture.CheckpointFixture
import cash.z.ecc.fixture.toJson
import org.json.JSONObject

View File

@ -1,5 +1,6 @@
package cash.z.ecc.android.sdk.internal
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.MediumTest
import androidx.test.filters.SmallTest
import cash.z.ecc.android.sdk.exception.TransactionEncoderException
@ -11,6 +12,7 @@ import cash.z.ecc.fixture.SaplingParamsFixture
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import java.io.File
@ -168,4 +170,17 @@ class SaplingParamToolBasicTest {
saplingParamTool.ensureParams(SaplingParamToolFixture.PARAMS_DIRECTORY)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
@SmallTest
fun sapling_params_path() = runTest {
val paramsDir = SaplingParamTool.new(ApplicationProvider.getApplicationContext()).properties.paramsDirectory
Assert.assertTrue(
"Invalid CacheDB params dir",
paramsDir.endsWith(
"no_backup/co.electricoin.zcash"
)
)
}
}

View File

@ -1,10 +1,10 @@
package cash.z.ecc.android.sdk.internal.storage.block
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.ext.deleteRecursivelySuspend
import cash.z.ecc.android.sdk.internal.ext.existsSuspend
import cash.z.ecc.android.sdk.internal.ext.listSuspend
import cash.z.ecc.android.sdk.internal.ext.mkdirsSuspend
import cash.z.ecc.android.sdk.jni.Backend
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.fixture.FakeRustBackendFixture

View File

@ -2,6 +2,10 @@ package cash.z.ecc.android.sdk.jni
import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.getBranchIdForHeight
import cash.z.ecc.android.sdk.internal.jni.RustBackend
import cash.z.ecc.android.sdk.internal.network
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
import kotlinx.coroutines.runBlocking
@ -47,21 +51,21 @@ class BranchIdTest internal constructor(
// However, due to quirks on certain devices, we created this test at the Android level,
// as a sanity check
val testnetBackend = runBlocking {
RustBackend.init(
RustBackend.new(
File(""),
File(""),
File(""),
ZcashNetwork.Testnet,
ZcashNetwork.Testnet.saplingActivationHeight
File(""),
ZcashNetwork.Testnet.id,
)
}
val mainnetBackend = runBlocking {
RustBackend.init(
RustBackend.new(
File(""),
File(""),
File(""),
ZcashNetwork.Mainnet,
ZcashNetwork.Mainnet.saplingActivationHeight
File(""),
ZcashNetwork.Mainnet.id,
)
}
return listOf(

View File

@ -5,8 +5,11 @@ import cash.z.ecc.android.bip39.Mnemonics.MnemonicCode
import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.annotation.MaintainedTest
import cash.z.ecc.android.sdk.annotation.TestPurpose
import cash.z.ecc.android.sdk.internal.Derivation
import cash.z.ecc.android.sdk.internal.deriveUnifiedAddress
import cash.z.ecc.android.sdk.internal.deriveUnifiedFullViewingKeysTypesafe
import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.BeforeClass
@ -21,10 +24,15 @@ class TransparentTest(val expected: Expected, val network: ZcashNetwork) {
@Test
fun deriveUnifiedFullViewingKeysFromSeedTest() = runBlocking {
val ufvks = DerivationTool.deriveUnifiedFullViewingKeys(SEED, network = network)
val ufvks = RustDerivationTool.new().deriveUnifiedFullViewingKeysTypesafe(
SEED,
network = network,
numberOfAccounts =
Derivation.DEFAULT_NUMBER_OF_ACCOUNTS
)
assertEquals(1, ufvks.size)
val ufvk = ufvks.first()
assertEquals(expected.uAddr, DerivationTool.deriveUnifiedAddress(ufvk.encoding, network = network))
assertEquals(expected.uAddr, RustDerivationTool.new().deriveUnifiedAddress(ufvk.encoding, network = network))
// TODO: If we need this, change DerivationTool to derive from the UFVK instead of the public key.
// assertEquals(expected.tAddr, DerivationTool.deriveTransparentAddressFromPublicKey(ufvk.encoding,
// network = network))

View File

@ -1,22 +0,0 @@
package cash.z.ecc.android.sdk.tool
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.sdk.fixture.WalletFixture
import cash.z.ecc.android.sdk.model.Account
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import kotlin.test.assertContentEquals
class DerivationToolTest {
@Test
@OptIn(ExperimentalCoroutinesApi::class)
fun create_spending_key_does_not_mutate_passed_bytes() = runTest {
val bytesOne = Mnemonics.MnemonicCode(WalletFixture.SEED_PHRASE).toEntropy()
val bytesTwo = Mnemonics.MnemonicCode(WalletFixture.SEED_PHRASE).toEntropy()
DerivationTool.deriveUnifiedSpendingKey(bytesOne, WalletFixture.NETWORK, Account.DEFAULT)
assertContentEquals(bytesTwo, bytesOne)
}
}

View File

@ -1,9 +1,10 @@
package cash.z.ecc.android.sdk.util
import cash.z.ecc.android.sdk.internal.deriveUnifiedAddress
import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.test.readFileLinesInFlow
import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
@ -31,7 +32,7 @@ class AddressGeneratorUtil {
.map { seedPhrase ->
mnemonics.toSeed(seedPhrase.toCharArray())
}.map { seed ->
DerivationTool.deriveUnifiedAddress(seed, ZcashNetwork.Mainnet, Account.DEFAULT)
RustDerivationTool.new().deriveUnifiedAddress(seed, ZcashNetwork.Mainnet, Account.DEFAULT)
}.collect { address ->
println("xrxrx2\t$address")
assertTrue(address.startsWith("u1"))

View File

@ -6,13 +6,14 @@ import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.deriveUnifiedSpendingKey
import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
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.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import co.electriccoin.lightwallet.client.model.LightWalletEndpoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
@ -58,7 +59,7 @@ class TestWallet(
private val context = InstrumentationRegistry.getInstrumentation().context
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
private val spendingKey =
runBlocking { DerivationTool.deriveUnifiedSpendingKey(seed, network = network, account) }
runBlocking { RustDerivationTool.new().deriveUnifiedSpendingKey(seed, network = network, account) }
val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
context,
network,

View File

@ -1,12 +1,12 @@
package cash.z.ecc.fixture
import cash.z.ecc.android.sdk.internal.KEY_EPOCH_SECONDS
import cash.z.ecc.android.sdk.internal.KEY_HASH
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_VERSION
import cash.z.ecc.android.sdk.internal.VERSION_1
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.model.ext.KEY_EPOCH_SECONDS
import cash.z.ecc.android.sdk.internal.model.ext.KEY_HASH
import cash.z.ecc.android.sdk.internal.model.ext.KEY_HEIGHT
import cash.z.ecc.android.sdk.internal.model.ext.KEY_TREE
import cash.z.ecc.android.sdk.internal.model.ext.KEY_VERSION
import cash.z.ecc.android.sdk.internal.model.ext.VERSION_1
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
import org.json.JSONObject

View File

@ -1,15 +1,11 @@
package cash.z.ecc.fixture
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.jni.Backend
import cash.z.ecc.android.sdk.jni.UnifiedSpendingKeyJni
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
import java.io.File
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
internal class FakeRustBackend(
override val network: ZcashNetwork,
override val saplingParamDir: File,
override val networkId: Int,
val metadata: MutableList<JniBlockMeta>
) : Backend {
@ -34,6 +30,17 @@ internal class FakeRustBackend(
TODO("Not yet implemented")
}
override suspend fun putUtxo(
tAddress: String,
txId: ByteArray,
index: Int,
script: ByteArray,
value: Long,
height: Long
): Boolean {
TODO("Not yet implemented")
}
override suspend fun findBlockMetadata(height: Long): JniBlockMeta? {
return metadata.findLast { it.height == height }
}
@ -78,7 +85,7 @@ internal class FakeRustBackend(
override suspend fun initDataDb(seed: ByteArray?): Int =
error("Intentionally not implemented in mocked FakeRustBackend implementation.")
override suspend fun createAccount(seed: ByteArray): UnifiedSpendingKeyJni =
override suspend fun createAccount(seed: ByteArray): JniUnifiedSpendingKey =
error("Intentionally not implemented in mocked FakeRustBackend implementation.")
override fun isValidShieldedAddr(addr: String): Boolean =
@ -128,13 +135,4 @@ internal class FakeRustBackend(
override suspend fun scanBlocks(limit: Long?): Boolean =
error("Intentionally not implemented in mocked FakeRustBackend implementation.")
override suspend fun putUtxo(
tAddress: String,
txId: ByteArray,
index: Int,
script: ByteArray,
value: Long,
height: BlockHeight
): Boolean = error("Intentionally not implemented in mocked FakeRustBackend implementation.")
}

View File

@ -2,20 +2,16 @@ package cash.z.ecc.fixture
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.model.ZcashNetwork
import java.io.File
internal class FakeRustBackendFixture {
private val DEFAULT_SAPLING_PARAM_DIR = File(DatabasePathFixture.new())
private val DEFAULT_NETWORK = ZcashNetwork.Testnet
fun new(
saplingParamDir: File = DEFAULT_SAPLING_PARAM_DIR,
network: ZcashNetwork = DEFAULT_NETWORK,
metadata: MutableList<JniBlockMeta> = mutableListOf()
) = FakeRustBackend(
saplingParamDir = saplingParamDir,
network = network,
networkId = network.id,
metadata = metadata
)
}

View File

@ -17,15 +17,17 @@ import cash.z.ecc.android.sdk.exception.TransactionEncoderException
import cash.z.ecc.android.sdk.exception.TransactionSubmitException
import cash.z.ecc.android.sdk.ext.ConsensusBranchId
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.SaplingParamTool
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader
import cash.z.ecc.android.sdk.internal.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.internal.db.derived.DbDerivedDataRepository
import cash.z.ecc.android.sdk.internal.db.derived.DerivedDataDb
import cash.z.ecc.android.sdk.internal.ext.isNullOrEmpty
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.isNullOrEmpty
import cash.z.ecc.android.sdk.internal.jni.RustBackend
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.repository.CompactBlockRepository
import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository
@ -34,7 +36,6 @@ import cash.z.ecc.android.sdk.internal.transaction.OutboundTransactionManager
import cash.z.ecc.android.sdk.internal.transaction.OutboundTransactionManagerImpl
import cash.z.ecc.android.sdk.internal.transaction.TransactionEncoder
import cash.z.ecc.android.sdk.internal.transaction.TransactionEncoderImpl
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.PercentDecimal
@ -69,6 +70,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.CoroutineContext
@ -92,7 +94,7 @@ class SdkSynchronizer private constructor(
private val storage: DerivedDataRepository,
private val txManager: OutboundTransactionManager,
val processor: CompactBlockProcessor,
private val rustBackend: RustBackend
private val backend: Backend
) : CloseableSynchronizer {
companion object {
@ -116,7 +118,7 @@ class SdkSynchronizer private constructor(
repository: DerivedDataRepository,
txManager: OutboundTransactionManager,
processor: CompactBlockProcessor,
rustBackend: RustBackend
backend: Backend
): CloseableSynchronizer {
val synchronizerKey = SynchronizerKey(zcashNetwork, alias)
@ -128,7 +130,7 @@ class SdkSynchronizer private constructor(
repository,
txManager,
processor,
rustBackend
backend
).apply {
instances[synchronizerKey] = InstanceState.Active
@ -311,10 +313,10 @@ class SdkSynchronizer private constructor(
return storage.getNoteIds(transactionOverview.id).map {
when (transactionOverview.isSentTransaction) {
true -> {
rustBackend.getSentMemoAsUtf8(it)
backend.getSentMemoAsUtf8(it)
}
false -> {
rustBackend.getReceivedMemoAsUtf8(it)
backend.getReceivedMemoAsUtf8(it)
}
}
}.filterNotNull()
@ -405,7 +407,6 @@ class SdkSynchronizer private constructor(
lastScanTime = now
SYNCED
}
is Stopped -> STOPPED
is Disconnected -> DISCONNECTED
is Syncing, Initialized -> SYNCING
@ -509,25 +510,25 @@ class SdkSynchronizer private constructor(
// Not ready to be a public API; internal for testing only
internal suspend fun createAccount(seed: ByteArray): UnifiedSpendingKey =
CompactBlockProcessor.createAccount(rustBackend, seed)
CompactBlockProcessor.createAccount(backend, seed)
/**
* Returns the current Unified Address for this account.
*/
override suspend fun getUnifiedAddress(account: Account): String =
CompactBlockProcessor.getCurrentAddress(rustBackend, account)
CompactBlockProcessor.getCurrentAddress(backend, account)
/**
* Returns the legacy Sapling address corresponding to the current Unified Address for this account.
*/
override suspend fun getSaplingAddress(account: Account): String =
CompactBlockProcessor.getLegacySaplingAddress(rustBackend, account)
CompactBlockProcessor.getLegacySaplingAddress(backend, account)
/**
* Returns the legacy transparent address corresponding to the current Unified Address for this account.
*/
override suspend fun getTransparentAddress(account: Account): String =
CompactBlockProcessor.getTransparentAddress(rustBackend, account)
CompactBlockProcessor.getTransparentAddress(backend, account)
@Throws(TransactionEncoderException::class, TransactionSubmitException::class)
override suspend fun sendToAddress(
@ -557,7 +558,7 @@ class SdkSynchronizer private constructor(
memo: String
): Long {
Twig.debug { "Initializing shielding transaction" }
val tAddr = CompactBlockProcessor.getTransparentAddress(rustBackend, usk.account)
val tAddr = CompactBlockProcessor.getTransparentAddress(backend, usk.account)
val tBalance = processor.getUtxoCacheBalance(tAddr)
val encodedTx = txManager.encode(
@ -628,26 +629,26 @@ class SdkSynchronizer private constructor(
*/
internal object DefaultSynchronizerFactory {
internal suspend fun defaultRustBackend(
internal suspend fun defaultBackend(
network: ZcashNetwork,
alias: String,
blockHeight: BlockHeight,
saplingParamTool: SaplingParamTool,
coordinator: DatabaseCoordinator
): RustBackend {
return RustBackend.init(
): Backend {
return RustBackend.new(
coordinator.fsBlockDbRoot(network, alias),
coordinator.dataDbFile(network, alias),
saplingParamTool.properties.paramsDirectory,
network,
blockHeight
saplingOutputFile = saplingParamTool.outputParamsFile,
saplingSpendFile = saplingParamTool.spendParamsFile,
zcashNetworkId = network.id
)
}
@Suppress("LongParameterList")
internal suspend fun defaultDerivedDataRepository(
context: Context,
rustBackend: RustBackend,
rustBackend: Backend,
databaseFile: File,
zcashNetwork: ZcashNetwork,
checkpoint: Checkpoint,
seed: ByteArray?,
@ -657,6 +658,7 @@ internal object DefaultSynchronizerFactory {
DerivedDataDb.new(
context,
rustBackend,
databaseFile,
zcashNetwork,
checkpoint,
seed,
@ -664,19 +666,20 @@ internal object DefaultSynchronizerFactory {
)
)
internal suspend fun defaultFileCompactBlockRepository(rustBackend: RustBackend): CompactBlockRepository =
internal suspend fun defaultCompactBlockRepository(blockCacheRoot: File, backend: Backend): CompactBlockRepository =
FileCompactBlockRepository.new(
rustBackend
blockCacheRoot,
backend
)
fun defaultService(context: Context, lightWalletEndpoint: LightWalletEndpoint): LightWalletClient =
LightWalletClient.new(context, lightWalletEndpoint)
internal fun defaultEncoder(
rustBackend: RustBackend,
backend: Backend,
saplingParamTool: SaplingParamTool,
repository: DerivedDataRepository
): TransactionEncoder = TransactionEncoderImpl(rustBackend, saplingParamTool, repository)
): TransactionEncoder = TransactionEncoderImpl(backend, saplingParamTool, repository)
fun defaultDownloader(
service: LightWalletClient,
@ -689,19 +692,20 @@ internal object DefaultSynchronizerFactory {
): OutboundTransactionManager {
return OutboundTransactionManagerImpl.new(
encoder,
service,
service
)
}
internal fun defaultProcessor(
rustBackend: RustBackend,
backend: Backend,
downloader: CompactBlockDownloader,
repository: DerivedDataRepository
repository: DerivedDataRepository,
birthdayHeight: BlockHeight
): CompactBlockProcessor = CompactBlockProcessor(
downloader,
repository,
rustBackend,
rustBackend.birthdayHeight
backend,
birthdayHeight
)
}

View File

@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk
import android.content.Context
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.internal.Derivation
import cash.z.ecc.android.sdk.internal.SaplingParamTool
import cash.z.ecc.android.sdk.internal.db.DatabaseCoordinator
import cash.z.ecc.android.sdk.model.Account
@ -412,7 +413,7 @@ interface Synchronizer {
* If customized initialization is required (e.g. for dependency injection or testing), see
* [DefaultSynchronizerFactory].
*/
@Suppress("LongParameterList")
@Suppress("LongParameterList", "LongMethod")
suspend fun new(
context: Context,
zcashNetwork: ZcashNetwork,
@ -437,29 +438,29 @@ interface Synchronizer {
// The pending transaction database no longer exists, so we can delete the file
coordinator.deletePendingTransactionDatabase(zcashNetwork, alias)
val rustBackend = DefaultSynchronizerFactory.defaultRustBackend(
val backend = DefaultSynchronizerFactory.defaultBackend(
zcashNetwork,
alias,
loadedCheckpoint.height,
saplingParamTool,
coordinator
)
val blockStore =
DefaultSynchronizerFactory
.defaultFileCompactBlockRepository(rustBackend)
.defaultCompactBlockRepository(coordinator.fsBlockDbRoot(zcashNetwork, alias), backend)
val viewingKeys = seed?.let {
DerivationTool.deriveUnifiedFullViewingKeys(
DerivationTool.getInstance().deriveUnifiedFullViewingKeys(
seed,
zcashNetwork,
1
Derivation.DEFAULT_NUMBER_OF_ACCOUNTS
).toList()
} ?: emptyList()
val repository = DefaultSynchronizerFactory.defaultDerivedDataRepository(
applicationContext,
rustBackend,
backend,
coordinator.dataDbFile(zcashNetwork, alias),
zcashNetwork,
loadedCheckpoint,
seed,
@ -467,10 +468,19 @@ interface Synchronizer {
)
val service = DefaultSynchronizerFactory.defaultService(applicationContext, lightWalletEndpoint)
val encoder = DefaultSynchronizerFactory.defaultEncoder(rustBackend, saplingParamTool, repository)
val encoder = DefaultSynchronizerFactory.defaultEncoder(backend, saplingParamTool, repository)
val downloader = DefaultSynchronizerFactory.defaultDownloader(service, blockStore)
val txManager = DefaultSynchronizerFactory.defaultTxManager(encoder, service)
val processor = DefaultSynchronizerFactory.defaultProcessor(rustBackend, downloader, repository)
val txManager = DefaultSynchronizerFactory.defaultTxManager(
encoder,
service
)
val processor = DefaultSynchronizerFactory.defaultProcessor(
backend,
downloader,
repository,
birthday
?: zcashNetwork.saplingActivationHeight
)
return SdkSynchronizer.new(
zcashNetwork,
@ -478,7 +488,7 @@ interface Synchronizer {
repository,
txManager,
processor,
rustBackend
backend
)
}

View File

@ -14,30 +14,31 @@ import cash.z.ecc.android.sdk.ext.BatchMetrics
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.ZcashSdk.MAX_BACKOFF_INTERVAL
import cash.z.ecc.android.sdk.ext.ZcashSdk.POLL_INTERVAL
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader
import cash.z.ecc.android.sdk.internal.createAccountAndGetSpendingKey
import cash.z.ecc.android.sdk.internal.ext.isNullOrEmpty
import cash.z.ecc.android.sdk.internal.ext.length
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.toHexReversed
import cash.z.ecc.android.sdk.internal.isNullOrEmpty
import cash.z.ecc.android.sdk.internal.length
import cash.z.ecc.android.sdk.internal.getBalance
import cash.z.ecc.android.sdk.internal.getBranchIdForHeight
import cash.z.ecc.android.sdk.internal.getCurrentAddress
import cash.z.ecc.android.sdk.internal.getDownloadedUtxoBalance
import cash.z.ecc.android.sdk.internal.getNearestRewindHeight
import cash.z.ecc.android.sdk.internal.getVerifiedBalance
import cash.z.ecc.android.sdk.internal.listTransparentReceivers
import cash.z.ecc.android.sdk.internal.model.BlockBatch
import cash.z.ecc.android.sdk.internal.model.DbTransactionOverview
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.internal.model.ext.from
import cash.z.ecc.android.sdk.internal.model.ext.toBlockHeight
import cash.z.ecc.android.sdk.internal.network
import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository
import cash.z.ecc.android.sdk.jni.Backend
import cash.z.ecc.android.sdk.jni.createAccountAndGetSpendingKey
import cash.z.ecc.android.sdk.jni.getBalance
import cash.z.ecc.android.sdk.jni.getBranchIdForHeight
import cash.z.ecc.android.sdk.jni.getCurrentAddress
import cash.z.ecc.android.sdk.jni.getDownloadedUtxoBalance
import cash.z.ecc.android.sdk.jni.getNearestRewindHeight
import cash.z.ecc.android.sdk.jni.getVerifiedBalance
import cash.z.ecc.android.sdk.jni.listTransparentReceivers
import cash.z.ecc.android.sdk.jni.rewindToHeight
import cash.z.ecc.android.sdk.jni.validateCombinedChainOrErrorBlockHeight
import cash.z.ecc.android.sdk.internal.rewindToHeight
import cash.z.ecc.android.sdk.internal.validateCombinedChainOrErrorBlockHeight
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.PercentDecimal
@ -80,7 +81,7 @@ import kotlin.time.Duration.Companion.days
* @property downloader the component responsible for downloading compact blocks and persisting them
* locally for processing.
* @property repository the repository holding transaction information.
* @property rustBackend the librustzcash functionality available and exposed to the SDK.
* @property backend the librustzcash functionality available and exposed to the SDK.
* @param minimumHeight the lowest height that we could care about. This is mostly used during
* reorgs as a backstop to make sure we do not rewind beyond sapling activation. It also is factored
* in when considering initial range to download. In most cases, this should be the birthday height
@ -91,8 +92,8 @@ import kotlin.time.Duration.Companion.days
class CompactBlockProcessor internal constructor(
val downloader: CompactBlockDownloader,
private val repository: DerivedDataRepository,
private val rustBackend: Backend,
minimumHeight: BlockHeight = rustBackend.network.saplingActivationHeight
private val backend: Backend,
minimumHeight: BlockHeight
) {
/**
* Callback for any non-trivial errors that occur while processing compact blocks.
@ -130,9 +131,15 @@ class CompactBlockProcessor internal constructor(
var onScanMetricCompleteListener: ((BatchMetrics, Boolean) -> Unit)? = null
private val consecutiveChainErrors = AtomicInteger(0)
/**
* The zcash network that is being processed. Either Testnet or Mainnet.
*/
val network = backend.network
private val lowerBoundHeight: BlockHeight = BlockHeight(
max(
rustBackend.network.saplingActivationHeight.value,
network.saplingActivationHeight.value,
minimumHeight.value - MAX_REORG_SIZE
)
)
@ -152,11 +159,6 @@ class CompactBlockProcessor internal constructor(
*/
private val _birthdayHeight = MutableStateFlow(lowerBoundHeight)
/**
* The zcash network that is being processed. Either Testnet or Mainnet.
*/
val network = rustBackend.network
/**
* The flow of state values so that a wallet can monitor the state of this class without needing
* to poll.
@ -357,7 +359,7 @@ class CompactBlockProcessor internal constructor(
// Sync
var syncResult: BlockProcessingResult = BlockProcessingResult.Success
syncNewBlocks(
backend = rustBackend,
backend = backend,
downloader = downloader,
repository = repository,
network = network,
@ -508,7 +510,7 @@ class CompactBlockProcessor internal constructor(
is Response.Success -> {
runCatching {
Twig.debug { "decrypting and storing transaction (id:$id block:$minedHeight)" }
rustBackend.decryptAndStoreTransaction(response.result.data)
backend.decryptAndStoreTransaction(response.result.data)
}.onSuccess {
Twig.debug { "DONE: enhancing transaction (id:$id block:$minedHeight)" }
}.onFailure { error ->
@ -548,9 +550,9 @@ class CompactBlockProcessor internal constructor(
} else {
val clientBranch = "%x".format(
Locale.ROOT,
rustBackend.getBranchIdForHeight(serverBlockHeight)
backend.getBranchIdForHeight(serverBlockHeight)
)
val network = rustBackend.network.networkName
val network = backend.network.networkName
if (!clientBranch.equals(info.consensusBranchId, true)) {
MismatchedNetwork(
@ -610,7 +612,7 @@ class CompactBlockProcessor internal constructor(
@Suppress("TooGenericExceptionCaught")
try {
retryUpTo(3) {
val tAddresses = rustBackend.listTransparentReceivers(account)
val tAddresses = backend.listTransparentReceivers(account)
downloader.lightWalletClient.fetchUtxos(
tAddresses,
@ -689,13 +691,13 @@ class CompactBlockProcessor internal constructor(
// TODO [#920]: Tweak RustBackend public APIs to have void return values.
// TODO [#920]: Thus, we don't need to check the boolean result of this call until fixed.
// TODO [#920]: https://github.com/zcash/zcash-android-wallet-sdk/issues/920
rustBackend.putUtxo(
backend.putUtxo(
utxo.address,
utxo.txid,
utxo.index,
utxo.script,
utxo.valueZat,
BlockHeight(utxo.height)
utxo.height
)
true
} catch (t: Throwable) {
@ -1071,7 +1073,7 @@ class CompactBlockProcessor internal constructor(
// tricky: subtract one because we delete ABOVE this block
// This could create an invalid height if if height was saplingActivationHeight
val rewindHeight = BlockHeight(height.value - 1)
rustBackend.getNearestRewindHeight(rewindHeight)
backend.getNearestRewindHeight(rewindHeight)
}
}
@ -1109,10 +1111,10 @@ class CompactBlockProcessor internal constructor(
if (null == lastSyncedHeight && targetHeight < lastLocalBlock) {
Twig.debug { "Rewinding because targetHeight is less than lastLocalBlock." }
rustBackend.rewindToHeight(targetHeight)
backend.rewindToHeight(targetHeight)
} else if (null != lastSyncedHeight && targetHeight < lastSyncedHeight) {
Twig.debug { "Rewinding because targetHeight is less than lastSyncedHeight." }
rustBackend.rewindToHeight(targetHeight)
backend.rewindToHeight(targetHeight)
} else {
Twig.debug {
"Not rewinding dataDb because the last synced height is $lastSyncedHeight and the" +
@ -1265,7 +1267,7 @@ class CompactBlockProcessor internal constructor(
}
return buildList<BlockHeight> {
add(lowerBoundHeight)
add(rustBackend.network.saplingActivationHeight)
add(backend.network.saplingActivationHeight)
oldestTransactionHeight?.let { add(it) }
}.maxOf { it }
}
@ -1280,9 +1282,9 @@ class CompactBlockProcessor internal constructor(
suspend fun getBalanceInfo(account: Account): WalletBalance {
@Suppress("TooGenericExceptionCaught")
return try {
val balanceTotal = rustBackend.getBalance(account)
val balanceTotal = backend.getBalance(account)
Twig.debug { "found total balance: $balanceTotal" }
val balanceAvailable = rustBackend.getVerifiedBalance(account)
val balanceAvailable = backend.getVerifiedBalance(account)
Twig.debug { "found available balance: $balanceAvailable" }
WalletBalance(balanceTotal, balanceAvailable)
} catch (t: Throwable) {
@ -1292,7 +1294,7 @@ class CompactBlockProcessor internal constructor(
}
suspend fun getUtxoCacheBalance(address: String): WalletBalance =
rustBackend.getDownloadedUtxoBalance(address)
backend.getDownloadedUtxoBalance(address)
/**
* Transmits the given state for this processor.

View File

@ -1,8 +1,7 @@
@file:Suppress("TooManyFunctions")
package cash.z.ecc.android.sdk.jni
package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.internal.SdkDispatchers
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.model.Account
@ -11,9 +10,13 @@ import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.withContext
internal val Backend.network: ZcashNetwork
get() = ZcashNetwork.from(networkId)
internal suspend fun Backend.initAccountsTable(vararg keys: UnifiedFullViewingKey): Boolean {
val ufvks = Array(keys.size) { keys[it].encoding }
@ -21,14 +24,11 @@ internal suspend fun Backend.initAccountsTable(vararg keys: UnifiedFullViewingKe
return initAccountsTable(*ufvks)
}
internal suspend fun Backend.initAccountsTable(
internal suspend fun Backend.initAccountsTableTypesafe(
seed: ByteArray,
numberOfAccounts: Int
): Array<UnifiedFullViewingKey> {
return DerivationTool.deriveUnifiedFullViewingKeys(seed, network, numberOfAccounts).apply {
@Suppress("SpreadOperator")
initAccountsTable(*this)
}
): List<UnifiedFullViewingKey> {
return DerivationTool.getInstance().deriveUnifiedFullViewingKeys(seed, network, numberOfAccounts)
}
internal suspend fun Backend.initBlocksTable(checkpoint: Checkpoint): Boolean = initBlocksTable(
@ -80,7 +80,7 @@ internal suspend fun Backend.getVerifiedBalance(account: Account): Zatoshi = Zat
)
internal suspend fun Backend.getNearestRewindHeight(height: BlockHeight): BlockHeight = BlockHeight.new(
network,
ZcashNetwork.from(networkId),
getNearestRewindHeight(height.value)
)
@ -88,7 +88,7 @@ internal suspend fun Backend.rewindToHeight(height: BlockHeight): Boolean = rewi
internal suspend fun Backend.getLatestBlockHeight(): BlockHeight? = getLatestHeight()?.let {
BlockHeight.new(
network,
ZcashNetwork.from(networkId),
it
)
}
@ -106,7 +106,7 @@ internal suspend fun Backend.rewindBlockMetadataToHeight(height: BlockHeight) =
internal suspend fun Backend.validateCombinedChainOrErrorBlockHeight(limit: Long?): BlockHeight? =
validateCombinedChainOrErrorHeight(limit)?.let {
BlockHeight.new(
network,
ZcashNetwork.from(networkId),
it
)
}
@ -124,3 +124,20 @@ internal suspend fun Backend.getDownloadedUtxoBalance(address: String): WalletBa
}
return WalletBalance(Zatoshi(total), Zatoshi(verified))
}
@Suppress("LongParameterList")
internal suspend fun Backend.putUtxo(
tAddress: String,
txId: ByteArray,
index: Int,
script: ByteArray,
value: Long,
height: BlockHeight
): Boolean = putUtxo(
tAddress,
txId,
index,
script,
value,
height.value
)

View File

@ -0,0 +1,44 @@
package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.ZcashNetwork
fun Derivation.deriveUnifiedAddress(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): String = deriveUnifiedAddress(seed, network.id, account.value)
fun Derivation.deriveUnifiedAddress(
viewingKey: String,
network: ZcashNetwork,
): String = deriveUnifiedAddress(viewingKey, network.id)
fun Derivation.deriveUnifiedSpendingKey(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): UnifiedSpendingKey = UnifiedSpendingKey(deriveUnifiedSpendingKey(seed, network.id, account.value))
fun Derivation.deriveUnifiedFullViewingKey(
usk: UnifiedSpendingKey,
network: ZcashNetwork
): UnifiedFullViewingKey = UnifiedFullViewingKey(
deriveUnifiedFullViewingKey(
JniUnifiedSpendingKey(
usk.account.value,
usk.copyBytes()
),
network.id
)
)
fun Derivation.deriveUnifiedFullViewingKeysTypesafe(
seed: ByteArray,
network: ZcashNetwork,
numberOfAccounts: Int
): List<UnifiedFullViewingKey> =
deriveUnifiedFullViewingKeys(seed, network.id, numberOfAccounts).map { UnifiedFullViewingKey(it) }

View File

@ -21,6 +21,13 @@ import java.nio.channels.Channels
import kotlin.time.Duration.Companion.milliseconds
internal class SaplingParamTool(val properties: SaplingParamToolProperties) {
val spendParamsFile: File
get() = File(properties.paramsDirectory, SPEND_PARAM_FILE_NAME)
val outputParamsFile: File
get() = File(properties.paramsDirectory, OUTPUT_PARAM_FILE_NAME)
companion object {
/**
* Maximum file size for the sapling spend params - 50MB

View File

@ -1,43 +0,0 @@
package cash.z.ecc.android.sdk.internal
/**
* Simple implementation of Simple moving average.
*/
class Sma(val window: Int = 3) {
private val values = Array(window) { 0.0 }
var average = 0.0
private set
var count: Int = 0
var index: Int = 0
fun add(value: Number) = add(value.toDouble())
fun add(value: Double): Double {
when {
// full window
count == window -> {
index = (index + 1) % window
average += ((value - values[index]) / count.toFloat())
values[index] = value
}
// partially-filled window
count != 0 -> {
index = (index + 1) % window
average = ((value + count.toFloat() * average) / (count + 1).toFloat())
values[index] = value
count++
}
// empty window
else -> {
// simply assign given value as current average:
average = value
values[0] = value
count = 1
}
}
return average
}
fun format(places: Int = 0) = "%.${places}f".format(average)
}

View File

@ -0,0 +1,28 @@
package cash.z.ecc.android.sdk.internal
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/**
* Implements a coroutines-friendly lazy singleton pattern with an input argument.
*
* This class is thread-safe.
*/
internal class SuspendingLazy<in Input, out Output>(private val deferredCreator: suspend ((Input) -> Output)) {
private var singletonInstance: Output? = null
private val mutex = Mutex()
suspend fun getInstance(input: Input): Output {
mutex.withLock {
singletonInstance?.let {
return it
}
val newInstance = deferredCreator(input)
singletonInstance = newInstance
return newInstance
}
}
}

View File

@ -1,4 +1,4 @@
package cash.z.ecc.android.sdk.jni
package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
@ -16,7 +16,7 @@ internal interface TypesafeBackend {
suspend fun initAccountsTable(
seed: ByteArray,
numberOfAccounts: Int
): Array<UnifiedFullViewingKey>
): List<UnifiedFullViewingKey>
suspend fun initBlocksTable(checkpoint: Checkpoint): Boolean

View File

@ -1,4 +1,4 @@
package cash.z.ecc.android.sdk.jni
package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
@ -8,6 +8,7 @@ import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
// This class is currently unused, although the goal is to swap out usages of BackendExt for this throughout the SDK.
@Suppress("TooManyFunctions")
internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBackend {
override suspend fun initAccountsTable(vararg keys: UnifiedFullViewingKey): Boolean =
@ -16,7 +17,7 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke
override suspend fun initAccountsTable(
seed: ByteArray,
numberOfAccounts: Int
): Array<UnifiedFullViewingKey> = backend.initAccountsTable(seed, numberOfAccounts)
): List<UnifiedFullViewingKey> = backend.initAccountsTableTypesafe(seed, numberOfAccounts)
override suspend fun initBlocksTable(checkpoint: Checkpoint): Boolean = backend.initBlocksTable(checkpoint)

View File

@ -0,0 +1,38 @@
package cash.z.ecc.android.sdk.internal
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
internal class TypesafeDerivationToolImpl(private val derivation: Derivation) : DerivationTool {
override suspend fun deriveUnifiedFullViewingKeys(
seed: ByteArray,
network: ZcashNetwork,
numberOfAccounts: Int
): List<UnifiedFullViewingKey> = derivation.deriveUnifiedFullViewingKeysTypesafe(seed, network, numberOfAccounts)
override suspend fun deriveUnifiedFullViewingKey(
usk: UnifiedSpendingKey,
network: ZcashNetwork
): UnifiedFullViewingKey = derivation.deriveUnifiedFullViewingKey(usk, network)
override suspend fun deriveUnifiedSpendingKey(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): UnifiedSpendingKey = derivation.deriveUnifiedSpendingKey(seed, network, account)
override suspend fun deriveUnifiedAddress(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): String = derivation.deriveUnifiedAddress(seed, network, account)
override suspend fun deriveUnifiedAddress(
viewingKey: String,
network: ZcashNetwork,
): String = derivation.deriveUnifiedAddress(viewingKey, network)
}

View File

@ -2,17 +2,18 @@ package cash.z.ecc.android.sdk.internal.db.derived
import android.content.Context
import androidx.sqlite.db.SupportSQLiteDatabase
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.NoBackupContextWrapper
import cash.z.ecc.android.sdk.internal.db.ReadOnlySupportSqliteOpenHelper
import cash.z.ecc.android.sdk.internal.ext.tryWarn
import cash.z.ecc.android.sdk.internal.initAccountsTable
import cash.z.ecc.android.sdk.internal.initBlocksTable
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.jni.initAccountsTable
import cash.z.ecc.android.sdk.jni.initBlocksTable
import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.ZcashNetwork
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
internal class DerivedDataDb private constructor(
zcashNetwork: ZcashNetwork,
@ -42,13 +43,14 @@ internal class DerivedDataDb private constructor(
@Suppress("LongParameterList", "SpreadOperator")
suspend fun new(
context: Context,
rustBackend: RustBackend,
backend: Backend,
databaseFile: File,
zcashNetwork: ZcashNetwork,
checkpoint: Checkpoint,
seed: ByteArray?,
viewingKeys: List<UnifiedFullViewingKey>
): DerivedDataDb {
rustBackend.initDataDb(seed)
backend.initDataDb(seed)
// TODO [#681]: consider converting these to typed exceptions in the welding layer
// TODO [#681]: https://github.com/zcash/zcash-android-wallet-sdk/issues/681
@ -56,22 +58,22 @@ internal class DerivedDataDb private constructor(
"Did not initialize the blocks table. It probably was already initialized.",
ifContains = "table is not empty"
) {
rustBackend.initBlocksTable(checkpoint)
backend.initBlocksTable(checkpoint)
}
tryWarn(
"Did not initialize the accounts table. It probably was already initialized.",
ifContains = "table is not empty"
) {
rustBackend.initAccountsTable(*viewingKeys.toTypedArray())
backend.initAccountsTable(*viewingKeys.toTypedArray())
}
val database = ReadOnlySupportSqliteOpenHelper.openExistingDatabaseAsReadOnly(
NoBackupContextWrapper(
context,
rustBackend.dataDbFile.parentFile!!
databaseFile.parentFile!!
),
rustBackend.dataDbFile,
databaseFile,
DATABASE_VERSION
)

View File

@ -1,4 +1,4 @@
package cash.z.ecc.android.sdk.internal
package cash.z.ecc.android.sdk.internal.ext
import cash.z.ecc.android.sdk.model.BlockHeight

View File

@ -0,0 +1,17 @@
package cash.z.ecc.android.sdk.internal.model
import cash.z.wallet.sdk.internal.rpc.CompactFormats
import co.electriccoin.lightwallet.client.model.CompactBlockUnsafe
internal fun JniBlockMeta.Companion.new(
block: CompactFormats.CompactBlock,
outputs: CompactBlockUnsafe.CompactBlockOutputsCounts
): JniBlockMeta {
return JniBlockMeta(
height = block.height,
hash = block.hash.toByteArray(),
time = block.time.toLong(),
saplingOutputsCount = outputs.saplingOutputsCount.toLong(),
orchardOutputsCount = outputs.orchardActionsCount.toLong()
)
}

View File

@ -1,4 +1,4 @@
package cash.z.ecc.android.sdk.internal
package cash.z.ecc.android.sdk.internal.model.ext
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.model.BlockHeight

View File

@ -1,6 +1,7 @@
package cash.z.ecc.android.sdk.internal.storage.block
import androidx.annotation.VisibleForTesting
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.ext.createNewFileSuspend
import cash.z.ecc.android.sdk.internal.ext.deleteRecursivelySuspend
@ -12,13 +13,11 @@ import cash.z.ecc.android.sdk.internal.ext.mkdirsSuspend
import cash.z.ecc.android.sdk.internal.ext.renameToSuspend
import cash.z.ecc.android.sdk.internal.ext.toHexReversed
import cash.z.ecc.android.sdk.internal.ext.writeBytesSuspend
import cash.z.ecc.android.sdk.internal.findBlockMetadata
import cash.z.ecc.android.sdk.internal.getLatestBlockHeight
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.internal.repository.CompactBlockRepository
import cash.z.ecc.android.sdk.jni.Backend
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.jni.findBlockMetadata
import cash.z.ecc.android.sdk.jni.getLatestBlockHeight
import cash.z.ecc.android.sdk.jni.rewindBlockMetadataToHeight
import cash.z.ecc.android.sdk.internal.rewindBlockMetadataToHeight
import cash.z.ecc.android.sdk.model.BlockHeight
import co.electriccoin.lightwallet.client.model.CompactBlockUnsafe
import kotlinx.coroutines.flow.Flow
@ -26,12 +25,12 @@ import java.io.File
internal class FileCompactBlockRepository(
private val blocksDirectory: File,
private val rustBackend: Backend
private val backend: Backend
) : CompactBlockRepository {
override suspend fun getLatestHeight() = rustBackend.getLatestBlockHeight()
override suspend fun getLatestHeight() = backend.getLatestBlockHeight()
override suspend fun findCompactBlock(height: BlockHeight) = rustBackend.findBlockMetadata(height)
override suspend fun findCompactBlock(height: BlockHeight) = backend.findBlockMetadata(height)
override suspend fun write(blocks: Flow<CompactBlockUnsafe>): List<JniBlockMeta> {
val processingBlocks = mutableListOf<JniBlockMeta>()
@ -66,11 +65,11 @@ internal class FileCompactBlockRepository(
* Write block metadata to storage when the buffer is full or when we reached the current range end.
*/
private suspend fun writeAndClearBuffer(metaDataBuffer: MutableList<JniBlockMeta>) {
rustBackend.writeBlockMetadata(metaDataBuffer)
backend.writeBlockMetadata(metaDataBuffer)
metaDataBuffer.clear()
}
override suspend fun rewindTo(height: BlockHeight) = rustBackend.rewindBlockMetadataToHeight(height)
override suspend fun rewindTo(height: BlockHeight) = backend.rewindBlockMetadataToHeight(height)
override suspend fun deleteAllCompactBlockFiles(): Boolean {
Twig.verbose { "Deleting all blocks from directory ${blocksDirectory.path}" }
@ -129,13 +128,16 @@ internal class FileCompactBlockRepository(
*/
const val BLOCKS_METADATA_BUFFER_SIZE = 10
/**
* @param blockCacheRoot The root directory for the compact block cache (contains the database and a
* subdirectory for the blocks).
*/
suspend fun new(
rustBackend: RustBackend
blockCacheRoot: File,
rustBackend: Backend
): FileCompactBlockRepository {
Twig.debug { "${rustBackend.fsBlockDbRoot.absolutePath} \n ${rustBackend.dataDbFile.absolutePath}" }
// create and check cache directories
val blocksDirectory = File(rustBackend.fsBlockDbRoot, BLOCKS_DOWNLOAD_DIRECTORY).also {
val blocksDirectory = File(blockCacheRoot, BLOCKS_DOWNLOAD_DIRECTORY).also {
it.mkdirsSuspend()
}
if (!blocksDirectory.existsSuspend()) {

View File

@ -2,14 +2,15 @@ package cash.z.ecc.android.sdk.internal.transaction
import cash.z.ecc.android.sdk.exception.TransactionEncoderException
import cash.z.ecc.android.sdk.ext.masked
import cash.z.ecc.android.sdk.internal.Backend
import cash.z.ecc.android.sdk.internal.SaplingParamTool
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.createToAddress
import cash.z.ecc.android.sdk.internal.getBranchIdForHeight
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
import cash.z.ecc.android.sdk.internal.network
import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository
import cash.z.ecc.android.sdk.jni.Backend
import cash.z.ecc.android.sdk.jni.createToAddress
import cash.z.ecc.android.sdk.jni.getBranchIdForHeight
import cash.z.ecc.android.sdk.jni.shieldToAddress
import cash.z.ecc.android.sdk.internal.shieldToAddress
import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.Zatoshi
@ -19,7 +20,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
* behaving like a stateless API so that callers can request [createTransaction] and receive a
* result, even though there are intermediate database interactions.
*
* @property backend the instance of RustBackendWelding to use for creating and validating.
* @property rustBackend the instance of RustBackendWelding to use for creating and validating.
* @property repository the repository that stores information about the transactions being created
* such as the raw bytes and raw txId.
*/
@ -132,7 +133,7 @@ internal class TransactionEncoderImpl(
@Suppress("TooGenericExceptionCaught")
return try {
saplingParamTool.ensureParams(backend.saplingParamDir)
saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory)
Twig.debug { "params exist! attempting to send..." }
backend.createToAddress(
usk,
@ -154,7 +155,7 @@ internal class TransactionEncoderImpl(
): Long {
@Suppress("TooGenericExceptionCaught")
return try {
saplingParamTool.ensureParams(backend.saplingParamDir)
saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory)
Twig.debug { "params exist! attempting to shield..." }
backend.shieldToAddress(
usk,

View File

@ -1,37 +0,0 @@
package cash.z.ecc.android.sdk.jni
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.ZcashNetwork
// Implemented by `DerivationTool`
interface Derivation {
suspend fun deriveUnifiedAddress(
viewingKey: String,
network: ZcashNetwork
): String
suspend fun deriveUnifiedAddress(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): String
suspend fun deriveUnifiedSpendingKey(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): UnifiedSpendingKey
suspend fun deriveUnifiedFullViewingKey(
usk: UnifiedSpendingKey,
network: ZcashNetwork
): UnifiedFullViewingKey
suspend fun deriveUnifiedFullViewingKeys(
seed: ByteArray,
network: ZcashNetwork,
numberOfAccounts: Int = 1
): Array<UnifiedFullViewingKey>
}

View File

@ -1,7 +1,7 @@
package cash.z.ecc.android.sdk.model
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.jni.UnifiedSpendingKeyJni
import cash.z.ecc.android.sdk.internal.jni.RustBackend
import cash.z.ecc.android.sdk.internal.model.JniUnifiedSpendingKey
/**
* A [ZIP 316](https://zips.z.cash/zip-0316) Unified Spending Key.
@ -26,9 +26,9 @@ class UnifiedSpendingKey private constructor(
private val bytes: FirstClassByteArray
) {
internal constructor(uskJni: UnifiedSpendingKeyJni) : this(
internal constructor(uskJni: JniUnifiedSpendingKey) : this(
Account(uskJni.account),
FirstClassByteArray(uskJni.copyBytes())
FirstClassByteArray(uskJni.bytes.copyOf())
)
/**
@ -75,9 +75,7 @@ class UnifiedSpendingKey private constructor(
val bytesCopy = bytes.copyOf()
RustBackend.loadLibrary()
return runCatching {
// We can ignore the Boolean returned from this, because if an error
// occurs the Rust side will throw.
RustBackend.validateUnifiedSpendingKey(bytesCopy)
require(RustBackend.validateUnifiedSpendingKey(bytesCopy))
UnifiedSpendingKey(account, FirstClassByteArray(bytesCopy))
}
}

View File

@ -4,8 +4,8 @@ import android.content.Context
import androidx.annotation.VisibleForTesting
import cash.z.ecc.android.sdk.exception.BirthdayException
import cash.z.ecc.android.sdk.internal.Twig
import cash.z.ecc.android.sdk.internal.from
import cash.z.ecc.android.sdk.internal.model.Checkpoint
import cash.z.ecc.android.sdk.internal.model.ext.from
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork
import kotlinx.coroutines.Dispatchers

View File

@ -1,15 +1,15 @@
package cash.z.ecc.android.sdk.tool
import cash.z.ecc.android.sdk.jni.Derivation
import cash.z.ecc.android.sdk.jni.RustBackend
import cash.z.ecc.android.sdk.jni.UnifiedSpendingKeyJni
import cash.z.ecc.android.sdk.internal.Derivation
import cash.z.ecc.android.sdk.internal.SuspendingLazy
import cash.z.ecc.android.sdk.internal.TypesafeDerivationToolImpl
import cash.z.ecc.android.sdk.internal.jni.RustDerivationTool
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.UnifiedFullViewingKey
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.ZcashNetwork
@Suppress("TooManyFunctions")
object DerivationTool : Derivation {
interface DerivationTool {
/**
* Given a seed and a number of accounts, return the associated Unified Full Viewing Keys.
@ -20,16 +20,11 @@ object DerivationTool : Derivation {
*
* @return the UFVKs derived from the seed, encoded as Strings.
*/
override suspend fun deriveUnifiedFullViewingKeys(
suspend fun deriveUnifiedFullViewingKeys(
seed: ByteArray,
network: ZcashNetwork,
numberOfAccounts: Int
): Array<UnifiedFullViewingKey> =
withRustBackendLoaded {
deriveUnifiedFullViewingKeysFromSeed(seed, numberOfAccounts, networkId = network.id).map {
UnifiedFullViewingKey(it)
}.toTypedArray()
}
): List<UnifiedFullViewingKey>
/**
* Given a unified spending key, return the associated unified full viewing key.
@ -38,14 +33,10 @@ object DerivationTool : Derivation {
*
* @return a unified full viewing key.
*/
override suspend fun deriveUnifiedFullViewingKey(
suspend fun deriveUnifiedFullViewingKey(
usk: UnifiedSpendingKey,
network: ZcashNetwork
): UnifiedFullViewingKey = withRustBackendLoaded {
UnifiedFullViewingKey(
deriveUnifiedFullViewingKey(usk.copyBytes(), networkId = network.id)
)
}
): UnifiedFullViewingKey
/**
* Derives and returns a unified spending key from the given seed for the given account ID.
@ -59,26 +50,21 @@ object DerivationTool : Derivation {
*
* @return the unified spending key for the account.
*/
override suspend fun deriveUnifiedSpendingKey(
suspend fun deriveUnifiedSpendingKey(
seed: ByteArray,
network: ZcashNetwork,
account: Account
): UnifiedSpendingKey = withRustBackendLoaded {
UnifiedSpendingKey(deriveSpendingKey(seed, account.value, networkId = network.id))
}
): UnifiedSpendingKey
/**
* Given a seed and account index, return the associated Unified Address.
*
* @param seed the seed from which to derive the address.
* @param accountIndex the index of the account to use for deriving the address.
* @param account the index of the account to use for deriving the address.
*
* @return the address that corresponds to the seed and account index.
*/
override suspend fun deriveUnifiedAddress(seed: ByteArray, network: ZcashNetwork, account: Account): String =
withRustBackendLoaded {
deriveUnifiedAddressFromSeed(seed, account.value, networkId = network.id)
}
suspend fun deriveUnifiedAddress(seed: ByteArray, network: ZcashNetwork, account: Account): String
/**
* Given a Unified Full Viewing Key string, return the associated Unified Address.
@ -88,56 +74,17 @@ object DerivationTool : Derivation {
*
* @return the address that corresponds to the viewing key.
*/
override suspend fun deriveUnifiedAddress(
suspend fun deriveUnifiedAddress(
viewingKey: String,
network: ZcashNetwork
): String = withRustBackendLoaded {
deriveUnifiedAddressFromViewingKey(viewingKey, networkId = network.id)
}
@Suppress("UNUSED_PARAMETER")
fun validateUnifiedFullViewingKey(viewingKey: UnifiedFullViewingKey, networkId: Int = ZcashNetwork.Mainnet.id) {
// TODO [#654] https://github.com/zcash/zcash-android-wallet-sdk/issues/654
}
/**
* A helper function to ensure that the Rust libraries are loaded before any code in this
* class attempts to interact with it, indirectly, by invoking JNI functions. It would be
* nice to have an annotation like @UsesSystemLibrary for this
*/
private suspend fun <T> withRustBackendLoaded(block: () -> T): T {
RustBackend.loadLibrary()
return block()
}
//
// JNI functions
//
@JvmStatic
private external fun deriveSpendingKey(
seed: ByteArray,
account: Int,
networkId: Int
): UnifiedSpendingKeyJni
@JvmStatic
private external fun deriveUnifiedFullViewingKeysFromSeed(
seed: ByteArray,
numberOfAccounts: Int,
networkId: Int
): Array<String>
@JvmStatic
private external fun deriveUnifiedFullViewingKey(usk: ByteArray, networkId: Int): String
@JvmStatic
private external fun deriveUnifiedAddressFromSeed(
seed: ByteArray,
accountIndex: Int,
networkId: Int
): String
@JvmStatic
private external fun deriveUnifiedAddressFromViewingKey(key: String, networkId: Int): String
companion object {
const val DEFAULT_NUMBER_OF_ACCOUNTS = Derivation.DEFAULT_NUMBER_OF_ACCOUNTS
private val instance = SuspendingLazy<Unit, DerivationTool> {
TypesafeDerivationToolImpl(RustDerivationTool.new())
}
suspend fun getInstance() = instance.getInstance(Unit)
}
}

View File

@ -1,46 +0,0 @@
task unregistered {
println "configuring unregistered"
doLast {
println 'unregistered'
}
}
tasks.register("pb") {
println "configuring pb"
doLast {
println 'preBuild'
}
}
tasks.register("generateJni") {
println "configuring generateJni"
doLast {
println 'jni'
}
}
tasks.register("copyA") {
dependsOn generateJni
println "configuring copyA"
doLast {
println 'copyA'
}
}
tasks.register("copyB") {
dependsOn generateJni
println "configuring copyB"
doLast {
println 'copyB'
}
}
tasks.register("copyC") {
dependsOn generateJni
println "configuring copyC"
doLast {
println 'copyC'
}
}
task copyAll {
dependsOn copyA, copyB, copyC
}
pb.dependsOn copyAll

View File

@ -269,6 +269,7 @@ rootProject.name = "zcash-android-sdk"
includeBuild("build-conventions")
include("backend-lib")
include("darkside-test-lib")
include("demo-app")
include("demo-app-benchmark-test")