diff --git a/CHANGELOG.md b/CHANGELOG.md index 007b2fe8f..bc04fe518 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 the given ZIP-321 Uri, and then creating transactions from it. ### Changed +- Migrated to Rust 1.82.0. +- `Synchronizer.rewindToNearestHeight` now returns the block height that was + actually rewound to, or `null` if no rewind was performed. - `Synchronizer.proposeTransfer` throws `TransactionEncoderException.ProposalFromParametersException` - `Synchronizer.proposeShielding` throws `TransactionEncoderException.ProposalShieldingException` - `Synchronizer.createProposedTransactions` throws `TransactionEncoderException.TransactionNotCreatedException` and `TransactionEncoderException.TransactionNotFoundException` @@ -22,6 +25,12 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `FailedSynchronizationException` reported using `Synchronizer.onProcessorErrorHandler` now contains the full stacktrace history +### Removed +- `Synchronizer.getNearestRewindHeight` (its function is now handled internally + by `Synchronizer.rewindToNearestHeight`). +- `Synchronizer.quickRewind` and `CompactBlockProcessor.quickRewind` have been removed as they triggered the block + rewind action at an invalid height. Use `Synchronizer.rewindToNearestHeight` instead. + ## [2.2.4] - 2024-09-16 ### Added diff --git a/backend-lib/Cargo.lock b/backend-lib/Cargo.lock index 1ecfe0a35..3cd7b7119 100644 --- a/backend-lib/Cargo.lock +++ b/backend-lib/Cargo.lock @@ -290,12 +290,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -1665,9 +1659,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "incrementalmerkletree" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75346da3bd8e3d8891d02508245ed2df34447ca6637e343829f8d08986e9cde2" +checksum = "d45063fbc4b0a37837f6bfe0445f269d13d730ad0aa3b5a7f74aa7bf27a0f4df" dependencies = [ "either", ] @@ -2144,9 +2138,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "orchard" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc7bde644aeb980be296cd908c6650894dc8541deb56f9f5294c52ed7ca568f" +checksum = "4f18e997fa121de5c73e95cdc7e8512ae43b7de38904aeea5e5713cc48f3c0ba" dependencies = [ "aes", "bitvec", @@ -2999,9 +2993,9 @@ dependencies = [ [[package]] name = "sapling-crypto" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e379398fffad84e49f9a45a05635fc004f66086e65942dbf4eb95332c26d2a" +checksum = "cfff8cfce16aeb38da50b8e2ed33c9018f30552beff2210c266662a021b17f38" dependencies = [ "aes", "bellman", @@ -3181,7 +3175,7 @@ version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ - "base64 0.22.1", + "base64", "chrono", "hex", "indexmap 1.9.3", @@ -3261,9 +3255,9 @@ dependencies = [ [[package]] name = "shardtree" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78222845cd8bbe5eb95687407648ff17693a35de5e8abaa39a4681fb21e033f9" +checksum = "b5f2390975ebfe8838f9e861f7a588123d49a7a7a0a08568ea831d8ad53fc9b4" dependencies = [ "bitflags", "either", @@ -5029,9 +5023,9 @@ dependencies = [ [[package]] name = "zcash_address" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d26f21381dc220836dd8d2a9a10dbe85928a26232b011bc6a42b611789b743" +checksum = "4ff95eac82f71286a79c750e674550d64fb2b7aadaef7b89286b2917f645457d" dependencies = [ "bech32", "bs58", @@ -5042,13 +5036,13 @@ dependencies = [ [[package]] name = "zcash_client_backend" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e3a0f3e5d7f299d8b7ef3237697630989c31ab1b162824c99c1cd8bc83715e" +checksum = "cbeeede366fdb642710d3c59fc2090489affd075f66db53ed11bb7138d2d0258" dependencies = [ "arti-client", "async-trait", - "base64 0.21.7", + "base64", "bech32", "bip32", "bls12_381", @@ -5067,6 +5061,7 @@ dependencies = [ "nom", "nonempty", "orchard", + "pasta_curves", "percent-encoding", "prost", "rand 0.8.5", @@ -5100,9 +5095,9 @@ dependencies = [ [[package]] name = "zcash_client_sqlite" -version = "0.11.2" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742a0d2ff7113d1173ccb9e5a832892236a570c07ac8cb5f6208a16bb6704b5" +checksum = "43d7adae3033790f9cd54c145a79e32a98c01897f8a9f495d51a8a8b6463df14" dependencies = [ "bip32", "bs58", @@ -5148,9 +5143,9 @@ dependencies = [ [[package]] name = "zcash_keys" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712faf4070107ab0b2828d0eda6aeaf4c3cb02564109832d95b97ad3467c95a5" +checksum = "e8162c94957f1e379b8e2fb30f97b95cfa93ac9c6bc02895946ca6392d1abb81" dependencies = [ "bech32", "bip32", @@ -5190,9 +5185,9 @@ dependencies = [ [[package]] name = "zcash_primitives" -version = "0.16.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f044bc9cf2887ec408196fbafb44749e5581f57cc18d8da7aabaeb60cc40c64" +checksum = "6ab47d526d7fd6f88b3a2854ad81b54757a80c2aeadd1d8b06f690556af9743c" dependencies = [ "aes", "bip32", @@ -5229,9 +5224,9 @@ dependencies = [ [[package]] name = "zcash_proofs" -version = "0.16.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c579a5893ac140fab49cf73023ace91d51b441f37094bba5969c775526d8c1e" +checksum = "daba607872e60d91a09248d8e1ea3d6801c819fb80d67016d9de02d81323c10d" dependencies = [ "bellman", "blake2b_simd", @@ -5252,9 +5247,9 @@ dependencies = [ [[package]] name = "zcash_protocol" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f35eac659fdbba614333d119217c5963c0d7cea43aee33176c4f2f95e5460d8d" +checksum = "6bc22b9155b2c7eb20105cd06de170d188c1bc86489b92aa3fda7b8da8d96acf" dependencies = [ "document-features", "memuse", @@ -5343,11 +5338,11 @@ dependencies = [ [[package]] name = "zip321" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc85f862f7be64fb0d46f9eb5b82ad54e58cde314fa979d5bae591bc0143693" +checksum = "1f3e613defb0940acef1f54774b51c7f48f2fa705613dd800870dc69f35cd2ea" dependencies = [ - "base64 0.21.7", + "base64", "nom", "percent-encoding", "zcash_address", diff --git a/backend-lib/Cargo.toml b/backend-lib/Cargo.toml index 47fd448fa..dcb5c4217 100644 --- a/backend-lib/Cargo.toml +++ b/backend-lib/Cargo.toml @@ -8,17 +8,17 @@ authors = [ description = "JNI backend for the Android wallet SDK" publish = false edition = "2018" -rust-version = "1.80" +rust-version = "1.82" [dependencies] # Zcash dependencies -orchard = "0.9" -sapling = { package = "sapling-crypto", version = "0.2", default-features = false } -zcash_address = "0.4" -zcash_client_backend = { version = "0.13", features = ["orchard", "tor", "transparent-inputs", "unstable"] } -zcash_client_sqlite = { version = "0.11.2", features = ["orchard", "transparent-inputs", "unstable"] } -zcash_primitives = "0.16" -zcash_proofs = "0.16" +orchard = "0.10" +sapling = { package = "sapling-crypto", version = "0.3", default-features = false } +zcash_address = "0.6" +zcash_client_backend = { version = "0.14", features = ["orchard", "tor", "transparent-inputs", "unstable"] } +zcash_client_sqlite = { version = "0.12.2", features = ["orchard", "transparent-inputs", "unstable"] } +zcash_primitives = "0.19" +zcash_proofs = "0.19" # Infrastructure prost = "0.13" diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt index ca5b8f857..3379607fc 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/Backend.kt @@ -1,6 +1,7 @@ package cash.z.ecc.android.sdk.internal import cash.z.ecc.android.sdk.internal.model.JniBlockMeta +import cash.z.ecc.android.sdk.internal.model.JniRewindResult import cash.z.ecc.android.sdk.internal.model.JniScanRange import cash.z.ecc.android.sdk.internal.model.JniScanSummary import cash.z.ecc.android.sdk.internal.model.JniSubtreeRoot @@ -121,13 +122,11 @@ interface Backend { outputIndex: Int ): String? - suspend fun getNearestRewindHeight(height: Long): Long - /** * @throws RuntimeException as a common indicator of the operation failure */ @Throws(RuntimeException::class) - suspend fun rewindToHeight(height: Long) + suspend fun rewindToHeight(height: Long): JniRewindResult /** * @throws RuntimeException as a common indicator of the operation failure diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt index ef7aef277..92fa1f4ea 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt @@ -5,6 +5,7 @@ import cash.z.ecc.android.sdk.internal.SdkDispatchers 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.internal.model.JniRewindResult import cash.z.ecc.android.sdk.internal.model.JniScanRange import cash.z.ecc.android.sdk.internal.model.JniScanSummary import cash.z.ecc.android.sdk.internal.model.JniSubtreeRoot @@ -184,21 +185,10 @@ class RustBackend private constructor( ) } - override suspend fun getNearestRewindHeight(height: Long): Long = - withContext(SdkDispatchers.DATABASE_IO) { - getNearestRewindHeight( - dataDbFile.absolutePath, - height, - networkId = networkId - ) - } - /** - * Deletes data for all blocks above the given height. Boils down to: - * - * DELETE FROM blocks WHERE height > ? + * Rewinds the data database to at most the given height. */ - override suspend fun rewindToHeight(height: Long) = + override suspend fun rewindToHeight(height: Long): JniRewindResult = withContext(SdkDispatchers.DATABASE_IO) { rewindToHeight( dataDbFile.absolutePath, @@ -580,19 +570,12 @@ class RustBackend private constructor( height: Long ) - @JvmStatic - private external fun getNearestRewindHeight( - dbDataPath: String, - height: Long, - networkId: Int - ): Long - @JvmStatic private external fun rewindToHeight( dbDataPath: String, height: Long, networkId: Int - ) + ): JniRewindResult @JvmStatic @Suppress("LongParameterList") diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniRewindResult.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniRewindResult.kt new file mode 100644 index 000000000..544ee4459 --- /dev/null +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/JniRewindResult.kt @@ -0,0 +1,43 @@ +package cash.z.ecc.android.sdk.internal.model + +import androidx.annotation.Keep +import cash.z.ecc.android.sdk.internal.ext.isInUIntRange + +/** + * Serves as cross layer (Kotlin, Rust) communication class. + */ +@Keep +sealed class JniRewindResult { + /** + * A rewind was successful. + * + * `height` is the height to which the data store was actually truncated. + */ + @Keep + class Success(val height: Long) : JniRewindResult() { + init { + require(height.isInUIntRange()) { + "Height $height is outside of allowed UInt range" + } + } + } + + /** + * A requested rewind would violate invariants of the storage layer. + * + * If no safe rewind height can be determined, the safe rewind height member will be -1. + */ + @Keep + class Invalid(val safeRewindHeight: Long, val requestedHeight: Long) : JniRewindResult() { + init { + if (safeRewindHeight != -1L) { + require(safeRewindHeight.isInUIntRange()) { + "Height $safeRewindHeight is outside of allowed UInt range and is not -1" + } + } + require(requestedHeight.isInUIntRange()) { + "Height $requestedHeight is outside of allowed UInt range" + } + } + } +} diff --git a/backend-lib/src/main/rust/lib.rs b/backend-lib/src/main/rust/lib.rs index 14dfc65e4..0482aa849 100644 --- a/backend-lib/src/main/rust/lib.rs +++ b/backend-lib/src/main/rust/lib.rs @@ -41,6 +41,7 @@ use zcash_client_backend::{ zip321::{Payment, TransactionRequest}, ShieldedProtocol, }; +use zcash_client_sqlite::error::SqliteClientError; use zcash_client_sqlite::{ chain::{init::init_blockmeta_db, BlockMeta}, wallet::init::{init_wallet_db, WalletMigrationError}, @@ -159,7 +160,7 @@ fn account_id_from_jni( /// # Panics /// /// This method panics if called more than once. -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initOnLoad<'local>( mut _env: JNIEnv<'local>, _: JClass<'local>, @@ -222,7 +223,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initOnLoa /// Sets up the internal structure of the blockmeta database. /// /// Returns 0 if successful, or -1 otherwise. -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initBlockMetaDb<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -247,7 +248,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initBlock /// - 0 if successful. /// - 1 if the seed must be provided in order to execute the requested migrations. /// - 2 if the provided seed is not relevant to any of the derived accounts in the wallet. -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_initDataDb<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -333,7 +334,7 @@ fn decode_usk(env: &JNIEnv, usk: JByteArray) -> anyhow::Result( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -379,7 +380,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createAcc } /// Checks whether the given seed is relevant to any of the derived accounts in the wallet. -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isSeedRelevantToAnyDerivedAccounts< 'local, >( @@ -408,7 +409,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isSeedRel /// Returns the newly created [ZIP 316] account identifier, along with the binary encoding /// of the [`UnifiedSpendingKey`] for the newly created account. The caller should store /// the returned spending key in a secure fashion. -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveSpendingKey< 'local, >( @@ -432,7 +433,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_de unwrap_exc_or(&mut env, res, ptr::null_mut()) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedFullViewingKeysFromSeed< 'local, >( @@ -477,7 +478,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_de unwrap_exc_or(&mut env, res, ptr::null_mut()) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedAddressFromSeed< 'local, >( @@ -510,7 +511,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_de unwrap_exc_or(&mut env, res, ptr::null_mut()) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedAddressFromViewingKey< 'local, >( @@ -546,7 +547,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_de unwrap_exc_or(&mut env, res, ptr::null_mut()) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_deriveUnifiedFullViewingKey< 'local, >( @@ -571,7 +572,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustDerivationTool_de unwrap_exc_or(&mut env, res, ptr::null_mut()) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getCurrentAddress<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -616,7 +617,7 @@ impl zcash_address::TryFromRawAddress for UnifiedAddressParser { } /// Returns the transparent receiver within the given Unified Address, if any. -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getTransparentReceiverForUnifiedAddress< 'local, >( @@ -660,7 +661,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getTransp } /// Returns the Sapling receiver within the given Unified Address, if any. -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getSaplingReceiverForUnifiedAddress< 'local, >( @@ -694,7 +695,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getSaplin unwrap_exc_or(&mut env, res, ptr::null_mut()) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidSpendingKey< 'local, >( @@ -710,7 +711,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidSp unwrap_exc_or(&mut env, res, JNI_FALSE) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidSaplingAddress< 'local, >( @@ -735,7 +736,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidSa unwrap_exc_or(&mut env, res, JNI_FALSE) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidTransparentAddress< 'local, >( @@ -760,7 +761,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidTr unwrap_exc_or(&mut env, res, JNI_FALSE) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidUnifiedAddress< 'local, >( @@ -785,7 +786,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidUn unwrap_exc_or(&mut env, res, JNI_FALSE) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidTexAddress<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -810,7 +811,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_isValidTe unwrap_exc_or(&mut env, res, JNI_FALSE) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getTotalTransparentBalance< 'local, >( @@ -861,7 +862,7 @@ fn parse_protocol(code: i32) -> Option { } } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getMemoAsUtf8<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -937,7 +938,7 @@ fn decode_blockmeta(env: &mut JNIEnv, obj: JObject) -> anyhow::Result }) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_writeBlockMetadata< 'local, >( @@ -974,7 +975,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_writeBloc unwrap_exc_or(&mut env, res, JNI_FALSE) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getLatestCacheHeight< 'local, >( @@ -999,7 +1000,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getLatest unwrap_exc_or(&mut env, res, -1) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_findBlockMetadata<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -1023,7 +1024,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_findBlock unwrap_exc_or(&mut env, res, ptr::null_mut()) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_rewindBlockMetadataToHeight< 'local, >( @@ -1049,63 +1050,56 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_rewindBlo unwrap_exc_or(&mut env, res, ()) } -#[no_mangle] -pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getNearestRewindHeight< - 'local, ->( - mut env: JNIEnv<'local>, - _: JClass<'local>, - db_data: JString<'local>, - height: jlong, - network_id: jint, -) -> jlong { - #[allow(deprecated)] - let res = catch_unwind(&mut env, |env| { - let _span = tracing::info_span!("RustBackend.getNearestRewindHeight").entered(); - if height < 100 { - Ok(height) - } else { - let network = parse_network(network_id as u32)?; - let db_data = wallet_db(env, network, db_data)?; - match db_data.get_min_unspent_height() { - Ok(Some(best_height)) => { - let first_unspent_note_height = u32::from(best_height); - Ok(std::cmp::min(first_unspent_note_height as i64, height)) - } - Ok(None) => Ok(height), - Err(e) => Err(anyhow!( - "Error while getting nearest rewind height for {}: {}", - height, - e - )), - } - } - }); - - unwrap_exc_or(&mut env, res, -1) as jlong +fn encode_rewind_result<'a>( + env: &mut JNIEnv<'a>, + requested_height: BlockHeight, + rewind_result: Result, +) -> anyhow::Result> { + match rewind_result { + Ok(height) => Ok(env.new_object( + "cash/z/ecc/android/sdk/internal/model/JniRewindResult$Success", + "(J)V", + &[JValue::Long(u32::from(height).into())], + )?), + Err(SqliteClientError::RequestedRewindInvalid { + safe_rewind_height: Some(safe_rewind_height), + .. + }) => Ok(env.new_object( + "cash/z/ecc/android/sdk/internal/model/JniRewindResult$Invalid", + "(JJ)V", + &[ + JValue::Long(u32::from(safe_rewind_height).into()), + JValue::Long(u32::from(requested_height).into()), + ], + )?), + Err(e) => Err(anyhow!( + "Error while rewinding data DB to height {}: {}", + requested_height, + e, + )), + } } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_rewindToHeight<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, db_data: JString<'local>, height: jlong, network_id: jint, -) -> jboolean { +) -> jobject { let res = catch_unwind(&mut env, |env| { let _span = tracing::info_span!("RustBackend.rewindToHeight").entered(); let network = parse_network(network_id as u32)?; let mut db_data = wallet_db(env, network, db_data)?; let height = BlockHeight::try_from(height)?; - db_data - .truncate_to_height(height) - .map(|_| 1) - .map_err(|e| anyhow!("Error while rewinding data DB to height {}: {}", height, e)) + let rewind_result = db_data.truncate_to_height(height); + + Ok(encode_rewind_result(env, height, rewind_result)?.into_raw()) }); - unwrap_exc_or(&mut env, res, JNI_FALSE) + unwrap_exc_or(&mut env, res, ptr::null_mut()) } fn decode_subtree_root( @@ -1128,7 +1122,7 @@ fn decode_subtree_root( )) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_putSubtreeRoots<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -1191,7 +1185,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_putSubtre unwrap_exc_or(&mut env, res, JNI_FALSE) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_updateChainTip<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -1214,7 +1208,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_updateCha unwrap_exc_or(&mut env, res, JNI_FALSE) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getFullyScannedHeight< 'local, >( @@ -1241,7 +1235,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getFullyS unwrap_exc_or(&mut env, res, -1) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getMaxScannedHeight< 'local, >( @@ -1342,12 +1336,21 @@ fn encode_wallet_summary<'a, P: Parameters>( let (progress_numerator, progress_denominator) = summary .scan_progress() - .map(|progress| (*progress.numerator(), *progress.denominator())) + .map(|scan_progress| { + let (recovery_numerator, recovery_denominator) = summary + .recovery_progress() + .map(|progress| (*progress.numerator(), *progress.denominator())) + .unwrap_or((0, 0)); + ( + *scan_progress.numerator() + recovery_numerator, + *scan_progress.denominator() + recovery_denominator, + ) + }) .unwrap_or((0, 1)); Ok(env.new_object( "cash/z/ecc/android/sdk/internal/model/JniWalletSummary", - &format!("([L{};JJJJJJ)V", JNI_ACCOUNT_BALANCE), + format!("([L{};JJJJJJ)V", JNI_ACCOUNT_BALANCE), &[ (&account_balances).into(), JValue::Long(i64::from(u32::from(summary.chain_tip_height()))), @@ -1360,7 +1363,7 @@ fn encode_wallet_summary<'a, P: Parameters>( )?) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getWalletSummary<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -1412,7 +1415,7 @@ fn encode_scan_range<'a>( ) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_suggestScanRanges<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -1462,7 +1465,7 @@ fn encode_scan_summary<'a>( )?) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_scanBlocks<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -1546,7 +1549,7 @@ fn encode_transaction_data_request<'a>( } } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_transactionDataRequests< 'local, >( @@ -1584,7 +1587,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_transacti unwrap_exc_or(&mut env, res, ptr::null_mut()) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_putUtxo<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -1629,7 +1632,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_putUtxo<' unwrap_exc_or(&mut env, res, JNI_FALSE) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_decryptAndStoreTransaction< 'local, >( @@ -1663,7 +1666,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_decryptAn unwrap_exc_or(&mut env, res, JNI_FALSE) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_setTransactionStatus< 'local, >( @@ -1699,13 +1702,19 @@ fn zip317_helper( change_memo: Option, ) -> GreedyInputSelector { GreedyInputSelector::new( - SingleOutputChangeStrategy::new(StandardFeeRule::Zip317, change_memo, ShieldedProtocol::Orchard), + SingleOutputChangeStrategy::new( + StandardFeeRule::Zip317, + change_memo, + ShieldedProtocol::Orchard, + ), DustOutputPolicy::default(), ) } -#[no_mangle] -pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeTransferFromUri<'local>( +#[unsafe(no_mangle)] +pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeTransferFromUri< + 'local, +>( mut env: JNIEnv<'local>, _: JClass<'local>, db_data: JString<'local>, @@ -1724,7 +1733,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeTr let input_selector = zip317_helper(None); let request = TransactionRequest::from_uri(&payment_uri) - .map_err(|e| anyhow!("Error creating transaction request: {:?}", e))?; + .map_err(|e| anyhow!("Error creating transaction request: {:?}", e))?; let proposal = propose_transfer::<_, _, _, Infallible>( &mut db_data, @@ -1747,7 +1756,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeTr unwrap_exc_or(&mut env, res, ptr::null_mut()) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeTransfer<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -1810,7 +1819,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeTr unwrap_exc_or(&mut env, res, ptr::null_mut()) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeShielding<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -1923,7 +1932,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeSh unwrap_exc_or(&mut env, res, ptr::null_mut()) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createProposedTransactions< 'local, >( @@ -1973,7 +1982,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createPro unwrap_exc_or(&mut env, res, ptr::null_mut()) } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_branchIdForHeight<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -1999,7 +2008,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_branchIdF // /// Creates a Tor runtime -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_model_TorClient_createTorRuntime<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, @@ -2017,7 +2026,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_model_TorClient_createTor } /// Frees a Tor runtime. -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_model_TorClient_freeTorRuntime<'local>( _: JNIEnv<'local>, _: JClass<'local>, @@ -2031,7 +2040,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_model_TorClient_freeTorRu } /// Fetches the current ZEC-USD exchange rate over Tor. -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_model_TorClient_getExchangeRateUsd< 'local, >( @@ -2096,7 +2105,7 @@ fn parse_network(value: u32) -> anyhow::Result { /// documentation of pointer::offset. /// - Call [`zcashlc_free_keys`] to free the memory associated with the returned pointer /// when done using it. -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_listTransparentReceivers< 'local, >( diff --git a/backend-lib/src/main/rust/local_rpc_types.rs b/backend-lib/src/main/rust/local_rpc_types.rs deleted file mode 100644 index 2dc195529..000000000 --- a/backend-lib/src/main/rust/local_rpc_types.rs +++ /dev/null @@ -1,786 +0,0 @@ -// This file is generated by rust-protobuf 2.22.0. Do not edit -// @generated - -// https://github.com/rust-lang/rust-clippy/issues/702 -#![allow(unknown_lints)] -#![allow(clippy::all)] - -#![allow(unused_attributes)] -#![rustfmt::skip] - -#![allow(box_pointers)] -#![allow(dead_code)] -#![allow(missing_docs)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] -#![allow(non_upper_case_globals)] -#![allow(trivial_casts)] -#![allow(unused_imports)] -#![allow(unused_results)] -//! Generated file from `local_rpc_types.proto` - -/// Generated files are compatible only with the same version -/// of protobuf runtime. -// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_22_0; - -#[derive(PartialEq,Clone,Default)] -pub struct TransactionDataList { - // message fields - pub data: ::protobuf::RepeatedField<::std::vec::Vec>, - // special fields - pub unknown_fields: ::protobuf::UnknownFields, - pub cached_size: ::protobuf::CachedSize, -} - -impl<'a> ::std::default::Default for &'a TransactionDataList { - fn default() -> &'a TransactionDataList { - ::default_instance() - } -} - -impl TransactionDataList { - pub fn new() -> TransactionDataList { - ::std::default::Default::default() - } - - // repeated bytes data = 1; - - - pub fn get_data(&self) -> &[::std::vec::Vec] { - &self.data - } - pub fn clear_data(&mut self) { - self.data.clear(); - } - - // Param is passed by value, moved - pub fn set_data(&mut self, v: ::protobuf::RepeatedField<::std::vec::Vec>) { - self.data = v; - } - - // Mutable pointer to the field. - pub fn mut_data(&mut self) -> &mut ::protobuf::RepeatedField<::std::vec::Vec> { - &mut self.data - } - - // Take field - pub fn take_data(&mut self) -> ::protobuf::RepeatedField<::std::vec::Vec> { - ::std::mem::replace(&mut self.data, ::protobuf::RepeatedField::new()) - } -} - -impl ::protobuf::Message for TransactionDataList { - fn is_initialized(&self) -> bool { - true - } - - fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> { - while !is.eof()? { - let (field_number, wire_type) = is.read_tag_unpack()?; - match field_number { - 1 => { - ::protobuf::rt::read_repeated_bytes_into(wire_type, is, &mut self.data)?; - }, - _ => { - ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; - }, - }; - } - ::std::result::Result::Ok(()) - } - - // Compute sizes of nested messages - #[allow(unused_variables)] - fn compute_size(&self) -> u32 { - let mut my_size = 0; - for value in &self.data { - my_size += ::protobuf::rt::bytes_size(1, &value); - }; - my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); - self.cached_size.set(my_size); - my_size - } - - fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { - for v in &self.data { - os.write_bytes(1, &v)?; - }; - os.write_unknown_fields(self.get_unknown_fields())?; - ::std::result::Result::Ok(()) - } - - fn get_cached_size(&self) -> u32 { - self.cached_size.get() - } - - fn get_unknown_fields(&self) -> &::protobuf::UnknownFields { - &self.unknown_fields - } - - fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields { - &mut self.unknown_fields - } - - fn as_any(&self) -> &dyn (::std::any::Any) { - self as &dyn (::std::any::Any) - } - fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) { - self as &mut dyn (::std::any::Any) - } - fn into_any(self: ::std::boxed::Box) -> ::std::boxed::Box { - self - } - - fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor { - Self::descriptor_static() - } - - fn new() -> TransactionDataList { - TransactionDataList::new() - } - - fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor { - static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT; - descriptor.get(|| { - let mut fields = ::std::vec::Vec::new(); - fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>( - "data", - |m: &TransactionDataList| { &m.data }, - |m: &mut TransactionDataList| { &mut m.data }, - )); - ::protobuf::reflect::MessageDescriptor::new_pb_name::( - "TransactionDataList", - fields, - file_descriptor_proto() - ) - }) - } - - fn default_instance() -> &'static TransactionDataList { - static instance: ::protobuf::rt::LazyV2 = ::protobuf::rt::LazyV2::INIT; - instance.get(TransactionDataList::new) - } -} - -impl ::protobuf::Clear for TransactionDataList { - fn clear(&mut self) { - self.data.clear(); - self.unknown_fields.clear(); - } -} - -impl ::std::fmt::Debug for TransactionDataList { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - ::protobuf::text_format::fmt(self, f) - } -} - -impl ::protobuf::reflect::ProtobufValue for TransactionDataList { - fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { - ::protobuf::reflect::ReflectValueRef::Message(self) - } -} - -#[derive(PartialEq,Clone,Default)] -pub struct TransparentTransactionList { - // message fields - pub transactions: ::protobuf::RepeatedField, - // special fields - pub unknown_fields: ::protobuf::UnknownFields, - pub cached_size: ::protobuf::CachedSize, -} - -impl<'a> ::std::default::Default for &'a TransparentTransactionList { - fn default() -> &'a TransparentTransactionList { - ::default_instance() - } -} - -impl TransparentTransactionList { - pub fn new() -> TransparentTransactionList { - ::std::default::Default::default() - } - - // repeated .cash.z.ecc.android.sdk.rpc.TransparentTransaction transactions = 1; - - - pub fn get_transactions(&self) -> &[TransparentTransaction] { - &self.transactions - } - pub fn clear_transactions(&mut self) { - self.transactions.clear(); - } - - // Param is passed by value, moved - pub fn set_transactions(&mut self, v: ::protobuf::RepeatedField) { - self.transactions = v; - } - - // Mutable pointer to the field. - pub fn mut_transactions(&mut self) -> &mut ::protobuf::RepeatedField { - &mut self.transactions - } - - // Take field - pub fn take_transactions(&mut self) -> ::protobuf::RepeatedField { - ::std::mem::replace(&mut self.transactions, ::protobuf::RepeatedField::new()) - } -} - -impl ::protobuf::Message for TransparentTransactionList { - fn is_initialized(&self) -> bool { - for v in &self.transactions { - if !v.is_initialized() { - return false; - } - }; - true - } - - fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> { - while !is.eof()? { - let (field_number, wire_type) = is.read_tag_unpack()?; - match field_number { - 1 => { - ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.transactions)?; - }, - _ => { - ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; - }, - }; - } - ::std::result::Result::Ok(()) - } - - // Compute sizes of nested messages - #[allow(unused_variables)] - fn compute_size(&self) -> u32 { - let mut my_size = 0; - for value in &self.transactions { - let len = value.compute_size(); - my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len; - }; - my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); - self.cached_size.set(my_size); - my_size - } - - fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { - for v in &self.transactions { - os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?; - os.write_raw_varint32(v.get_cached_size())?; - v.write_to_with_cached_sizes(os)?; - }; - os.write_unknown_fields(self.get_unknown_fields())?; - ::std::result::Result::Ok(()) - } - - fn get_cached_size(&self) -> u32 { - self.cached_size.get() - } - - fn get_unknown_fields(&self) -> &::protobuf::UnknownFields { - &self.unknown_fields - } - - fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields { - &mut self.unknown_fields - } - - fn as_any(&self) -> &dyn (::std::any::Any) { - self as &dyn (::std::any::Any) - } - fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) { - self as &mut dyn (::std::any::Any) - } - fn into_any(self: ::std::boxed::Box) -> ::std::boxed::Box { - self - } - - fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor { - Self::descriptor_static() - } - - fn new() -> TransparentTransactionList { - TransparentTransactionList::new() - } - - fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor { - static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT; - descriptor.get(|| { - let mut fields = ::std::vec::Vec::new(); - fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage>( - "transactions", - |m: &TransparentTransactionList| { &m.transactions }, - |m: &mut TransparentTransactionList| { &mut m.transactions }, - )); - ::protobuf::reflect::MessageDescriptor::new_pb_name::( - "TransparentTransactionList", - fields, - file_descriptor_proto() - ) - }) - } - - fn default_instance() -> &'static TransparentTransactionList { - static instance: ::protobuf::rt::LazyV2 = ::protobuf::rt::LazyV2::INIT; - instance.get(TransparentTransactionList::new) - } -} - -impl ::protobuf::Clear for TransparentTransactionList { - fn clear(&mut self) { - self.transactions.clear(); - self.unknown_fields.clear(); - } -} - -impl ::std::fmt::Debug for TransparentTransactionList { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - ::protobuf::text_format::fmt(self, f) - } -} - -impl ::protobuf::reflect::ProtobufValue for TransparentTransactionList { - fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { - ::protobuf::reflect::ReflectValueRef::Message(self) - } -} - -#[derive(PartialEq,Clone,Default)] -pub struct TransparentTransaction { - // message fields - pub protoVersion: u32, - pub expiryHeight: u32, - pub hasShieldedOutputs: bool, - pub hasShieldedSpends: bool, - pub height: u32, - pub value: i64, - pub toAddress: ::std::string::String, - pub fromAddress: ::std::string::String, - // special fields - pub unknown_fields: ::protobuf::UnknownFields, - pub cached_size: ::protobuf::CachedSize, -} - -impl<'a> ::std::default::Default for &'a TransparentTransaction { - fn default() -> &'a TransparentTransaction { - ::default_instance() - } -} - -impl TransparentTransaction { - pub fn new() -> TransparentTransaction { - ::std::default::Default::default() - } - - // uint32 protoVersion = 1; - - - pub fn get_protoVersion(&self) -> u32 { - self.protoVersion - } - pub fn clear_protoVersion(&mut self) { - self.protoVersion = 0; - } - - // Param is passed by value, moved - pub fn set_protoVersion(&mut self, v: u32) { - self.protoVersion = v; - } - - // uint32 expiryHeight = 2; - - - pub fn get_expiryHeight(&self) -> u32 { - self.expiryHeight - } - pub fn clear_expiryHeight(&mut self) { - self.expiryHeight = 0; - } - - // Param is passed by value, moved - pub fn set_expiryHeight(&mut self, v: u32) { - self.expiryHeight = v; - } - - // bool hasShieldedOutputs = 3; - - - pub fn get_hasShieldedOutputs(&self) -> bool { - self.hasShieldedOutputs - } - pub fn clear_hasShieldedOutputs(&mut self) { - self.hasShieldedOutputs = false; - } - - // Param is passed by value, moved - pub fn set_hasShieldedOutputs(&mut self, v: bool) { - self.hasShieldedOutputs = v; - } - - // bool hasShieldedSpends = 4; - - - pub fn get_hasShieldedSpends(&self) -> bool { - self.hasShieldedSpends - } - pub fn clear_hasShieldedSpends(&mut self) { - self.hasShieldedSpends = false; - } - - // Param is passed by value, moved - pub fn set_hasShieldedSpends(&mut self, v: bool) { - self.hasShieldedSpends = v; - } - - // uint32 height = 5; - - - pub fn get_height(&self) -> u32 { - self.height - } - pub fn clear_height(&mut self) { - self.height = 0; - } - - // Param is passed by value, moved - pub fn set_height(&mut self, v: u32) { - self.height = v; - } - - // int64 value = 6; - - - pub fn get_value(&self) -> i64 { - self.value - } - pub fn clear_value(&mut self) { - self.value = 0; - } - - // Param is passed by value, moved - pub fn set_value(&mut self, v: i64) { - self.value = v; - } - - // string toAddress = 7; - - - pub fn get_toAddress(&self) -> &str { - &self.toAddress - } - pub fn clear_toAddress(&mut self) { - self.toAddress.clear(); - } - - // Param is passed by value, moved - pub fn set_toAddress(&mut self, v: ::std::string::String) { - self.toAddress = v; - } - - // Mutable pointer to the field. - // If field is not initialized, it is initialized with default value first. - pub fn mut_toAddress(&mut self) -> &mut ::std::string::String { - &mut self.toAddress - } - - // Take field - pub fn take_toAddress(&mut self) -> ::std::string::String { - ::std::mem::replace(&mut self.toAddress, ::std::string::String::new()) - } - - // string fromAddress = 8; - - - pub fn get_fromAddress(&self) -> &str { - &self.fromAddress - } - pub fn clear_fromAddress(&mut self) { - self.fromAddress.clear(); - } - - // Param is passed by value, moved - pub fn set_fromAddress(&mut self, v: ::std::string::String) { - self.fromAddress = v; - } - - // Mutable pointer to the field. - // If field is not initialized, it is initialized with default value first. - pub fn mut_fromAddress(&mut self) -> &mut ::std::string::String { - &mut self.fromAddress - } - - // Take field - pub fn take_fromAddress(&mut self) -> ::std::string::String { - ::std::mem::replace(&mut self.fromAddress, ::std::string::String::new()) - } -} - -impl ::protobuf::Message for TransparentTransaction { - fn is_initialized(&self) -> bool { - true - } - - fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> { - while !is.eof()? { - let (field_number, wire_type) = is.read_tag_unpack()?; - match field_number { - 1 => { - if wire_type != ::protobuf::wire_format::WireTypeVarint { - return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type)); - } - let tmp = is.read_uint32()?; - self.protoVersion = tmp; - }, - 2 => { - if wire_type != ::protobuf::wire_format::WireTypeVarint { - return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type)); - } - let tmp = is.read_uint32()?; - self.expiryHeight = tmp; - }, - 3 => { - if wire_type != ::protobuf::wire_format::WireTypeVarint { - return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type)); - } - let tmp = is.read_bool()?; - self.hasShieldedOutputs = tmp; - }, - 4 => { - if wire_type != ::protobuf::wire_format::WireTypeVarint { - return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type)); - } - let tmp = is.read_bool()?; - self.hasShieldedSpends = tmp; - }, - 5 => { - if wire_type != ::protobuf::wire_format::WireTypeVarint { - return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type)); - } - let tmp = is.read_uint32()?; - self.height = tmp; - }, - 6 => { - if wire_type != ::protobuf::wire_format::WireTypeVarint { - return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type)); - } - let tmp = is.read_int64()?; - self.value = tmp; - }, - 7 => { - ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.toAddress)?; - }, - 8 => { - ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.fromAddress)?; - }, - _ => { - ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; - }, - }; - } - ::std::result::Result::Ok(()) - } - - // Compute sizes of nested messages - #[allow(unused_variables)] - fn compute_size(&self) -> u32 { - let mut my_size = 0; - if self.protoVersion != 0 { - my_size += ::protobuf::rt::value_size(1, self.protoVersion, ::protobuf::wire_format::WireTypeVarint); - } - if self.expiryHeight != 0 { - my_size += ::protobuf::rt::value_size(2, self.expiryHeight, ::protobuf::wire_format::WireTypeVarint); - } - if self.hasShieldedOutputs != false { - my_size += 2; - } - if self.hasShieldedSpends != false { - my_size += 2; - } - if self.height != 0 { - my_size += ::protobuf::rt::value_size(5, self.height, ::protobuf::wire_format::WireTypeVarint); - } - if self.value != 0 { - my_size += ::protobuf::rt::value_size(6, self.value, ::protobuf::wire_format::WireTypeVarint); - } - if !self.toAddress.is_empty() { - my_size += ::protobuf::rt::string_size(7, &self.toAddress); - } - if !self.fromAddress.is_empty() { - my_size += ::protobuf::rt::string_size(8, &self.fromAddress); - } - my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); - self.cached_size.set(my_size); - my_size - } - - fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> { - if self.protoVersion != 0 { - os.write_uint32(1, self.protoVersion)?; - } - if self.expiryHeight != 0 { - os.write_uint32(2, self.expiryHeight)?; - } - if self.hasShieldedOutputs != false { - os.write_bool(3, self.hasShieldedOutputs)?; - } - if self.hasShieldedSpends != false { - os.write_bool(4, self.hasShieldedSpends)?; - } - if self.height != 0 { - os.write_uint32(5, self.height)?; - } - if self.value != 0 { - os.write_int64(6, self.value)?; - } - if !self.toAddress.is_empty() { - os.write_string(7, &self.toAddress)?; - } - if !self.fromAddress.is_empty() { - os.write_string(8, &self.fromAddress)?; - } - os.write_unknown_fields(self.get_unknown_fields())?; - ::std::result::Result::Ok(()) - } - - fn get_cached_size(&self) -> u32 { - self.cached_size.get() - } - - fn get_unknown_fields(&self) -> &::protobuf::UnknownFields { - &self.unknown_fields - } - - fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields { - &mut self.unknown_fields - } - - fn as_any(&self) -> &dyn (::std::any::Any) { - self as &dyn (::std::any::Any) - } - fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) { - self as &mut dyn (::std::any::Any) - } - fn into_any(self: ::std::boxed::Box) -> ::std::boxed::Box { - self - } - - fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor { - Self::descriptor_static() - } - - fn new() -> TransparentTransaction { - TransparentTransaction::new() - } - - fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor { - static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT; - descriptor.get(|| { - let mut fields = ::std::vec::Vec::new(); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeUint32>( - "protoVersion", - |m: &TransparentTransaction| { &m.protoVersion }, - |m: &mut TransparentTransaction| { &mut m.protoVersion }, - )); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeUint32>( - "expiryHeight", - |m: &TransparentTransaction| { &m.expiryHeight }, - |m: &mut TransparentTransaction| { &mut m.expiryHeight }, - )); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>( - "hasShieldedOutputs", - |m: &TransparentTransaction| { &m.hasShieldedOutputs }, - |m: &mut TransparentTransaction| { &mut m.hasShieldedOutputs }, - )); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>( - "hasShieldedSpends", - |m: &TransparentTransaction| { &m.hasShieldedSpends }, - |m: &mut TransparentTransaction| { &mut m.hasShieldedSpends }, - )); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeUint32>( - "height", - |m: &TransparentTransaction| { &m.height }, - |m: &mut TransparentTransaction| { &mut m.height }, - )); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>( - "value", - |m: &TransparentTransaction| { &m.value }, - |m: &mut TransparentTransaction| { &mut m.value }, - )); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( - "toAddress", - |m: &TransparentTransaction| { &m.toAddress }, - |m: &mut TransparentTransaction| { &mut m.toAddress }, - )); - fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>( - "fromAddress", - |m: &TransparentTransaction| { &m.fromAddress }, - |m: &mut TransparentTransaction| { &mut m.fromAddress }, - )); - ::protobuf::reflect::MessageDescriptor::new_pb_name::( - "TransparentTransaction", - fields, - file_descriptor_proto() - ) - }) - } - - fn default_instance() -> &'static TransparentTransaction { - static instance: ::protobuf::rt::LazyV2 = ::protobuf::rt::LazyV2::INIT; - instance.get(TransparentTransaction::new) - } -} - -impl ::protobuf::Clear for TransparentTransaction { - fn clear(&mut self) { - self.protoVersion = 0; - self.expiryHeight = 0; - self.hasShieldedOutputs = false; - self.hasShieldedSpends = false; - self.height = 0; - self.value = 0; - self.toAddress.clear(); - self.fromAddress.clear(); - self.unknown_fields.clear(); - } -} - -impl ::std::fmt::Debug for TransparentTransaction { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - ::protobuf::text_format::fmt(self, f) - } -} - -impl ::protobuf::reflect::ProtobufValue for TransparentTransaction { - fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef { - ::protobuf::reflect::ReflectValueRef::Message(self) - } -} - -static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x15local_rpc_types.proto\x12\x1acash.z.ecc.android.sdk.rpc\"-\n\x13Tr\ - ansactionDataList\x12\x14\n\x04data\x18\x01\x20\x03(\x0cR\x04dataB\0:\0\ - \"x\n\x1aTransparentTransactionList\x12X\n\x0ctransactions\x18\x01\x20\ - \x03(\x0b22.cash.z.ecc.android.sdk.rpc.TransparentTransactionR\x0ctransa\ - ctionsB\0:\0\"\xbe\x02\n\x16TransparentTransaction\x12$\n\x0cprotoVersio\ - n\x18\x01\x20\x01(\rR\x0cprotoVersionB\0\x12$\n\x0cexpiryHeight\x18\x02\ - \x20\x01(\rR\x0cexpiryHeightB\0\x120\n\x12hasShieldedOutputs\x18\x03\x20\ - \x01(\x08R\x12hasShieldedOutputsB\0\x12.\n\x11hasShieldedSpends\x18\x04\ - \x20\x01(\x08R\x11hasShieldedSpendsB\0\x12\x18\n\x06height\x18\x05\x20\ - \x01(\rR\x06heightB\0\x12\x16\n\x05value\x18\x06\x20\x01(\x03R\x05valueB\ - \0\x12\x1e\n\ttoAddress\x18\x07\x20\x01(\tR\ttoAddressB\0\x12\"\n\x0bfro\ - mAddress\x18\x08\x20\x01(\tR\x0bfromAddressB\0:\0B\0b\x06proto3\ -"; - -static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; - -fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto { - ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap() -} - -pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { - file_descriptor_proto_lazy.get(|| { - parse_descriptor_proto() - }) -} diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt index b474eaa40..1053a16b6 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt @@ -327,13 +327,16 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) } /** - * This rewinds to the nearest height, i.e. 14 days back from the current chain tip. + * This rewinds to the nearest height, i.e. 100 blocks back from the current chain tip. */ fun rewind() { val synchronizer = synchronizer.value - if (null != synchronizer) { + val currentBlockHeight = synchronizer?.networkHeight?.value + if (null != synchronizer && null != currentBlockHeight) { viewModelScope.launch { - synchronizer.quickRewind() + synchronizer.rewindToNearestHeight( + BlockHeight.new(currentBlockHeight.value - QUICK_REWIND_BLOCKS) + ) } } } @@ -349,6 +352,10 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) } } } + + companion object { + private const val QUICK_REWIND_BLOCKS = 100 + } } /** diff --git a/docs/Setup.md b/docs/Setup.md index 0a0764480..5ddb3d9f9 100644 --- a/docs/Setup.md +++ b/docs/Setup.md @@ -15,12 +15,12 @@ Start by making sure the command line with Gradle works first, because **all the JVM distributions are available and should work, we have settled on recommending [Adoptium/Temurin](https://adoptium.net), because this is the default distribution used by Gradle toolchains. For Windows or Linux, be sure that the `JAVA_HOME` environment variable points to the right Java version. Note: If you switch from a newer to an older JVM version, you may see an error like the following `> com.android.ide.common.signing.KeytoolException: Failed to read key AndroidDebugKey from store "~/.android/debug.keystore": Integrity check failed: java.security.NoSuchAlgorithmException: Algorithm HmacPBESHA256 not available`. A solution is to delete the debug keystore and allow it to be re-generated. 1. Android Studio has an embedded JVM, although running Gradle tasks from the command line requires a separate JVM to be installed. Our Gradle scripts are configured to use toolchains to automatically install the correct JVM version. 1. Configure Rust - 1. [Install Rust](https://www.rust-lang.org/learn/get-started). You will need at least Rust 1.80. Older versions of Rust will fail. If you use system packages, check the provided version. + 1. [Install Rust](https://www.rust-lang.org/learn/get-started). You will need at least Rust 1.82. Older versions of Rust will fail. If you use system packages, check the provided version. 1. macOS with Homebrew 1. `brew install rustup` 1. `rustup-init` - 1. `rustup install 1.80.0` - 1. `rustup default 1.80.0` + 1. `rustup install 1.82.0` + 1. `rustup default 1.82.0` 1. Add Android targets ```bash rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 239ebb510..a78a167e3 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.80.0" +channel = "1.82.0" targets = [ "armv7-linux-androideabi", "aarch64-linux-android", diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt index da36f2b1d..35df3118a 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt @@ -2,6 +2,7 @@ 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.internal.model.JniRewindResult import cash.z.ecc.android.sdk.internal.model.JniScanRange import cash.z.ecc.android.sdk.internal.model.JniSubtreeRoot import cash.z.ecc.android.sdk.internal.model.JniTransactionDataRequest @@ -17,8 +18,9 @@ internal class FakeRustBackend( metadata.addAll(blockMetadata) } - override suspend fun rewindToHeight(height: Long) { + override suspend fun rewindToHeight(height: Long): JniRewindResult { metadata.removeAll { it.height > height } + return JniRewindResult.Success(height) } override suspend fun putSubtreeRoots( @@ -180,10 +182,6 @@ internal class FakeRustBackend( error("Intentionally not implemented yet.") } - override suspend fun getNearestRewindHeight(height: Long): Long { - error("Intentionally not implemented yet.") - } - override suspend fun scanBlocks( fromHeight: Long, fromState: ByteArray, diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt index 82289102c..1803f6970 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt @@ -413,17 +413,8 @@ class SdkSynchronizer private constructor( } } - override suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight = - processor.getNearestRewindHeight( - height - ) - - override suspend fun rewindToNearestHeight(height: BlockHeight) { - processor.rewindToNearestHeight(height) - } - - override suspend fun quickRewind() { - processor.quickRewind() + override suspend fun rewindToNearestHeight(height: BlockHeight): BlockHeight? { + return processor.rewindToNearestHeight(height) } override fun getMemos(transactionOverview: TransactionOverview): Flow { diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt index b59e3c79c..5e7ef677b 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt @@ -424,28 +424,17 @@ interface Synchronizer { */ suspend fun getTransparentBalance(tAddr: String): Zatoshi - /** - * Returns the safest height to which we can rewind, given a desire to rewind to the height - * provided. Due to how witness incrementing works, a wallet cannot simply rewind to any - * arbitrary height. This handles all that complexity yet remains flexible in the future as - * improvements are made. - */ - suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight - /** * Rewinds to the safest height to which we can rewind, given a desire to rewind to the height - * provided. Due to how witness incrementing works, a wallet cannot simply rewind to any + * provided. + * + * Due to how witness incrementing works, a wallet cannot simply rewind to any * arbitrary height. This handles all that complexity yet remains flexible in the future as * improvements are made. + * + * Returns the height to which we actually rewound, or `null` if the rewind failed. */ - suspend fun rewindToNearestHeight(height: BlockHeight) - - /** - * Rewinds to the safest height approximately 14 days backward from the current chain tip. Due to how witness - * incrementing works, a wallet cannot simply rewind to any arbitrary height. This handles all that complexity - * yet remains flexible in the future as improvements are made. - */ - suspend fun quickRewind() + suspend fun rewindToNearestHeight(height: BlockHeight): BlockHeight? /** * Returns a stream of memos for a transaction. It works for both received and sent transaction. diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt index 9b06aeff6..814db9ab6 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/block/processor/CompactBlockProcessor.kt @@ -24,7 +24,6 @@ import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException.Mismatche import cash.z.ecc.android.sdk.exception.InitializeException import cash.z.ecc.android.sdk.exception.LightWalletException import cash.z.ecc.android.sdk.exception.TransactionEncoderException -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.ext.ZcashSdk.POLL_INTERVAL_SHORT @@ -42,6 +41,7 @@ import cash.z.ecc.android.sdk.internal.ext.toHexReversed import cash.z.ecc.android.sdk.internal.metrics.TraceScope import cash.z.ecc.android.sdk.internal.model.BlockBatch import cash.z.ecc.android.sdk.internal.model.JniBlockMeta +import cash.z.ecc.android.sdk.internal.model.RewindResult import cash.z.ecc.android.sdk.internal.model.ScanRange import cash.z.ecc.android.sdk.internal.model.SubtreeRoot import cash.z.ecc.android.sdk.internal.model.SuggestScanRangePriority @@ -90,7 +90,6 @@ import kotlin.math.max import kotlin.math.min import kotlin.random.Random import kotlin.time.Duration -import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.minutes import kotlin.time.DurationUnit import kotlin.time.toDuration @@ -2302,72 +2301,59 @@ class CompactBlockProcessor internal constructor( } } - suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight { - // TODO [#1545]: Improve getNearestRewindHeight - // Add a concept of original checkpoint height to the processor. For now, derive it - // add one because we already have the checkpoint. Add one again because we delete ABOVE the block - // TODO [#1545]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/1545 - val originalCheckpoint = lowerBoundHeight + MAX_REORG_SIZE + 2 - return if (height < originalCheckpoint) { - originalCheckpoint - } else { - // tricky: subtract one because we delete ABOVE this block - // This could create an invalid height if height was saplingActivationHeight - val rewindHeight = BlockHeight(height.value - 1) - backend.getNearestRewindHeight(rewindHeight) - } - } - - /** - * Rewind back two weeks worth of blocks. The final amount of rewinded blocks depends on [rewindToNearestHeight]. - */ - suspend fun quickRewind(): Boolean { - val height = - when (val result = getMaxScannedHeight(backend)) { - is GetMaxScannedHeightResult.Success -> result.height - else -> return false - } - val blocksPer14Days = 14.days.inWholeMilliseconds / ZcashSdk.BLOCK_INTERVAL_MILLIS.toInt() - val twoWeeksBack = BlockHeight.new((height.value - blocksPer14Days).coerceAtLeast(lowerBoundHeight.value)) - return rewindToNearestHeight(twoWeeksBack) - } - @Suppress("LongMethod") - suspend fun rewindToNearestHeight(height: BlockHeight): Boolean { + suspend fun rewindToNearestHeight(height: BlockHeight): BlockHeight? { processingMutex.withLockLogged("rewindToHeight") { val lastLocalBlock = when (val result = getMaxScannedHeight(backend)) { is GetMaxScannedHeightResult.Success -> result.height - else -> return false + else -> return null } - val targetHeight = getNearestRewindHeight(height) Twig.debug { - "Rewinding to requested height: $height using target height: $targetHeight with last local block:" + - " $lastLocalBlock" + "Rewinding to requested height: $height with last local block: $lastLocalBlock" } - if (targetHeight < lastLocalBlock) { - Twig.debug { "Rewinding because targetHeight is less than lastLocalBlock." } - runCatching { - backend.rewindToHeight(targetHeight) - downloader.rewindToHeight(targetHeight) - }.onFailure { - Twig.error { "Rewinding to the targetHeight $targetHeight failed with $it" } - }.onSuccess { - Twig.info { "Rewind to $targetHeight was successful." } - setState(newState = State.Syncing) - setProgress(progress = PercentDecimal.ZERO_PERCENT) - setProcessorInfo(overallSyncRange = null) - } + return if (height < lastLocalBlock) { + Twig.debug { "Rewinding because height is less than lastLocalBlock." } + rewindToHeightInner(height) } else { Twig.info { - "Not rewinding dataDb because last local block is $lastLocalBlock which is less than the target " + - "height of $targetHeight" + "Not rewinding dataDb because last local block is $lastLocalBlock which is less than the " + + "requested height of $height" } + lastLocalBlock } } - return true + } + + private suspend fun rewindToHeightInner(height: BlockHeight): BlockHeight? { + val ret = + runCatching { + backend.rewindToHeight(height) + }.onFailure { + Twig.error { "Rewinding to $height failed with $it" } + }.mapCatching { + when (it) { + is RewindResult.Success -> { + downloader.rewindToHeight(it.height) + Twig.info { "Rewound to ${it.height} successfully" } + setState(newState = State.Syncing) + setProgress(progress = PercentDecimal.ZERO_PERCENT) + setProcessorInfo(overallSyncRange = null) + it.height + } + is RewindResult.Invalid -> { + Twig.warn { "Requested rewind height ${it.requestedHeight} is invalid" } + if (it.safeRewindHeight != null) { + rewindToHeightInner(it.safeRewindHeight) + } else { + null + } + } + } + } + return ret.getOrNull() } /** insightful function for debugging these critical errors */ diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt index e74c21f7d..8e5545e97 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt @@ -3,6 +3,7 @@ package cash.z.ecc.android.sdk.internal import cash.z.ecc.android.sdk.exception.InitializeException import cash.z.ecc.android.sdk.exception.RustLayerException import cash.z.ecc.android.sdk.internal.model.JniBlockMeta +import cash.z.ecc.android.sdk.internal.model.RewindResult import cash.z.ecc.android.sdk.internal.model.ScanRange import cash.z.ecc.android.sdk.internal.model.ScanSummary import cash.z.ecc.android.sdk.internal.model.SubtreeRoot @@ -61,9 +62,7 @@ internal interface TypesafeBackend { fun getBranchIdForHeight(height: BlockHeight): Long - suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight - - suspend fun rewindToHeight(height: BlockHeight) + suspend fun rewindToHeight(height: BlockHeight): RewindResult suspend fun getLatestCacheHeight(): BlockHeight? diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt index 88b93ef11..342dafc70 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt @@ -4,6 +4,7 @@ import cash.z.ecc.android.sdk.exception.InitializeException import cash.z.ecc.android.sdk.exception.RustLayerException import cash.z.ecc.android.sdk.internal.model.JniBlockMeta import cash.z.ecc.android.sdk.internal.model.JniSubtreeRoot +import cash.z.ecc.android.sdk.internal.model.RewindResult import cash.z.ecc.android.sdk.internal.model.ScanRange import cash.z.ecc.android.sdk.internal.model.ScanSummary import cash.z.ecc.android.sdk.internal.model.SubtreeRoot @@ -109,12 +110,8 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke return backend.getBranchIdForHeight(height.value) } - override suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight { - return BlockHeight.new(backend.getNearestRewindHeight(height.value)) - } - - override suspend fun rewindToHeight(height: BlockHeight) { - backend.rewindToHeight(height.value) + override suspend fun rewindToHeight(height: BlockHeight): RewindResult { + return RewindResult.new(backend.rewindToHeight(height.value)) } override suspend fun getLatestCacheHeight(): BlockHeight? { diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/RewindResult.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/RewindResult.kt new file mode 100644 index 000000000..86645ed4a --- /dev/null +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/RewindResult.kt @@ -0,0 +1,32 @@ +package cash.z.ecc.android.sdk.internal.model + +import cash.z.ecc.android.sdk.exception.SdkException +import cash.z.ecc.android.sdk.model.BlockHeight + +sealed interface RewindResult { + data class Success(val height: BlockHeight) : RewindResult + + data class Invalid( + val safeRewindHeight: BlockHeight?, + val requestedHeight: BlockHeight, + ) : RewindResult + + companion object { + fun new(jni: JniRewindResult): RewindResult { + return when (jni) { + is JniRewindResult.Success -> Success(BlockHeight.new(jni.height)) + is JniRewindResult.Invalid -> + Invalid( + if (jni.safeRewindHeight == -1L) { + null + } else { + BlockHeight.new(jni.safeRewindHeight) + }, + BlockHeight.new(jni.requestedHeight), + ) + + else -> throw SdkException("Unknown JniRewindResult variant", cause = null) + } + } + } +}