Replace Sapling balance and scan progress FFIs with wallet summary FFI
* Replace Sapling balance and scan progress FFIs with wallet summary FFI Closes zcash/zcash-android-wallet-sdk#1282. * Using test fixture for JniAccountBalance * Minor documentation changes * Fix typo --------- Co-authored-by: Honza <rychnovsky.honza@gmail.com>
This commit is contained in:
parent
b82278e747
commit
ae2d7152aa
|
@ -1,10 +1,10 @@
|
|||
package cash.z.ecc.android.sdk.internal
|
||||
|
||||
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
|
||||
import cash.z.ecc.android.sdk.internal.model.JniScanProgress
|
||||
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.JniUnifiedSpendingKey
|
||||
import cash.z.ecc.android.sdk.internal.model.JniWalletSummary
|
||||
|
||||
/**
|
||||
* Contract defining the exposed capabilities of the Rust backend.
|
||||
|
@ -69,12 +69,6 @@ interface Backend {
|
|||
|
||||
suspend fun listTransparentReceivers(account: Int): List<String>
|
||||
|
||||
/**
|
||||
* @throws RuntimeException as a common indicator of the operation failure
|
||||
*/
|
||||
@Throws(RuntimeException::class)
|
||||
suspend fun getBalance(account: Int): Long
|
||||
|
||||
fun getBranchIdForHeight(height: Long): Long
|
||||
|
||||
/**
|
||||
|
@ -83,12 +77,6 @@ interface Backend {
|
|||
@Throws(RuntimeException::class)
|
||||
suspend fun getMemoAsUtf8(txId: ByteArray, outputIndex: Int): String?
|
||||
|
||||
/**
|
||||
* @throws RuntimeException as a common indicator of the operation failure
|
||||
*/
|
||||
@Throws(RuntimeException::class)
|
||||
suspend fun getVerifiedBalance(account: Int): Long
|
||||
|
||||
suspend fun getNearestRewindHeight(height: Long): Long
|
||||
|
||||
/**
|
||||
|
@ -139,7 +127,7 @@ interface Backend {
|
|||
* @throws RuntimeException as a common indicator of the operation failure
|
||||
*/
|
||||
@Throws(RuntimeException::class)
|
||||
suspend fun getScanProgress(): JniScanProgress?
|
||||
suspend fun getWalletSummary(): JniWalletSummary?
|
||||
|
||||
/**
|
||||
* @throws RuntimeException as a common indicator of the operation failure
|
||||
|
|
|
@ -5,10 +5,10 @@ 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.JniScanProgress
|
||||
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.JniUnifiedSpendingKey
|
||||
import cash.z.ecc.android.sdk.internal.model.JniWalletSummary
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
|
@ -108,30 +108,6 @@ class RustBackend private constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getBalance(account: Int): Long {
|
||||
val longValue = withContext(SdkDispatchers.DATABASE_IO) {
|
||||
getBalance(
|
||||
dataDbFile.absolutePath,
|
||||
account,
|
||||
networkId = networkId
|
||||
)
|
||||
}
|
||||
|
||||
return longValue
|
||||
}
|
||||
|
||||
override suspend fun getVerifiedBalance(account: Int): Long {
|
||||
val longValue = withContext(SdkDispatchers.DATABASE_IO) {
|
||||
getVerifiedBalance(
|
||||
dbDataPath = dataDbFile.absolutePath,
|
||||
account = account,
|
||||
networkId = networkId
|
||||
)
|
||||
}
|
||||
|
||||
return longValue
|
||||
}
|
||||
|
||||
override suspend fun getMemoAsUtf8(txId: ByteArray, outputIndex: Int) =
|
||||
withContext(SdkDispatchers.DATABASE_IO) {
|
||||
getMemoAsUtf8(
|
||||
|
@ -267,9 +243,9 @@ class RustBackend private constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getScanProgress(): JniScanProgress? =
|
||||
override suspend fun getWalletSummary(): JniWalletSummary? =
|
||||
withContext(SdkDispatchers.DATABASE_IO) {
|
||||
getScanProgress(
|
||||
getWalletSummary(
|
||||
dataDbFile.absolutePath,
|
||||
networkId = networkId
|
||||
)
|
||||
|
@ -462,16 +438,6 @@ class RustBackend private constructor(
|
|||
@JvmStatic
|
||||
private external fun isValidUnifiedAddress(addr: String, networkId: Int): Boolean
|
||||
|
||||
@JvmStatic
|
||||
private external fun getBalance(dbDataPath: String, account: Int, networkId: Int): Long
|
||||
|
||||
@JvmStatic
|
||||
private external fun getVerifiedBalance(
|
||||
dbDataPath: String,
|
||||
account: Int,
|
||||
networkId: Int
|
||||
): Long
|
||||
|
||||
@JvmStatic
|
||||
private external fun getMemoAsUtf8(
|
||||
dbDataPath: String,
|
||||
|
@ -543,10 +509,10 @@ class RustBackend private constructor(
|
|||
): Long
|
||||
|
||||
@JvmStatic
|
||||
private external fun getScanProgress(
|
||||
private external fun getWalletSummary(
|
||||
dbDataPath: String,
|
||||
networkId: Int
|
||||
): JniScanProgress?
|
||||
): JniWalletSummary?
|
||||
|
||||
@JvmStatic
|
||||
private external fun suggestScanRanges(
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package cash.z.ecc.android.sdk.internal.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* Serves as cross layer (Kotlin, Rust) communication class.
|
||||
*
|
||||
* @param account the account ID
|
||||
* @param saplingTotalBalance The total account balance in the Sapling pool, including unconfirmed funds.
|
||||
* @param saplingVerifiedBalance The verified account balance in the Sapling pool.
|
||||
* @throws IllegalArgumentException if the values are inconsistent.
|
||||
*/
|
||||
@Keep
|
||||
class JniAccountBalance(
|
||||
val account: Int,
|
||||
val saplingTotalBalance: Long,
|
||||
val saplingVerifiedBalance: Long,
|
||||
) {
|
||||
init {
|
||||
require(saplingTotalBalance >= saplingVerifiedBalance) {
|
||||
"Total Sapling balance $saplingTotalBalance must not be " +
|
||||
"less than verified Sapling balance $saplingVerifiedBalance."
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package cash.z.ecc.android.sdk.internal.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* Serves as cross layer (Kotlin, Rust) communication class.
|
||||
*
|
||||
* @throws IllegalArgumentException unless (numerator is nonnegative, denominator is
|
||||
* positive, and the represented ratio is in the range 0.0 to 1.0 inclusive).
|
||||
* @param numerator the numerator of the progress ratio
|
||||
* @param denominator the denominator of the progress ratio
|
||||
*/
|
||||
@Keep
|
||||
class JniScanProgress(
|
||||
val numerator: Long,
|
||||
val denominator: Long
|
||||
) {
|
||||
init {
|
||||
require(numerator >= 0L) {
|
||||
"Numerator $numerator is outside of allowed range [0, Long.MAX_VALUE]"
|
||||
}
|
||||
require(denominator >= 1L) {
|
||||
"Denominator $denominator is outside of allowed range [1, Long.MAX_VALUE]"
|
||||
}
|
||||
require(numerator.toFloat().div(denominator) >= 0f) {
|
||||
"Result of ${numerator.toFloat()}/$denominator is outside of allowed range"
|
||||
}
|
||||
require(numerator.toFloat().div(denominator) <= 1f) {
|
||||
"Result of ${numerator.toFloat()}/$denominator is outside of allowed range"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package cash.z.ecc.android.sdk.internal.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* Serves as cross layer (Kotlin, Rust) communication class.
|
||||
*
|
||||
* @param accountBalances the balances of the wallet accounts
|
||||
* @param progressNumerator the numerator of the progress ratio
|
||||
* @param progressDenominator the denominator of the progress ratio
|
||||
* @throws IllegalArgumentException unless (progressNumerator is nonnegative,
|
||||
* progressDenominator is positive, and the represented ratio is in the
|
||||
* range 0.0 to 1.0 inclusive).
|
||||
*/
|
||||
@Keep
|
||||
class JniWalletSummary(
|
||||
val accountBalances: Array<JniAccountBalance>,
|
||||
val progressNumerator: Long,
|
||||
val progressDenominator: Long
|
||||
) {
|
||||
init {
|
||||
require(progressNumerator >= 0L) {
|
||||
"Numerator $progressNumerator is outside of allowed range [0, Long.MAX_VALUE]"
|
||||
}
|
||||
require(progressDenominator >= 1L) {
|
||||
"Denominator $progressDenominator is outside of allowed range [1, Long.MAX_VALUE]"
|
||||
}
|
||||
require(progressNumerator.toFloat().div(progressDenominator) >= 0f) {
|
||||
"Result of ${progressNumerator.toFloat()}/$progressDenominator is outside of allowed range"
|
||||
}
|
||||
require(progressNumerator.toFloat().div(progressDenominator) <= 1f) {
|
||||
"Result of ${progressNumerator.toFloat()}/$progressDenominator is outside of allowed range"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ use tracing_subscriber::reload;
|
|||
use zcash_address::{ToAddress, ZcashAddress};
|
||||
use zcash_client_backend::data_api::{
|
||||
scanning::{ScanPriority, ScanRange},
|
||||
AccountBirthday, NoteId, Ratio, ShieldedProtocol,
|
||||
AccountBalance, AccountBirthday, NoteId, ShieldedProtocol, WalletSummary,
|
||||
};
|
||||
use zcash_client_backend::keys::{DecodingError, UnifiedSpendingKey};
|
||||
use zcash_client_backend::{
|
||||
|
@ -644,37 +644,6 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_is
|
|||
unwrap_exc_or(&env, res, JNI_FALSE)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getBalance(
|
||||
env: JNIEnv<'_>,
|
||||
_: JClass<'_>,
|
||||
db_data: JString<'_>,
|
||||
accountj: jint,
|
||||
network_id: jint,
|
||||
) -> jlong {
|
||||
let res = panic::catch_unwind(|| {
|
||||
let network = parse_network(network_id as u32)?;
|
||||
let db_data = wallet_db(&env, network, db_data)?;
|
||||
let account = AccountId::from(u32::try_from(accountj)?);
|
||||
|
||||
if let Some(wallet_summary) = db_data
|
||||
.get_wallet_summary(0)
|
||||
.map_err(|e| format_err!("Error while fetching balance: {}", e))?
|
||||
{
|
||||
wallet_summary
|
||||
.account_balances()
|
||||
.get(&account)
|
||||
.ok_or_else(|| format_err!("Unknown account"))
|
||||
.map(|balances| Amount::from(balances.sapling_balance.total()).into())
|
||||
} else {
|
||||
// `None` means that the caller has not yet called `updateChainTip` on a
|
||||
// brand-new wallet, so we can assume the balance is zero.
|
||||
Ok(0)
|
||||
}
|
||||
});
|
||||
unwrap_exc_or(&env, res, -1)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getVerifiedTransparentBalance(
|
||||
env: JNIEnv<'_>,
|
||||
|
@ -753,38 +722,6 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_ge
|
|||
unwrap_exc_or(&env, res, -1)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getVerifiedBalance(
|
||||
env: JNIEnv<'_>,
|
||||
_: JClass<'_>,
|
||||
db_data: JString<'_>,
|
||||
account: jint,
|
||||
network_id: jint,
|
||||
) -> jlong {
|
||||
let res = panic::catch_unwind(|| {
|
||||
let network = parse_network(network_id as u32)?;
|
||||
let db_data = wallet_db(&env, network, db_data)?;
|
||||
let account = AccountId::from(u32::try_from(account)?);
|
||||
|
||||
if let Some(wallet_summary) = db_data
|
||||
.get_wallet_summary(ANCHOR_OFFSET_U32)
|
||||
.map_err(|e| format_err!("Error while fetching verified balance: {}", e))?
|
||||
{
|
||||
wallet_summary
|
||||
.account_balances()
|
||||
.get(&account)
|
||||
.ok_or_else(|| format_err!("Unknown account"))
|
||||
.map(|balances| Amount::from(balances.sapling_balance.spendable_value).into())
|
||||
} else {
|
||||
// `None` means that the caller has not yet called `updateChainTip` on a
|
||||
// brand-new wallet, so we can assume the balance is zero.
|
||||
Ok(0)
|
||||
}
|
||||
});
|
||||
|
||||
unwrap_exc_or(&env, res, -1)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getMemoAsUtf8(
|
||||
env: JNIEnv<'_>,
|
||||
|
@ -1147,25 +1084,64 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_ge
|
|||
unwrap_exc_or(&env, res, -1)
|
||||
}
|
||||
|
||||
/// Returns a `JniScanProgress` object, provided that numerator is nonnegative, denominator
|
||||
/// is positive, and the represented ratio is in the range 0.0 to 1.0 inclusive.
|
||||
const JNI_ACCOUNT_BALANCE: &str = "cash/z/ecc/android/sdk/internal/model/JniAccountBalance";
|
||||
|
||||
fn encode_account_balance<'a>(
|
||||
env: &JNIEnv<'a>,
|
||||
account: &AccountId,
|
||||
balance: &AccountBalance,
|
||||
) -> jni::errors::Result<JObject<'a>> {
|
||||
let sapling_total_balance = Amount::from(balance.sapling_balance.total());
|
||||
let sapling_verified_balance = Amount::from(balance.sapling_balance.spendable_value);
|
||||
|
||||
env.new_object(
|
||||
JNI_ACCOUNT_BALANCE,
|
||||
"(IJJ)V",
|
||||
&[
|
||||
JValue::Int(u32::from(*account) as i32),
|
||||
JValue::Long(sapling_total_balance.into()),
|
||||
JValue::Long(sapling_verified_balance.into()),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a `JniWalletSummary` object, provided that `progress_numerator` is
|
||||
/// nonnegative, `progress_denominator` is positive, and the represented ratio is in the
|
||||
/// range 0.0 to 1.0 inclusive.
|
||||
///
|
||||
/// If these conditions are not met, this fails and leaves an `IllegalArgumentException`
|
||||
/// pending.
|
||||
fn encode_scan_progress(env: &JNIEnv<'_>, progress: Ratio<u64>) -> Result<jobject, failure::Error> {
|
||||
fn encode_wallet_summary(
|
||||
env: &JNIEnv<'_>,
|
||||
summary: WalletSummary,
|
||||
) -> Result<jobject, failure::Error> {
|
||||
let account_balances = utils::rust_vec_to_java(
|
||||
&env,
|
||||
summary.account_balances().into_iter().collect(),
|
||||
JNI_ACCOUNT_BALANCE,
|
||||
|env, (account, balance)| encode_account_balance(env, account, balance),
|
||||
|env| encode_account_balance(env, &AccountId::from(0), &AccountBalance::ZERO),
|
||||
);
|
||||
|
||||
let (progress_numerator, progress_denominator) = summary
|
||||
.scan_progress()
|
||||
.map(|progress| (*progress.numerator(), *progress.denominator()))
|
||||
.unwrap_or((0, 1));
|
||||
|
||||
let output = env.new_object(
|
||||
"cash/z/ecc/android/sdk/internal/model/JniScanProgress",
|
||||
"(JJ)V",
|
||||
"cash/z/ecc/android/sdk/internal/model/JniWalletSummary",
|
||||
&format!("([L{};JJ)V", JNI_ACCOUNT_BALANCE),
|
||||
&[
|
||||
JValue::Long(*progress.numerator() as i64),
|
||||
JValue::Long(*progress.denominator() as i64),
|
||||
JValue::Object(unsafe { JObject::from_raw(account_balances) }),
|
||||
JValue::Long(progress_numerator as i64),
|
||||
JValue::Long(progress_denominator as i64),
|
||||
],
|
||||
)?;
|
||||
Ok(output.into_raw())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getScanProgress(
|
||||
pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getWalletSummary(
|
||||
env: JNIEnv<'_>,
|
||||
_: JClass<'_>,
|
||||
db_data: JString<'_>,
|
||||
|
@ -1176,11 +1152,15 @@ pub unsafe extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_ge
|
|||
let db_data = wallet_db(&env, network, db_data)?;
|
||||
|
||||
match db_data
|
||||
.get_wallet_summary(0)
|
||||
.get_wallet_summary(ANCHOR_OFFSET_U32)
|
||||
.map_err(|e| format_err!("Error while fetching scan progress: {}", e))?
|
||||
.and_then(|summary| summary.scan_progress().filter(|r| r.denominator() > &0))
|
||||
{
|
||||
Some(progress) => encode_scan_progress(&env, progress),
|
||||
.filter(|summary| {
|
||||
summary
|
||||
.scan_progress()
|
||||
.map(|r| r.denominator() > &0)
|
||||
.unwrap_or(false)
|
||||
}) {
|
||||
Some(summary) => encode_wallet_summary(&env, summary),
|
||||
None => Ok(ptr::null_mut()),
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package cash.z.ecc.android.sdk.internal.fixture
|
||||
|
||||
import cash.z.ecc.android.sdk.internal.model.JniAccountBalance
|
||||
|
||||
object JniAccountBalanceFixture {
|
||||
|
||||
const val ACCOUNT_ID: Int = 0
|
||||
const val SAPLING_TOTAL_BALANCE: Long = 0L
|
||||
const val SAPLING_VERIFIED_BALANCE: Long = 0L
|
||||
|
||||
fun new(
|
||||
account: Int = ACCOUNT_ID,
|
||||
saplingTotalBalance: Long = SAPLING_TOTAL_BALANCE,
|
||||
saplingVerifiedBalance: Long = SAPLING_VERIFIED_BALANCE,
|
||||
|
||||
) = JniAccountBalance(
|
||||
account = account,
|
||||
saplingTotalBalance = saplingTotalBalance,
|
||||
saplingVerifiedBalance = saplingVerifiedBalance
|
||||
)
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package cash.z.ecc.android.sdk.internal.model
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertIs
|
||||
|
||||
class JniScanProgressTest {
|
||||
@Test
|
||||
fun both_attribute_within_constraints() {
|
||||
val instance = JniScanProgress(
|
||||
numerator = 1L,
|
||||
denominator = 100L
|
||||
)
|
||||
assertIs<JniScanProgress>(instance)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun numerator_attribute_not_in_constraints() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
JniScanProgress(
|
||||
numerator = -1L,
|
||||
denominator = 100L
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun denominator_attribute_not_in_constraints() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
JniScanProgress(
|
||||
numerator = 1L,
|
||||
denominator = 0L
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ratio_not_in_constraints() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
JniScanProgress(
|
||||
numerator = 100L,
|
||||
denominator = 1L
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package cash.z.ecc.android.sdk.internal.model
|
||||
|
||||
import cash.z.ecc.android.sdk.internal.fixture.JniAccountBalanceFixture
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertIs
|
||||
|
||||
class JniWalletSummaryTest {
|
||||
@Test
|
||||
fun both_attribute_within_constraints() {
|
||||
val instance = JniWalletSummary(
|
||||
accountBalances = arrayOf(JniAccountBalanceFixture.new()),
|
||||
progressNumerator = 1L,
|
||||
progressDenominator = 100L
|
||||
)
|
||||
assertIs<JniWalletSummary>(instance)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun numerator_attribute_not_in_constraints() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
JniWalletSummary(
|
||||
accountBalances = arrayOf(JniAccountBalanceFixture.new()),
|
||||
progressNumerator = -1L,
|
||||
progressDenominator = 100L
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun denominator_attribute_not_in_constraints() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
JniWalletSummary(
|
||||
accountBalances = arrayOf(JniAccountBalanceFixture.new()),
|
||||
progressNumerator = 1L,
|
||||
progressDenominator = 0L
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ratio_not_in_constraints() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
JniWalletSummary(
|
||||
accountBalances = arrayOf(JniAccountBalanceFixture.new()),
|
||||
progressNumerator = 100L,
|
||||
progressDenominator = 1L
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,10 +2,10 @@ 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.JniScanProgress
|
||||
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.JniUnifiedSpendingKey
|
||||
import cash.z.ecc.android.sdk.internal.model.JniWalletSummary
|
||||
|
||||
internal class FakeRustBackend(
|
||||
override val networkId: Int,
|
||||
|
@ -39,7 +39,7 @@ internal class FakeRustBackend(
|
|||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getScanProgress(): JniScanProgress {
|
||||
override suspend fun getWalletSummary(): JniWalletSummary {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
|
@ -129,10 +129,6 @@ internal class FakeRustBackend(
|
|||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getBalance(account: Int): Long {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getBranchIdForHeight(height: Long): Long {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
@ -140,10 +136,6 @@ internal class FakeRustBackend(
|
|||
override suspend fun getMemoAsUtf8(txId: ByteArray, outputIndex: Int): String? =
|
||||
error("Intentionally not implemented in mocked FakeRustBackend implementation.")
|
||||
|
||||
override suspend fun getVerifiedBalance(account: Int): Long {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getNearestRewindHeight(height: Long): Long {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import cash.z.ecc.android.sdk.BuildConfig
|
|||
import cash.z.ecc.android.sdk.annotation.OpenForTesting
|
||||
import cash.z.ecc.android.sdk.block.processor.model.BatchSyncProgress
|
||||
import cash.z.ecc.android.sdk.block.processor.model.GetMaxScannedHeightResult
|
||||
import cash.z.ecc.android.sdk.block.processor.model.GetScanProgressResult
|
||||
import cash.z.ecc.android.sdk.block.processor.model.GetSubtreeRootsResult
|
||||
import cash.z.ecc.android.sdk.block.processor.model.GetWalletSummaryResult
|
||||
import cash.z.ecc.android.sdk.block.processor.model.PutSaplingSubtreeRootsResult
|
||||
import cash.z.ecc.android.sdk.block.processor.model.SbSPreparationResult
|
||||
import cash.z.ecc.android.sdk.block.processor.model.SuggestScanRangesResult
|
||||
|
@ -41,6 +41,7 @@ import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
|
|||
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
|
||||
import cash.z.ecc.android.sdk.internal.model.WalletSummary
|
||||
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.repository.DerivedDataRepository
|
||||
|
@ -48,6 +49,7 @@ import cash.z.ecc.android.sdk.model.Account
|
|||
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||
import cash.z.ecc.android.sdk.model.PercentDecimal
|
||||
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 co.electriccoin.lightwallet.client.model.BlockHeightUnsafe
|
||||
import co.electriccoin.lightwallet.client.model.GetAddressUtxosReplyUnsafe
|
||||
|
@ -460,16 +462,16 @@ class CompactBlockProcessor internal constructor(
|
|||
withDownload = true,
|
||||
enhanceStartHeight = firstUnenhancedHeight
|
||||
).collect { batchSyncProgress ->
|
||||
// Update sync progress
|
||||
when (val result = getScanProgress(backend)) {
|
||||
is GetScanProgressResult.Success -> {
|
||||
val resultProgress = result.toPercentDecimal()
|
||||
// Update sync progress and wallet balance
|
||||
when (val result = getWalletSummary(backend)) {
|
||||
is GetWalletSummaryResult.Success -> {
|
||||
val resultProgress = result.scanProgressPercentDecimal()
|
||||
Twig.info { "Progress from rust: ${resultProgress.decimal}" }
|
||||
setProgress(resultProgress)
|
||||
updateAllBalances(result.walletSummary)
|
||||
}
|
||||
else -> { /* Do not report the progress in case of any error */ }
|
||||
else -> { /* Do not report the progress and balances in case of any error */ }
|
||||
}
|
||||
checkAllBalances()
|
||||
|
||||
when (batchSyncProgress.resultState) {
|
||||
SyncingResult.UpdateBirthday -> {
|
||||
|
@ -562,16 +564,16 @@ class CompactBlockProcessor internal constructor(
|
|||
withDownload = true,
|
||||
enhanceStartHeight = firstUnenhancedHeight
|
||||
).map { batchSyncProgress ->
|
||||
// Update sync progress
|
||||
when (val result = getScanProgress(backend)) {
|
||||
is GetScanProgressResult.Success -> {
|
||||
val resultProgress = result.toPercentDecimal()
|
||||
// Update sync progress and wallet balance
|
||||
when (val result = getWalletSummary(backend)) {
|
||||
is GetWalletSummaryResult.Success -> {
|
||||
val resultProgress = result.scanProgressPercentDecimal()
|
||||
Twig.info { "Progress from rust: ${resultProgress.decimal}" }
|
||||
setProgress(resultProgress)
|
||||
updateAllBalances(result.walletSummary)
|
||||
}
|
||||
else -> { /* Do not report the progress in case of any error */ }
|
||||
else -> { /* Do not report the progress and balances in case of any error */ }
|
||||
}
|
||||
checkAllBalances()
|
||||
|
||||
when (batchSyncProgress.resultState) {
|
||||
SyncingResult.UpdateBirthday -> {
|
||||
|
@ -757,6 +759,20 @@ class CompactBlockProcessor internal constructor(
|
|||
transparentBalances.value = getUtxoCacheBalance(getTransparentAddress(backend, Account.DEFAULT))
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the latest balances using the given wallet summary, and transmit this information
|
||||
* into the related internal flows. Note that the Orchard balance is not supported.
|
||||
*/
|
||||
internal suspend fun updateAllBalances(summary: WalletSummary) {
|
||||
summary.accountBalances[Account.DEFAULT]?.let {
|
||||
Twig.debug { "Updating Sapling balance" }
|
||||
saplingBalances.value = it.sapling
|
||||
// TODO [#682]: refresh orchard balance
|
||||
// TODO [#682]: https://github.com/zcash/zcash-android-wallet-sdk/issues/682
|
||||
}
|
||||
checkTransparentBalance()
|
||||
}
|
||||
|
||||
sealed class BlockProcessingResult {
|
||||
object NoBlocksToProcess : BlockProcessingResult()
|
||||
object Success : BlockProcessingResult()
|
||||
|
@ -1269,28 +1285,28 @@ class CompactBlockProcessor internal constructor(
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the current block scanning progress.
|
||||
* Get the current wallet summary.
|
||||
*
|
||||
* @return the last scanning progress calculated by the Rust layer and wrapped in [GetScanProgressResult]
|
||||
* @return the latest wallet summary calculated by the Rust layer and wrapped in [GetWalletSummaryResult]
|
||||
*/
|
||||
@VisibleForTesting
|
||||
internal suspend fun getScanProgress(backend: TypesafeBackend): GetScanProgressResult {
|
||||
internal suspend fun getWalletSummary(backend: TypesafeBackend): GetWalletSummaryResult {
|
||||
return runCatching {
|
||||
backend.getScanProgress()
|
||||
backend.getWalletSummary()
|
||||
}.onSuccess {
|
||||
Twig.verbose { "Successfully called getScanProgress with result: $it" }
|
||||
Twig.verbose { "Successfully called getWalletSummary with result: $it" }
|
||||
}.onFailure {
|
||||
Twig.error { "Failed to call getScanProgress with result: $it" }
|
||||
Twig.error { "Failed to call getWalletSummary with result: $it" }
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
if (it == null) {
|
||||
GetScanProgressResult.None
|
||||
GetWalletSummaryResult.None
|
||||
} else {
|
||||
GetScanProgressResult.Success(it)
|
||||
GetWalletSummaryResult.Success(it)
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
GetScanProgressResult.Failure(it)
|
||||
GetWalletSummaryResult.Failure(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -2049,11 +2065,17 @@ class CompactBlockProcessor internal constructor(
|
|||
*/
|
||||
suspend fun getBalanceInfo(account: Account): WalletBalance {
|
||||
return runCatching {
|
||||
val balanceTotal = backend.getBalance(account)
|
||||
Twig.info { "Found total balance: $balanceTotal" }
|
||||
val balanceAvailable = backend.getVerifiedBalance(account)
|
||||
Twig.info { "Found available balance: $balanceAvailable" }
|
||||
WalletBalance(balanceTotal, balanceAvailable)
|
||||
val walletSummary = backend.getWalletSummary()
|
||||
val accountBalance = walletSummary?.accountBalances?.get(account)
|
||||
// `None` means that the caller has not yet called `updateChainTip` on a
|
||||
// brand-new wallet, so we can assume the balance is zero.
|
||||
val saplingBalance = accountBalance?.sapling ?: WalletBalance(
|
||||
total = Zatoshi(0L),
|
||||
available = Zatoshi(0L)
|
||||
)
|
||||
Twig.info { "Found total balance: ${saplingBalance.total}" }
|
||||
Twig.info { "Found available balance: ${saplingBalance.available}" }
|
||||
saplingBalance
|
||||
}.onFailure {
|
||||
Twig.error(it) { "Failed to get balance due to ${it.localizedMessage}" }
|
||||
}.getOrElse {
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package cash.z.ecc.android.sdk.block.processor.model
|
||||
|
||||
import cash.z.ecc.android.sdk.internal.model.ScanProgress
|
||||
import cash.z.ecc.android.sdk.model.PercentDecimal
|
||||
|
||||
/**
|
||||
* Internal class for sharing get scan progress action result.
|
||||
*/
|
||||
internal sealed class GetScanProgressResult {
|
||||
data class Success(val scanProgress: ScanProgress) : GetScanProgressResult() {
|
||||
fun toPercentDecimal() = PercentDecimal(scanProgress.getSafeRatio())
|
||||
}
|
||||
|
||||
data object None : GetScanProgressResult()
|
||||
data class Failure(val exception: Throwable) : GetScanProgressResult()
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package cash.z.ecc.android.sdk.block.processor.model
|
||||
|
||||
import cash.z.ecc.android.sdk.internal.model.WalletSummary
|
||||
import cash.z.ecc.android.sdk.model.PercentDecimal
|
||||
|
||||
/**
|
||||
* Internal class for sharing wallet summary action result.
|
||||
*/
|
||||
internal sealed class GetWalletSummaryResult {
|
||||
data class Success(val walletSummary: WalletSummary) : GetWalletSummaryResult() {
|
||||
fun scanProgressPercentDecimal() = PercentDecimal(walletSummary.scanProgress.getSafeRatio())
|
||||
}
|
||||
|
||||
data object None : GetWalletSummaryResult()
|
||||
data class Failure(val exception: Throwable) : GetWalletSummaryResult()
|
||||
}
|
|
@ -1,16 +1,15 @@
|
|||
package cash.z.ecc.android.sdk.internal
|
||||
|
||||
import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
|
||||
import cash.z.ecc.android.sdk.internal.model.ScanProgress
|
||||
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.TreeState
|
||||
import cash.z.ecc.android.sdk.internal.model.WalletSummary
|
||||
import cash.z.ecc.android.sdk.model.Account
|
||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
||||
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
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
|
@ -41,12 +40,8 @@ internal interface TypesafeBackend {
|
|||
|
||||
suspend fun listTransparentReceivers(account: Account): List<String>
|
||||
|
||||
suspend fun getBalance(account: Account): Zatoshi
|
||||
|
||||
fun getBranchIdForHeight(height: BlockHeight): Long
|
||||
|
||||
suspend fun getVerifiedBalance(account: Account): Zatoshi
|
||||
|
||||
suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight
|
||||
|
||||
suspend fun rewindToHeight(height: BlockHeight)
|
||||
|
@ -121,7 +116,7 @@ internal interface TypesafeBackend {
|
|||
* @throws RuntimeException as a common indicator of the operation failure
|
||||
*/
|
||||
@Throws(RuntimeException::class)
|
||||
suspend fun getScanProgress(): ScanProgress?
|
||||
suspend fun getWalletSummary(): WalletSummary?
|
||||
|
||||
/**
|
||||
* @throws RuntimeException as a common indicator of the operation failure
|
||||
|
|
|
@ -2,10 +2,10 @@ package cash.z.ecc.android.sdk.internal
|
|||
|
||||
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.ScanProgress
|
||||
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.TreeState
|
||||
import cash.z.ecc.android.sdk.internal.model.WalletSummary
|
||||
import cash.z.ecc.android.sdk.model.Account
|
||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
||||
|
@ -70,18 +70,10 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke
|
|||
return backend.listTransparentReceivers(account.value)
|
||||
}
|
||||
|
||||
override suspend fun getBalance(account: Account): Zatoshi {
|
||||
return Zatoshi(backend.getBalance(account.value))
|
||||
}
|
||||
|
||||
override fun getBranchIdForHeight(height: BlockHeight): Long {
|
||||
return backend.getBranchIdForHeight(height.value)
|
||||
}
|
||||
|
||||
override suspend fun getVerifiedBalance(account: Account): Zatoshi {
|
||||
return Zatoshi(backend.getVerifiedBalance(account.value))
|
||||
}
|
||||
|
||||
override suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight {
|
||||
return BlockHeight.new(
|
||||
ZcashNetwork.from(backend.networkId),
|
||||
|
@ -182,8 +174,8 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke
|
|||
|
||||
override suspend fun scanBlocks(fromHeight: BlockHeight, limit: Long) = backend.scanBlocks(fromHeight.value, limit)
|
||||
|
||||
override suspend fun getScanProgress(): ScanProgress? = backend.getScanProgress()?.let { jniScanProgress ->
|
||||
ScanProgress.new(jniScanProgress)
|
||||
override suspend fun getWalletSummary(): WalletSummary? = backend.getWalletSummary()?.let { jniWalletSummary ->
|
||||
WalletSummary.new(jniWalletSummary)
|
||||
}
|
||||
|
||||
override suspend fun suggestScanRanges(): List<ScanRange> = backend.suggestScanRanges().map { jniScanRange ->
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package cash.z.ecc.android.sdk.internal.model
|
||||
|
||||
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
|
||||
internal data class AccountBalance(
|
||||
val sapling: WalletBalance
|
||||
) {
|
||||
companion object {
|
||||
fun new(jni: JniAccountBalance): AccountBalance {
|
||||
return AccountBalance(
|
||||
sapling = WalletBalance(
|
||||
Zatoshi(jni.saplingTotalBalance),
|
||||
Zatoshi(jni.saplingVerifiedBalance)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,19 +9,19 @@ internal data class ScanProgress(
|
|||
/**
|
||||
* Returns progress ratio in [0, 1] range. Any out-of-range value is treated as 0.
|
||||
*/
|
||||
fun getSafeRatio() = numerator.toFloat().div(denominator).let { ration ->
|
||||
if (ration < 0f || ration > 1f) {
|
||||
fun getSafeRatio() = numerator.toFloat().div(denominator).let { ratio ->
|
||||
if (ratio < 0f || ratio > 1f) {
|
||||
0f
|
||||
} else {
|
||||
ration
|
||||
ratio
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun new(jni: JniScanProgress): ScanProgress {
|
||||
fun new(jni: JniWalletSummary): ScanProgress {
|
||||
return ScanProgress(
|
||||
numerator = jni.numerator,
|
||||
denominator = jni.denominator
|
||||
numerator = jni.progressNumerator,
|
||||
denominator = jni.progressDenominator
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package cash.z.ecc.android.sdk.internal.model
|
||||
|
||||
import cash.z.ecc.android.sdk.model.Account
|
||||
|
||||
internal data class WalletSummary(
|
||||
val accountBalances: Map<Account, AccountBalance>,
|
||||
val scanProgress: ScanProgress
|
||||
) {
|
||||
companion object {
|
||||
fun new(jni: JniWalletSummary): WalletSummary {
|
||||
return WalletSummary(
|
||||
accountBalances = jni.accountBalances.associateBy({ Account(it.account) }, {
|
||||
AccountBalance.new(it)
|
||||
}),
|
||||
scanProgress = ScanProgress.new(jni)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue