From 04f1f479572b6c74d8490fa168de31513387cc6c Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 19 Feb 2024 20:45:49 +0000 Subject: [PATCH 01/12] Fix bug where mempool submission errors were being ignored --- CHANGELOG.md | 5 +++++ .../transaction/OutboundTransactionManagerImpl.kt | 15 ++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36a126b3..54677860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- `Synchronizer.sendToAddress` and `Synchronizer.shieldFunds` now throw an + exception if the created transaction successfully reaches `lightwalletd` but + fails to reach its backing full node's mempool. + ### Changed - `WalletBalance` now contains new fields `changePending` and `valuePending`. Fields `total` and `pending` are still provided. See more in the class documentation diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt index 0eadef75..cbe5dd3f 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt @@ -1,6 +1,7 @@ package cash.z.ecc.android.sdk.internal.transaction import cash.z.ecc.android.sdk.internal.Twig +import cash.z.ecc.android.sdk.internal.ext.toHexReversed import cash.z.ecc.android.sdk.internal.model.EncodedTransaction import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.TransactionRecipient @@ -43,13 +44,21 @@ internal class OutboundTransactionManagerImpl( override suspend fun submit(encodedTransaction: EncodedTransaction): Boolean { return when (val response = service.submitTransaction(encodedTransaction.raw.byteArray)) { is Response.Success -> { - Twig.debug { "SUCCESS: submit transaction completed with response: ${response.result}" } - true + if (response.result.code == 0) { + Twig.debug { "SUCCESS: submit transaction completed" } + true + } else { + Twig.debug { + "FAILURE! submit transaction ${encodedTransaction.txId.byteArray.toHexReversed()} " + + "completed with response: ${response.result.code}: ${response.result.message}" + } + false + } } is Response.Failure -> { Twig.debug { - "FAILURE! submit transaction completed with response: ${response.code}: ${ + "FAILURE! submit transaction failed with gRPC response: ${response.code}: ${ response.description }" } From a27fbda8c05f66470285ccb031173f691f680d39 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 19 Feb 2024 22:09:01 +0000 Subject: [PATCH 02/12] Expose APIs for working with transaction proposals Closes Electric-Coin-Company/zcash-android-wallet-sdk#1359. --- CHANGELOG.md | 7 ++ .../cash/z/ecc/android/sdk/SdkSynchronizer.kt | 65 ++++++++++++-- .../cash/z/ecc/android/sdk/Synchronizer.kt | 46 ++++++++++ .../android/sdk/internal/TypesafeBackend.kt | 4 +- .../sdk/internal/TypesafeBackendImpl.kt | 8 +- .../transaction/OutboundTransactionManager.kt | 46 +++++++++- .../OutboundTransactionManagerImpl.kt | 37 +++++++- .../transaction/TransactionEncoder.kt | 43 ++++++++++ .../transaction/TransactionEncoderImpl.kt | 84 +++++++++++++++++-- .../sdk/model/TransactionSubmitResult.kt | 32 +++++++ 10 files changed, 347 insertions(+), 25 deletions(-) create mode 100644 sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/TransactionSubmitResult.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 54677860..ab16a1d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,13 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Memo.MAX_MEMO_LENGTH_BYTES` is now available in public API ### Added +- APIs that enable constructing a proposal for transferring or shielding funds, + and then creating transactions from a proposal. The intermediate proposal can + be used to determine the required fee, before committing to producing + transactions. + - `Synchronizer.proposeTransfer` + - `Synchronizer.proposeShielding` + - `Synchronizer.createProposedTransactions` - `WalletBalanceFixture` class with mock values that are supposed to be used only for testing purposes - `Memo.countLength(memoString: String)` to count memo length in bytes - `PersistableWallet.toSafeString` is a safe alternative for the regular [toString] function that prints only 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 8670dc8c..bd74e975 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 @@ -42,8 +42,10 @@ import cash.z.ecc.android.sdk.internal.transaction.TransactionEncoderImpl 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.Proposal import cash.z.ecc.android.sdk.model.TransactionOverview import cash.z.ecc.android.sdk.model.TransactionRecipient +import cash.z.ecc.android.sdk.model.TransactionSubmitResult import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.Zatoshi import cash.z.ecc.android.sdk.model.ZcashNetwork @@ -68,6 +70,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -555,6 +558,46 @@ class SdkSynchronizer private constructor( account ) + @Throws(TransactionEncoderException::class) + override suspend fun proposeTransfer( + account: Account, + recipient: String, + amount: Zatoshi, + memo: String + ): Proposal = txManager.proposeTransfer(account, recipient, amount, memo) + + @Throws(TransactionEncoderException::class) + override suspend fun proposeShielding( + account: Account, + memo: String + ): Proposal = txManager.proposeShielding(account, memo) + + @Throws(TransactionEncoderException::class) + override suspend fun createProposedTransactions( + proposal: Proposal, + usk: UnifiedSpendingKey + ): Flow { + val transactions = txManager.createProposedTransactions(proposal, usk) + + return flow { + var submitFailed = false + for (transaction in transactions) { + if (submitFailed) { + emit(TransactionSubmitResult.NotAttempted(transaction.txId)) + } else { + val submitResult = txManager.submit(transaction) + when (submitResult) { + is TransactionSubmitResult.Success -> {} + else -> { + submitFailed = true + } + } + emit(submitResult) + } + } + } + } + @Throws(TransactionEncoderException::class, TransactionSubmitException::class) override suspend fun sendToAddress( usk: UnifiedSpendingKey, @@ -571,10 +614,13 @@ class SdkSynchronizer private constructor( usk.account ) - if (txManager.submit(encodedTx)) { - return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!! - } else { - throw TransactionSubmitException() + when (txManager.submit(encodedTx)) { + is TransactionSubmitResult.Success -> { + return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!! + } + else -> { + throw TransactionSubmitException() + } } } @@ -596,10 +642,13 @@ class SdkSynchronizer private constructor( usk.account ) - if (txManager.submit(encodedTx)) { - return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!! - } else { - throw TransactionSubmitException() + when (txManager.submit(encodedTx)) { + is TransactionSubmitResult.Success -> { + return storage.findMatchingTransactionId(encodedTx.txId.byteArray)!! + } + else -> { + throw TransactionSubmitException() + } } } 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 71018141..27b9d3d5 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 @@ -15,8 +15,10 @@ import cash.z.ecc.android.sdk.internal.model.ext.toBlockHeight 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.Proposal import cash.z.ecc.android.sdk.model.TransactionOverview import cash.z.ecc.android.sdk.model.TransactionRecipient +import cash.z.ecc.android.sdk.model.TransactionSubmitResult import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi @@ -167,6 +169,50 @@ interface Synchronizer { */ suspend fun getTransparentAddress(account: Account): String + /** + * Creates a proposal for transferring funds to the given recipient. + * + * @param account the account from which to transfer funds. + * @param recipient the recipient's address. + * @param amount the amount of zatoshi to send. + * @param memo the optional memo to include as part of the proposal's transactions. + * + * @return the proposal or an exception + */ + suspend fun proposeTransfer( + account: Account, + recipient: String, + amount: Zatoshi, + memo: String = "" + ): Proposal + + /** + * Creates a proposal for shielding any transparent funds received by the given account. + * + * @param account the account for which to shield funds. + * @param memo the optional memo to include as part of the proposal's transactions. + */ + suspend fun proposeShielding( + account: Account, + memo: String = ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX + ): Proposal + + /** + * Creates the transactions in the given proposal. + * + * @param proposal the proposal for which to create transactions. + * @param usk the unified spending key associated with the account for which the + * proposal was created. + * + * @return a flow of result objects for the transactions that were created as part of + * the proposal, indicating whether they were submitted to the network or if + * an error occurred. + */ + suspend fun createProposedTransactions( + proposal: Proposal, + usk: UnifiedSpendingKey + ): Flow + /** * Sends zatoshi. * 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 3c6a7fdd..fa36afa4 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 @@ -26,14 +26,14 @@ internal interface TypesafeBackend { @Suppress("LongParameterList") suspend fun proposeTransfer( - usk: UnifiedSpendingKey, + account: Account, to: String, value: Long, memo: ByteArray? = byteArrayOf() ): Proposal suspend fun proposeShielding( - usk: UnifiedSpendingKey, + account: Account, memo: ByteArray? = byteArrayOf() ): Proposal 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 1dbad3d4..d519e677 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 @@ -37,14 +37,14 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke @Suppress("LongParameterList") override suspend fun proposeTransfer( - usk: UnifiedSpendingKey, + account: Account, to: String, value: Long, memo: ByteArray? ): Proposal = Proposal.fromUnsafe( backend.proposeTransfer( - usk.account.value, + account.value, to, value, memo @@ -52,12 +52,12 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke ) override suspend fun proposeShielding( - usk: UnifiedSpendingKey, + account: Account, memo: ByteArray? ): Proposal = Proposal.fromUnsafe( backend.proposeShielding( - usk.account.value, + account.value, memo ) ) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt index 4671d457..7552c730 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt @@ -2,7 +2,9 @@ package cash.z.ecc.android.sdk.internal.transaction import cash.z.ecc.android.sdk.internal.model.EncodedTransaction import cash.z.ecc.android.sdk.model.Account +import cash.z.ecc.android.sdk.model.Proposal import cash.z.ecc.android.sdk.model.TransactionRecipient +import cash.z.ecc.android.sdk.model.TransactionSubmitResult import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.Zatoshi @@ -33,6 +35,48 @@ internal interface OutboundTransactionManager { account: Account ): EncodedTransaction + /** + * Creates a proposal for transferring funds to the given recipient. + * + * @param account the account from which to transfer funds. + * @param recipient the recipient's address. + * @param amount the amount of zatoshi to send. + * @param memo the optional memo to include as part of the proposal's transactions. + * + * @return the proposal or an exception + */ + suspend fun proposeTransfer( + account: Account, + recipient: String, + amount: Zatoshi, + memo: String + ): Proposal + + /** + * Creates a proposal for shielding any transparent funds received by the given account. + * + * @param account the account for which to shield funds. + * @param memo the optional memo to include as part of the proposal's transactions. + */ + suspend fun proposeShielding( + account: Account, + memo: String + ): Proposal + + /** + * Creates the transactions in the given proposal. + * + * @param proposal the proposal for which to create transactions. + * @param usk the unified spending key associated with the account for which the + * proposal was created. + * + * @return the successfully encoded transactions or an exception + */ + suspend fun createProposedTransactions( + proposal: Proposal, + usk: UnifiedSpendingKey + ): List + /** * Submits the transaction represented by [encodedTransaction] to lightwalletd to broadcast to the * network and, hopefully, include in the next block. @@ -41,7 +85,7 @@ internal interface OutboundTransactionManager { * to lightwalletd. * @return true if the transaction was successfully submitted to lightwalletd. */ - suspend fun submit(encodedTransaction: EncodedTransaction): Boolean + suspend fun submit(encodedTransaction: EncodedTransaction): TransactionSubmitResult /** * Return true when the given address is a valid t-addr. diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt index cbe5dd3f..c0c4adbd 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt @@ -4,7 +4,9 @@ import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.ext.toHexReversed import cash.z.ecc.android.sdk.internal.model.EncodedTransaction import cash.z.ecc.android.sdk.model.Account +import cash.z.ecc.android.sdk.model.Proposal import cash.z.ecc.android.sdk.model.TransactionRecipient +import cash.z.ecc.android.sdk.model.TransactionSubmitResult import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.Zatoshi import co.electriccoin.lightwallet.client.LightWalletClient @@ -41,18 +43,40 @@ internal class OutboundTransactionManagerImpl( } } - override suspend fun submit(encodedTransaction: EncodedTransaction): Boolean { + override suspend fun proposeTransfer( + account: Account, + recipient: String, + amount: Zatoshi, + memo: String + ): Proposal = encoder.proposeTransfer(account, recipient, amount, memo.toByteArray()) + + override suspend fun proposeShielding( + account: Account, + memo: String + ): Proposal = encoder.proposeShielding(account, memo.toByteArray()) + + override suspend fun createProposedTransactions( + proposal: Proposal, + usk: UnifiedSpendingKey + ): List = encoder.createProposedTransactions(proposal, usk) + + override suspend fun submit(encodedTransaction: EncodedTransaction): TransactionSubmitResult { return when (val response = service.submitTransaction(encodedTransaction.raw.byteArray)) { is Response.Success -> { if (response.result.code == 0) { Twig.debug { "SUCCESS: submit transaction completed" } - true + TransactionSubmitResult.Success(encodedTransaction.txId) } else { Twig.debug { "FAILURE! submit transaction ${encodedTransaction.txId.byteArray.toHexReversed()} " + "completed with response: ${response.result.code}: ${response.result.message}" } - false + TransactionSubmitResult.Failure( + encodedTransaction.txId, + false, + response.result.code, + response.result.message + ) } } @@ -62,7 +86,12 @@ internal class OutboundTransactionManagerImpl( response.description }" } - false + TransactionSubmitResult.Failure( + encodedTransaction.txId, + true, + response.code, + response.description + ) } } } diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoder.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoder.kt index 4a5383e9..437781e4 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoder.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoder.kt @@ -1,7 +1,9 @@ package cash.z.ecc.android.sdk.internal.transaction import cash.z.ecc.android.sdk.internal.model.EncodedTransaction +import cash.z.ecc.android.sdk.model.Account import cash.z.ecc.android.sdk.model.BlockHeight +import cash.z.ecc.android.sdk.model.Proposal import cash.z.ecc.android.sdk.model.TransactionRecipient import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.Zatoshi @@ -38,6 +40,47 @@ internal interface TransactionEncoder { memo: ByteArray? = byteArrayOf() ): EncodedTransaction + /** + * Creates a proposal for transferring funds to the given recipient. + * + * @param account the account from which to transfer funds. + * @param recipient the recipient's address. + * @param amount the amount of zatoshi to send. + * @param memo the optional memo to include as part of the proposal's transactions. + * + * @return the proposal or an exception + */ + suspend fun proposeTransfer( + account: Account, + recipient: String, + amount: Zatoshi, + memo: ByteArray? = byteArrayOf() + ): Proposal + + /** + * Creates a proposal for shielding any transparent funds sent to the given account. + * + * @param account the account for which to shield funds. + * @param memo the optional memo to include as part of the proposal's transactions. + */ + suspend fun proposeShielding( + account: Account, + memo: ByteArray? = byteArrayOf() + ): Proposal + + /** + * Creates the transactions in the given proposal. + * + * @param proposal the proposal to create. + * @param usk the unified spending key associated with the notes that will be spent. + * + * @return the successfully encoded transactions or an exception + */ + suspend fun createProposedTransactions( + proposal: Proposal, + usk: UnifiedSpendingKey + ): List + /** * Utility function to help with validation. This is not called during [createTransaction] * because this class asserts that all validation is done externally by the UI, for now. diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt index 652112ed..34e82718 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt @@ -7,8 +7,10 @@ import cash.z.ecc.android.sdk.internal.Twig import cash.z.ecc.android.sdk.internal.TypesafeBackend import cash.z.ecc.android.sdk.internal.model.EncodedTransaction import cash.z.ecc.android.sdk.internal.repository.DerivedDataRepository +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.Proposal import cash.z.ecc.android.sdk.model.TransactionRecipient import cash.z.ecc.android.sdk.model.UnifiedSpendingKey import cash.z.ecc.android.sdk.model.Zatoshi @@ -22,6 +24,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi * @property repository the repository that stores information about the transactions being created * such as the raw bytes and raw txId. */ +@Suppress("TooManyFunctions") internal class TransactionEncoderImpl( private val backend: TypesafeBackend, private val saplingParamTool: SaplingParamTool, @@ -64,6 +67,79 @@ internal class TransactionEncoderImpl( ?: throw TransactionEncoderException.TransactionNotFoundException(transactionId) } + override suspend fun proposeTransfer( + account: Account, + recipient: String, + amount: Zatoshi, + memo: ByteArray? + ): Proposal { + Twig.debug { + "creating proposal to spend $amount zatoshi to" + + " ${recipient.masked()} with memo: ${memo?.decodeToString()}" + } + + @Suppress("TooGenericExceptionCaught") + return try { + backend.proposeTransfer( + account, + recipient, + amount.value, + memo + ) + } catch (t: Throwable) { + Twig.debug(t) { "Caught exception while creating proposal." } + throw t + }.also { result -> + Twig.debug { "result of proposeTransfer: $result" } + } + } + + override suspend fun proposeShielding( + account: Account, + memo: ByteArray? + ): Proposal { + @Suppress("TooGenericExceptionCaught") + return try { + backend.proposeShielding(account, memo) + } catch (t: Throwable) { + // TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee) + // then consider custom error that says no UTXOs existed to shield + // TODO [#680]: https://github.com/zcash/zcash-android-wallet-sdk/issues/680 + Twig.debug(t) { "proposeShielding failed" } + throw t + }.also { result -> + Twig.debug { "result of proposeShielding: $result" } + } + } + + override suspend fun createProposedTransactions( + proposal: Proposal, + usk: UnifiedSpendingKey + ): List { + Twig.debug { + "creating transactions for proposal" + } + + @Suppress("TooGenericExceptionCaught") + val transactionId = + try { + saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory) + Twig.debug { "params exist! attempting to send..." } + backend.createProposedTransaction(proposal, usk) + } catch (t: Throwable) { + Twig.debug(t) { "Caught exception while creating transaction." } + throw t + }.also { result -> + Twig.debug { "result of createProposedTransactions: $result" } + } + + val tx = + repository.findEncodedTransactionByTxId(transactionId) + ?: throw TransactionEncoderException.TransactionNotFoundException(transactionId) + + return listOf(tx) + } + /** * Utility function to help with validation. This is not called during [createTransaction] * because this class asserts that all validation is done externally by the UI, for now. @@ -137,11 +213,9 @@ internal class TransactionEncoderImpl( return try { saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory) Twig.debug { "params exist! attempting to send..." } - // TODO [#1359]: Expose the proposal in a way that enables querying its fee. - // TODO [#1359]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/1359 val proposal = backend.proposeTransfer( - usk, + usk.account, toAddress, amount.value, memo @@ -163,9 +237,7 @@ internal class TransactionEncoderImpl( return try { saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory) Twig.debug { "params exist! attempting to shield..." } - // TODO [#1359]: Expose the proposal in a way that enables querying its fee. - // TODO [#1359]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/1359 - val proposal = backend.proposeShielding(usk, memo) + val proposal = backend.proposeShielding(usk.account, memo) backend.createProposedTransaction(proposal, usk) } catch (t: Throwable) { // TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/TransactionSubmitResult.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/TransactionSubmitResult.kt new file mode 100644 index 00000000..231ba48d --- /dev/null +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/TransactionSubmitResult.kt @@ -0,0 +1,32 @@ +package cash.z.ecc.android.sdk.model + +/** + * A result object for a transaction that was created as part of a proposal, indicating + * whether it was submitted to the network or if an error occurred. + */ +sealed class TransactionSubmitResult { + /** + * The transaction was successfully submitted to the mempool. + */ + data class Success(val txId: FirstClassByteArray) : TransactionSubmitResult() + + /** + * An error occurred while submitting the transaction. + * + * If `grpcError` is true, the transaction failed to reach the `lightwalletd` server. + * Otherwise, the transaction reached the `lightwalletd` server but failed to enter + * the mempool. + */ + data class Failure( + val txId: FirstClassByteArray, + val grpcError: Boolean, + val code: Int, + val description: String? + ) : TransactionSubmitResult() + + /** + * The transaction was created and is in the local wallet, but was not submitted to + * the network. + */ + data class NotAttempted(val txId: FirstClassByteArray) : TransactionSubmitResult() +} From d2fa4005316141f92d55d8e581ffc4cee67409f4 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 19 Feb 2024 22:43:49 +0000 Subject: [PATCH 03/12] Deprecate `Synchronizer.sendToAddress` and `Synchronizer.shieldFunds` --- CHANGELOG.md | 5 ++++- .../ecc/android/sdk/darkside/test/TestWallet.kt | 15 +++++++++++++-- .../wallet/sdk/sample/demoapp/SampleCodeTest.kt | 10 +++++++++- .../demos/getbalance/GetBalanceFragment.kt | 9 +++++++-- .../sdk/demoapp/demos/send/SendFragment.kt | 17 +++++++++++------ .../ui/screen/home/viewmodel/WalletViewModel.kt | 15 +++++++++++---- .../cash/z/ecc/android/sdk/model/ZecSend.kt | 13 ++++++++----- .../sdk/integration/TestnetIntegrationTest.kt | 13 ++++++++----- .../cash/z/ecc/android/sdk/util/TestWallet.kt | 12 ++++++++++-- .../cash/z/ecc/android/sdk/SdkSynchronizer.kt | 14 ++++++++++++++ .../java/cash/z/ecc/android/sdk/Synchronizer.kt | 14 ++++++++++++++ 11 files changed, 109 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab16a1d8..3faa983f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,11 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 still provided. See more in the class documentation `sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/WalletBalance.kt` - `Synchronizer.transparentBalances: WalletBalance` to `Synchronizer.transparentBalance: Zatoshi` -- `WalletSnapshot.transparentBalance: WalletBalance` to `WalletSnapshot.transparentBalance: Zatoshi` +- `WalletSnapshot.transparentBalance: WalletBalance` to `WalletSnapshot.transparentBalance: Zatoshi` - `Memo.MAX_MEMO_LENGTH_BYTES` is now available in public API +- `Synchronizer.sendToAddress` and `Synchronizer.shieldFunds` have been + deprecated, and will be removed in 2.1.0 (which will create multiple + transactions at once for some recipients). ### Added - APIs that enable constructing a proposal for transferring or shielding funds, diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt index d51fbf62..73ea19dd 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt @@ -105,7 +105,15 @@ class TestWallet( memo: String = "", amount: Zatoshi = Zatoshi(500L) ): TestWallet { - synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo) + synchronizer.createProposedTransactions( + synchronizer.proposeTransfer( + shieldedSpendingKey.account, + address, + amount, + memo + ), + shieldedSpendingKey + ) return this } @@ -121,7 +129,10 @@ class TestWallet( synchronizer.getTransparentBalance(transparentAddress).let { walletBalance -> if (walletBalance.value > 0L) { - synchronizer.shieldFunds(shieldedSpendingKey) + synchronizer.createProposedTransactions( + synchronizer.proposeShielding(shieldedSpendingKey.account), + shieldedSpendingKey + ) } } diff --git a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt index 3b68a48a..c5562420 100644 --- a/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt +++ b/demo-app/src/androidTest/java/cash/z/wallet/sdk/sample/demoapp/SampleCodeTest.kt @@ -189,7 +189,15 @@ class SampleCodeTest { ZcashNetwork.Mainnet, Account.DEFAULT ) - synchronizer.sendToAddress(spendingKey, amount, address, memo) + synchronizer.createProposedTransactions( + synchronizer.proposeTransfer( + spendingKey.account, + address, + amount, + memo + ), + spendingKey + ) } // ///////////////////////////////////////////////////// diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt index 0a7e7453..f5ce17fe 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt @@ -60,13 +60,18 @@ class GetBalanceFragment : BaseDemoFragment() { binding.shield.apply { setOnClickListener { lifecycleScope.launch { - sharedViewModel.synchronizerFlow.value?.shieldFunds( + val usk = DerivationTool.getInstance().deriveUnifiedSpendingKey( seed, network, Account.DEFAULT ) - ) + sharedViewModel.synchronizerFlow.value?.let { synchronizer -> + synchronizer.createProposedTransactions( + synchronizer.proposeShielding(usk.account), + usk + ) + } } } } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt index a9d8bc8b..170cff3c 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/send/SendFragment.kt @@ -145,12 +145,17 @@ class SendFragment : BaseDemoFragment() { val amount = amountInput.text.toString().toDouble().convertZecToZatoshi() val toAddress = addressInput.text.toString().trim() lifecycleScope.launch { - sharedViewModel.synchronizerFlow.value?.sendToAddress( - spendingKey, - amount, - toAddress, - "Funds from Demo App" - ) + sharedViewModel.synchronizerFlow.value?.let { synchronizer -> + synchronizer.createProposedTransactions( + synchronizer.proposeTransfer( + spendingKey.account, + toAddress, + amount, + "Funds from Demo App" + ), + spendingKey + ) + } } mainActivity()?.hideKeyboard() 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 94d23bb0..91d7f6fc 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 @@ -22,6 +22,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.PersistableWallet +import cash.z.ecc.android.sdk.model.TransactionSubmitResult import cash.z.ecc.android.sdk.model.WalletAddresses import cash.z.ecc.android.sdk.model.WalletBalance import cash.z.ecc.android.sdk.model.Zatoshi @@ -47,6 +48,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -202,7 +204,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) viewModelScope.launch { val spendingKey = spendingKey.filterNotNull().first() runCatching { synchronizer.send(spendingKey, zecSend) } - .onSuccess { mutableSendState.value = SendState.Sent(it) } + .onSuccess { mutableSendState.value = SendState.Sent(it.toList()) } .onFailure { mutableSendState.value = SendState.Error(it) } } } else { @@ -226,8 +228,13 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) if (null != synchronizer) { viewModelScope.launch { val spendingKey = spendingKey.filterNotNull().first() - kotlin.runCatching { synchronizer.shieldFunds(spendingKey) } - .onSuccess { mutableSendState.value = SendState.Sent(it) } + kotlin.runCatching { + synchronizer.createProposedTransactions( + synchronizer.proposeShielding(spendingKey.account), + spendingKey + ) + } + .onSuccess { mutableSendState.value = SendState.Sent(it.toList()) } .onFailure { mutableSendState.value = SendState.Error(it) } } } else { @@ -302,7 +309,7 @@ sealed class SendState { override fun toString(): String = "Sending" } - class Sent(val localTxId: Long) : SendState() { + class Sent(val txIds: List) : SendState() { override fun toString(): String = "Sent" } diff --git a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSend.kt b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSend.kt index dcf61e6c..2acb6767 100644 --- a/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSend.kt +++ b/sdk-incubator-lib/src/main/java/cash/z/ecc/android/sdk/model/ZecSend.kt @@ -9,9 +9,12 @@ data class ZecSend(val destination: WalletAddress, val amount: Zatoshi, val memo suspend fun Synchronizer.send( spendingKey: UnifiedSpendingKey, send: ZecSend -) = sendToAddress( - spendingKey, - send.amount, - send.destination.address, - send.memo.value +) = createProposedTransactions( + proposeTransfer( + spendingKey.account, + send.destination.address, + send.amount, + send.memo.value + ), + spendingKey ) diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt index 76fe8b05..98638f66 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/integration/TestnetIntegrationTest.kt @@ -112,11 +112,14 @@ class TestnetIntegrationTest : ScopedTest() { Account.DEFAULT ) log("sending to address") - synchronizer.sendToAddress( - spendingKey, - ZcashSdk.MINERS_FEE, - toAddress, - "first mainnet tx from the SDK" + synchronizer.createProposedTransactions( + synchronizer.proposeTransfer( + spendingKey.account, + toAddress, + ZcashSdk.MINERS_FEE, + "first mainnet tx from the SDK" + ), + spendingKey ) return true } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt index c7c866f0..f6d57a67 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt @@ -106,7 +106,15 @@ class TestWallet( memo: String = "", amount: Zatoshi = Zatoshi(500L) ): TestWallet { - synchronizer.sendToAddress(spendingKey, amount, address, memo) + synchronizer.createProposedTransactions( + synchronizer.proposeTransfer( + spendingKey.account, + address, + amount, + memo + ), + spendingKey + ) return this } @@ -124,7 +132,7 @@ class TestWallet( Twig.debug { "FOUND utxo balance of total: $walletBalance" } if (walletBalance.value > 0L) { - synchronizer.shieldFunds(spendingKey) + synchronizer.createProposedTransactions(synchronizer.proposeShielding(spendingKey.account), spendingKey) } } 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 bd74e975..2300d173 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 @@ -598,6 +598,13 @@ class SdkSynchronizer private constructor( } } + @Deprecated( + message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.", + replaceWith = + ReplaceWith( + "createProposedTransactions(proposeTransfer(usk.account, toAddress, amount, memo), usk)" + ) + ) @Throws(TransactionEncoderException::class, TransactionSubmitException::class) override suspend fun sendToAddress( usk: UnifiedSpendingKey, @@ -624,6 +631,13 @@ class SdkSynchronizer private constructor( } } + @Deprecated( + message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.", + replaceWith = + ReplaceWith( + "createProposedTransactions(proposeShielding(usk.account, memo), usk)" + ) + ) @Throws(TransactionEncoderException::class, TransactionSubmitException::class) override suspend fun shieldFunds( usk: UnifiedSpendingKey, 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 27b9d3d5..e8620f4e 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 @@ -226,6 +226,13 @@ interface Synchronizer { * useful for updating the UI without needing to poll. Of course, polling is always an option * for any wallet that wants to ignore this return value. */ + @Deprecated( + message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.", + replaceWith = + ReplaceWith( + "createProposedTransactions(proposeTransfer(usk.account, toAddress, amount, memo), usk)" + ) + ) suspend fun sendToAddress( usk: UnifiedSpendingKey, amount: Zatoshi, @@ -233,6 +240,13 @@ interface Synchronizer { memo: String = "" ): Long + @Deprecated( + message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.", + replaceWith = + ReplaceWith( + "createProposedTransactions(proposeShielding(usk.account, memo), usk)" + ) + ) suspend fun shieldFunds( usk: UnifiedSpendingKey, memo: String = ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX From e36bbdec7a536c68ac281ef9bdf35a8e517929ce Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 27 Feb 2024 19:00:06 +0000 Subject: [PATCH 04/12] Expose shielding threshold in `Synchronizer.proposeShielding` `Synchronizer.shieldFunds` is not altered because it is deprecated. --- .../main/java/cash/z/ecc/android/sdk/internal/Backend.kt | 1 + .../java/cash/z/ecc/android/sdk/internal/jni/RustBackend.kt | 3 +++ backend-lib/src/main/rust/lib.rs | 5 +++-- .../java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt | 2 +- .../sdk/demoapp/demos/getbalance/GetBalanceFragment.kt | 3 ++- .../sdk/demoapp/ui/screen/home/viewmodel/WalletViewModel.kt | 3 ++- .../java/cash/z/ecc/android/sdk/util/TestWallet.kt | 5 ++++- .../androidTest/java/cash/z/ecc/fixture/FakeRustBackend.kt | 1 + .../src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt | 5 +++-- .../src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt | 5 ++++- .../java/cash/z/ecc/android/sdk/internal/TypesafeBackend.kt | 1 + .../cash/z/ecc/android/sdk/internal/TypesafeBackendImpl.kt | 2 ++ .../sdk/internal/transaction/OutboundTransactionManager.kt | 3 +++ .../internal/transaction/OutboundTransactionManagerImpl.kt | 3 ++- .../android/sdk/internal/transaction/TransactionEncoder.kt | 3 +++ .../sdk/internal/transaction/TransactionEncoderImpl.kt | 6 ++++-- 16 files changed, 39 insertions(+), 12 deletions(-) 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 eb68e94f..d4dbf199 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 @@ -30,6 +30,7 @@ interface Backend { suspend fun proposeShielding( account: Int, + shieldingThreshold: Long, memo: ByteArray? = byteArrayOf() ): ProposalUnsafe 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 5cc71682..ec74a843 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 @@ -307,6 +307,7 @@ class RustBackend private constructor( override suspend fun proposeShielding( account: Int, + shieldingThreshold: Long, memo: ByteArray? ): ProposalUnsafe { return withContext(SdkDispatchers.DATABASE_IO) { @@ -314,6 +315,7 @@ class RustBackend private constructor( proposeShielding( dataDbFile.absolutePath, account, + shieldingThreshold, memo ?: ByteArray(0), networkId = networkId, useZip317Fees = IS_USE_ZIP_317_FEES @@ -584,6 +586,7 @@ class RustBackend private constructor( private external fun proposeShielding( dbDataPath: String, account: Int, + shieldingThreshold: Long, memo: ByteArray, networkId: Int, useZip317Fees: Boolean diff --git a/backend-lib/src/main/rust/lib.rs b/backend-lib/src/main/rust/lib.rs index 98c53545..b721f04e 100644 --- a/backend-lib/src/main/rust/lib.rs +++ b/backend-lib/src/main/rust/lib.rs @@ -1480,6 +1480,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeSh _: JClass<'local>, db_data: JString<'local>, account: jint, + shielding_threshold: jlong, memo: JByteArray<'local>, network_id: jint, use_zip317_fees: jboolean, @@ -1489,6 +1490,8 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeSh let network = parse_network(network_id as u32)?; let mut db_data = wallet_db(env, network, db_data)?; let account = account_id_from_jint(account)?; + let shielding_threshold = NonNegativeAmount::from_nonnegative_i64(shielding_threshold) + .map_err(|()| format_err!("Invalid shielding threshold, out of range"))?; let memo_bytes = env.convert_byte_array(memo).unwrap(); let min_confirmations = 0; @@ -1520,8 +1523,6 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeSh let input_selector = zip317_helper(Some(MemoBytes::from(&memo)), use_zip317_fees); - let shielding_threshold = NonNegativeAmount::from_u64(100000).unwrap(); - let proposal = propose_shielding::<_, _, _, Infallible>( &mut db_data, &network, diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt index 73ea19dd..65dd0f66 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt @@ -130,7 +130,7 @@ class TestWallet( synchronizer.getTransparentBalance(transparentAddress).let { walletBalance -> if (walletBalance.value > 0L) { synchronizer.createProposedTransactions( - synchronizer.proposeShielding(shieldedSpendingKey.account), + synchronizer.proposeShielding(shieldedSpendingKey.account, Zatoshi(100000)), shieldedSpendingKey ) } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt index f5ce17fe..9145ca9a 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt @@ -47,6 +47,7 @@ class GetBalanceFragment : BaseDemoFragment() { reportTraceEvent(SyncBlockchainBenchmarkTrace.Event.BALANCE_SCREEN_END) } + @Suppress("MagicNumber") override fun onViewCreated( view: View, savedInstanceState: Bundle? @@ -68,7 +69,7 @@ class GetBalanceFragment : BaseDemoFragment() { ) sharedViewModel.synchronizerFlow.value?.let { synchronizer -> synchronizer.createProposedTransactions( - synchronizer.proposeShielding(usk.account), + synchronizer.proposeShielding(usk.account, Zatoshi(100000)), usk ) } 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 91d7f6fc..417bd083 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 @@ -217,6 +217,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) * * Observe the result via [sendState]. */ + @Suppress("MagicNumber") fun shieldFunds() { if (sendState.value is SendState.Sending) { return @@ -230,7 +231,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) val spendingKey = spendingKey.filterNotNull().first() kotlin.runCatching { synchronizer.createProposedTransactions( - synchronizer.proposeShielding(spendingKey.account), + synchronizer.proposeShielding(spendingKey.account, Zatoshi(100000)), spendingKey ) } diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt index f6d57a67..a6ef3e45 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt @@ -132,7 +132,10 @@ class TestWallet( Twig.debug { "FOUND utxo balance of total: $walletBalance" } if (walletBalance.value > 0L) { - synchronizer.createProposedTransactions(synchronizer.proposeShielding(spendingKey.account), spendingKey) + synchronizer.createProposedTransactions( + synchronizer.proposeShielding(spendingKey.account, Zatoshi(100000)), + spendingKey + ) } } 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 631c8d98..90efd678 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 @@ -88,6 +88,7 @@ internal class FakeRustBackend( override suspend fun proposeShielding( account: Int, + shieldingThreshold: Long, memo: ByteArray? ): ProposalUnsafe { TODO("Not yet implemented") 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 2300d173..91d4eac0 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 @@ -569,8 +569,9 @@ class SdkSynchronizer private constructor( @Throws(TransactionEncoderException::class) override suspend fun proposeShielding( account: Account, + shieldingThreshold: Zatoshi, memo: String - ): Proposal = txManager.proposeShielding(account, memo) + ): Proposal = txManager.proposeShielding(account, shieldingThreshold, memo) @Throws(TransactionEncoderException::class) override suspend fun createProposedTransactions( @@ -635,7 +636,7 @@ class SdkSynchronizer private constructor( message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.", replaceWith = ReplaceWith( - "createProposedTransactions(proposeShielding(usk.account, memo), usk)" + "createProposedTransactions(proposeShielding(usk.account, shieldingThreshold, memo), usk)" ) ) @Throws(TransactionEncoderException::class, TransactionSubmitException::class) 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 e8620f4e..914b7ecc 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 @@ -190,10 +190,13 @@ interface Synchronizer { * Creates a proposal for shielding any transparent funds received by the given account. * * @param account the account for which to shield funds. + * @param shieldingThreshold the minimum transparent balance required before a + * proposal will be created. * @param memo the optional memo to include as part of the proposal's transactions. */ suspend fun proposeShielding( account: Account, + shieldingThreshold: Zatoshi, memo: String = ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX ): Proposal @@ -244,7 +247,7 @@ interface Synchronizer { message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.", replaceWith = ReplaceWith( - "createProposedTransactions(proposeShielding(usk.account, memo), usk)" + "createProposedTransactions(proposeShielding(usk.account, shieldingThreshold, memo), usk)" ) ) suspend fun shieldFunds( 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 fa36afa4..a8a3a272 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 @@ -34,6 +34,7 @@ internal interface TypesafeBackend { suspend fun proposeShielding( account: Account, + shieldingThreshold: Long, memo: ByteArray? = byteArrayOf() ): Proposal 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 d519e677..58afb413 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 @@ -53,11 +53,13 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke override suspend fun proposeShielding( account: Account, + shieldingThreshold: Long, memo: ByteArray? ): Proposal = Proposal.fromUnsafe( backend.proposeShielding( account.value, + shieldingThreshold, memo ) ) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt index 7552c730..0e4ca96b 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt @@ -56,10 +56,13 @@ internal interface OutboundTransactionManager { * Creates a proposal for shielding any transparent funds received by the given account. * * @param account the account for which to shield funds. + * @param shieldingThreshold the minimum transparent balance required before a + * proposal will be created. * @param memo the optional memo to include as part of the proposal's transactions. */ suspend fun proposeShielding( account: Account, + shieldingThreshold: Zatoshi, memo: String ): Proposal diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt index c0c4adbd..3d5143b3 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt @@ -52,8 +52,9 @@ internal class OutboundTransactionManagerImpl( override suspend fun proposeShielding( account: Account, + shieldingThreshold: Zatoshi, memo: String - ): Proposal = encoder.proposeShielding(account, memo.toByteArray()) + ): Proposal = encoder.proposeShielding(account, shieldingThreshold, memo.toByteArray()) override suspend fun createProposedTransactions( proposal: Proposal, diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoder.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoder.kt index 437781e4..cd1845d4 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoder.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoder.kt @@ -61,10 +61,13 @@ internal interface TransactionEncoder { * Creates a proposal for shielding any transparent funds sent to the given account. * * @param account the account for which to shield funds. + * @param shieldingThreshold the minimum transparent balance required before a + * proposal will be created. * @param memo the optional memo to include as part of the proposal's transactions. */ suspend fun proposeShielding( account: Account, + shieldingThreshold: Zatoshi, memo: ByteArray? = byteArrayOf() ): Proposal diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt index 34e82718..9509dca3 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt @@ -96,11 +96,12 @@ internal class TransactionEncoderImpl( override suspend fun proposeShielding( account: Account, + shieldingThreshold: Zatoshi, memo: ByteArray? ): Proposal { @Suppress("TooGenericExceptionCaught") return try { - backend.proposeShielding(account, memo) + backend.proposeShielding(account, shieldingThreshold.value, memo) } catch (t: Throwable) { // TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee) // then consider custom error that says no UTXOs existed to shield @@ -229,6 +230,7 @@ internal class TransactionEncoderImpl( } } + @Suppress("MagicNumber") private suspend fun createShieldingSpend( usk: UnifiedSpendingKey, memo: ByteArray? = byteArrayOf() @@ -237,7 +239,7 @@ internal class TransactionEncoderImpl( return try { saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory) Twig.debug { "params exist! attempting to shield..." } - val proposal = backend.proposeShielding(usk.account, memo) + val proposal = backend.proposeShielding(usk.account, 100000, memo) backend.createProposedTransaction(proposal, usk) } catch (t: Throwable) { // TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee) From abffb3f9ee096345135bef88dd77aced69322194 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 28 Feb 2024 01:44:12 +0000 Subject: [PATCH 05/12] Adjust `Synchronizer.proposeShielding` API - Returns `null` when there are no funds to shield or the shielding threshold is not met. - Throws an exception if there are funds to shield in more than one transparent receiver within the account. - Has an optional parameter for specifying which transparent receiver to shield funds from. Part of Electric-Coin-Company/zcash-android-wallet-sdk#680. --- .../z/ecc/android/sdk/internal/Backend.kt | 5 +- .../android/sdk/internal/jni/RustBackend.kt | 29 ++++++----- backend-lib/src/main/rust/lib.rs | 49 +++++++++++++++++-- backend-lib/src/main/rust/utils.rs | 4 ++ .../android/sdk/darkside/test/TestWallet.kt | 10 ++-- .../demos/getbalance/GetBalanceFragment.kt | 10 ++-- .../screen/home/viewmodel/WalletViewModel.kt | 12 +++-- .../cash/z/ecc/android/sdk/util/TestWallet.kt | 10 ++-- .../cash/z/ecc/fixture/FakeRustBackend.kt | 5 +- .../cash/z/ecc/android/sdk/SdkSynchronizer.kt | 7 +-- .../cash/z/ecc/android/sdk/Synchronizer.kt | 17 +++++-- .../android/sdk/internal/TypesafeBackend.kt | 5 +- .../sdk/internal/TypesafeBackendImpl.kt | 20 +++++--- .../transaction/OutboundTransactionManager.kt | 15 +++++- .../OutboundTransactionManagerImpl.kt | 5 +- .../transaction/TransactionEncoder.kt | 15 +++++- .../transaction/TransactionEncoderImpl.kt | 12 +++-- 17 files changed, 167 insertions(+), 63 deletions(-) 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 d4dbf199..6ba1375b 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 @@ -31,8 +31,9 @@ interface Backend { suspend fun proposeShielding( account: Int, shieldingThreshold: Long, - memo: ByteArray? = byteArrayOf() - ): ProposalUnsafe + memo: ByteArray? = byteArrayOf(), + transparentReceiver: String? = null + ): ProposalUnsafe? suspend fun createProposedTransaction( proposal: ProposalUnsafe, 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 ec74a843..e0cfffde 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 @@ -308,19 +308,23 @@ class RustBackend private constructor( override suspend fun proposeShielding( account: Int, shieldingThreshold: Long, - memo: ByteArray? - ): ProposalUnsafe { + memo: ByteArray?, + transparentReceiver: String? + ): ProposalUnsafe? { return withContext(SdkDispatchers.DATABASE_IO) { - ProposalUnsafe.parse( - proposeShielding( - dataDbFile.absolutePath, - account, - shieldingThreshold, - memo ?: ByteArray(0), - networkId = networkId, - useZip317Fees = IS_USE_ZIP_317_FEES + proposeShielding( + dataDbFile.absolutePath, + account, + shieldingThreshold, + memo ?: ByteArray(0), + transparentReceiver, + networkId = networkId, + useZip317Fees = IS_USE_ZIP_317_FEES + )?.let { + ProposalUnsafe.parse( + it ) - ) + } } } @@ -588,9 +592,10 @@ class RustBackend private constructor( account: Int, shieldingThreshold: Long, memo: ByteArray, + transparentReceiver: String?, networkId: Int, useZip317Fees: Boolean - ): ByteArray + ): ByteArray? @JvmStatic @Suppress("LongParameterList") diff --git a/backend-lib/src/main/rust/lib.rs b/backend-lib/src/main/rust/lib.rs index b721f04e..2890400c 100644 --- a/backend-lib/src/main/rust/lib.rs +++ b/backend-lib/src/main/rust/lib.rs @@ -1482,6 +1482,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeSh account: jint, shielding_threshold: jlong, memo: JByteArray<'local>, + transparent_receiver: JString<'local>, network_id: jint, use_zip317_fees: jboolean, ) -> jbyteArray { @@ -1494,10 +1495,36 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeSh .map_err(|()| format_err!("Invalid shielding threshold, out of range"))?; let memo_bytes = env.convert_byte_array(memo).unwrap(); + let transparent_receiver = + match utils::java_nullable_string_to_rust(env, &transparent_receiver) { + None => Ok(None), + Some(addr) => match Address::decode(&network, &addr) { + None => Err(format_err!("Transparent receiver is for the wrong network")), + Some(addr) => match addr { + Address::Sapling(_) | Address::Unified(_) => Err(format_err!( + "Transparent receiver is not a transparent address" + )), + Address::Transparent(addr) => { + if db_data + .get_transparent_receivers(account)? + .contains_key(&addr) + { + Ok(Some(addr)) + } else { + Err(format_err!( + "Transparent receiver does not belong to account {}", + u32::from(account), + )) + } + } + }, + }, + }?; + let min_confirmations = 0; let min_confirmations_for_heights = NonZeroU32::new(1).unwrap(); - let from_addrs: Vec = db_data + let account_receivers = db_data .get_target_and_anchor_heights(min_confirmations_for_heights) .map_err(|e| format_err!("Error while fetching anchor height: {}", e)) .and_then(|opt_anchor| { @@ -1515,9 +1542,23 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeSh e ) }) - })? - .into_keys() - .collect(); + })?; + + let from_addrs = if let Some((addr, _)) = transparent_receiver.map_or_else(|| + if account_receivers.len() > 1 { + Err(format_err!( + "Account has more than one transparent receiver with funds to shield; this is not yet supported by the SDK. Provide a specific transparent receiver to shield funds from." + )) + } else { + Ok(account_receivers.iter().next().map(|(a, v)| (*a, *v))) + }, + |addr| Ok(account_receivers.get(&addr).map(|value| (addr, *value))) + )?.filter(|(_, value)| *value >= shielding_threshold.into()) { + [addr] + } else { + // There are no transparent funds to shield; don't create a proposal. + return Ok(ptr::null_mut()); + }; let memo = Memo::from_bytes(&memo_bytes).unwrap(); diff --git a/backend-lib/src/main/rust/utils.rs b/backend-lib/src/main/rust/utils.rs index da7b7aac..ef1ed9aa 100644 --- a/backend-lib/src/main/rust/utils.rs +++ b/backend-lib/src/main/rust/utils.rs @@ -38,6 +38,10 @@ pub(crate) fn java_string_to_rust(env: &mut JNIEnv, jstring: &JString) -> String .into() } +pub(crate) fn java_nullable_string_to_rust(env: &mut JNIEnv, jstring: &JString) -> Option { + (!jstring.is_null()).then(|| java_string_to_rust(env, jstring)) +} + pub(crate) fn rust_bytes_to_java<'a>( env: &JNIEnv<'a>, data: &[u8], diff --git a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt index 65dd0f66..2bbd69aa 100644 --- a/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt +++ b/darkside-test-lib/src/androidTest/java/cash/z/ecc/android/sdk/darkside/test/TestWallet.kt @@ -129,10 +129,12 @@ class TestWallet( synchronizer.getTransparentBalance(transparentAddress).let { walletBalance -> if (walletBalance.value > 0L) { - synchronizer.createProposedTransactions( - synchronizer.proposeShielding(shieldedSpendingKey.account, Zatoshi(100000)), - shieldedSpendingKey - ) + synchronizer.proposeShielding(shieldedSpendingKey.account, Zatoshi(100000))?.let { + synchronizer.createProposedTransactions( + it, + shieldedSpendingKey + ) + } } } diff --git a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt index 9145ca9a..d221e9ad 100644 --- a/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt +++ b/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt @@ -68,10 +68,12 @@ class GetBalanceFragment : BaseDemoFragment() { Account.DEFAULT ) sharedViewModel.synchronizerFlow.value?.let { synchronizer -> - synchronizer.createProposedTransactions( - synchronizer.proposeShielding(usk.account, Zatoshi(100000)), - usk - ) + synchronizer.proposeShielding(usk.account, Zatoshi(100000))?.let { it1 -> + synchronizer.createProposedTransactions( + it1, + usk + ) + } } } } 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 417bd083..df66cb47 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 @@ -230,12 +230,14 @@ class WalletViewModel(application: Application) : AndroidViewModel(application) viewModelScope.launch { val spendingKey = spendingKey.filterNotNull().first() kotlin.runCatching { - synchronizer.createProposedTransactions( - synchronizer.proposeShielding(spendingKey.account, Zatoshi(100000)), - spendingKey - ) + synchronizer.proposeShielding(spendingKey.account, Zatoshi(100000))?.let { + synchronizer.createProposedTransactions( + it, + spendingKey + ) + } } - .onSuccess { mutableSendState.value = SendState.Sent(it.toList()) } + .onSuccess { it?.let { mutableSendState.value = SendState.Sent(it.toList()) } } .onFailure { mutableSendState.value = SendState.Error(it) } } } else { diff --git a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt index a6ef3e45..1edf516a 100644 --- a/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt +++ b/sdk-lib/src/androidTest/java/cash/z/ecc/android/sdk/util/TestWallet.kt @@ -132,10 +132,12 @@ class TestWallet( Twig.debug { "FOUND utxo balance of total: $walletBalance" } if (walletBalance.value > 0L) { - synchronizer.createProposedTransactions( - synchronizer.proposeShielding(spendingKey.account, Zatoshi(100000)), - spendingKey - ) + synchronizer.proposeShielding(spendingKey.account, Zatoshi(100000))?.let { + synchronizer.createProposedTransactions( + it, + spendingKey + ) + } } } 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 90efd678..53872421 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 @@ -89,8 +89,9 @@ internal class FakeRustBackend( override suspend fun proposeShielding( account: Int, shieldingThreshold: Long, - memo: ByteArray? - ): ProposalUnsafe { + memo: ByteArray?, + transparentReceiver: String? + ): ProposalUnsafe? { TODO("Not yet implemented") } 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 91d4eac0..af0183ef 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 @@ -570,8 +570,9 @@ class SdkSynchronizer private constructor( override suspend fun proposeShielding( account: Account, shieldingThreshold: Zatoshi, - memo: String - ): Proposal = txManager.proposeShielding(account, shieldingThreshold, memo) + memo: String, + transparentReceiver: String? + ): Proposal? = txManager.proposeShielding(account, shieldingThreshold, memo, transparentReceiver) @Throws(TransactionEncoderException::class) override suspend fun createProposedTransactions( @@ -636,7 +637,7 @@ class SdkSynchronizer private constructor( message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.", replaceWith = ReplaceWith( - "createProposedTransactions(proposeShielding(usk.account, shieldingThreshold, memo), usk)" + "proposeShielding(usk.account, shieldingThreshold, memo)?.let { createProposedTransactions(it, usk) }" ) ) @Throws(TransactionEncoderException::class, TransactionSubmitException::class) 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 914b7ecc..4bc740f7 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 @@ -193,12 +193,23 @@ interface Synchronizer { * @param shieldingThreshold the minimum transparent balance required before a * proposal will be created. * @param memo the optional memo to include as part of the proposal's transactions. + * @param transparentReceiver a specific transparent receiver within the account that + * should be the source of transparent funds. Default is + * null which will select whichever of the account's + * transparent receivers has funds to shield. + * + * @return the proposal, or null if the transparent balance that would be shielded is + * zero or below `shieldingThreshold`. + * + * @throws Exception if `transparentReceiver` is null and there are transparent funds + * in more than one of the account's transparent receivers. */ suspend fun proposeShielding( account: Account, shieldingThreshold: Zatoshi, - memo: String = ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX - ): Proposal + memo: String = ZcashSdk.DEFAULT_SHIELD_FUNDS_MEMO_PREFIX, + transparentReceiver: String? = null + ): Proposal? /** * Creates the transactions in the given proposal. @@ -247,7 +258,7 @@ interface Synchronizer { message = "Upcoming SDK 2.1 will create multiple transactions at once for some recipients.", replaceWith = ReplaceWith( - "createProposedTransactions(proposeShielding(usk.account, shieldingThreshold, memo), usk)" + "proposeShielding(usk.account, shieldingThreshold, memo)?.let { createProposedTransactions(it, usk) }" ) ) suspend fun shieldFunds( 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 a8a3a272..69a7c0b6 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 @@ -35,8 +35,9 @@ internal interface TypesafeBackend { suspend fun proposeShielding( account: Account, shieldingThreshold: Long, - memo: ByteArray? = byteArrayOf() - ): Proposal + memo: ByteArray? = byteArrayOf(), + transparentReceiver: String? = null + ): Proposal? suspend fun createProposedTransaction( proposal: Proposal, 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 58afb413..5d66a329 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 @@ -54,15 +54,19 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke override suspend fun proposeShielding( account: Account, shieldingThreshold: Long, - memo: ByteArray? - ): Proposal = - Proposal.fromUnsafe( - backend.proposeShielding( - account.value, - shieldingThreshold, - memo + memo: ByteArray?, + transparentReceiver: String? + ): Proposal? = + backend.proposeShielding( + account.value, + shieldingThreshold, + memo, + transparentReceiver + )?.let { + Proposal.fromUnsafe( + it ) - ) + } override suspend fun createProposedTransaction( proposal: Proposal, diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt index 0e4ca96b..fcc9b3e2 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManager.kt @@ -59,12 +59,23 @@ internal interface OutboundTransactionManager { * @param shieldingThreshold the minimum transparent balance required before a * proposal will be created. * @param memo the optional memo to include as part of the proposal's transactions. + * @param transparentReceiver a specific transparent receiver within the account that + * should be the source of transparent funds. Default is + * null which will select whichever of the account's + * transparent receivers has funds to shield. + * + * @return the proposal, or null if the transparent balance that would be shielded is + * zero or below `shieldingThreshold`. + * + * @throws Exception if `transparentReceiver` is null and there are transparent funds + * in more than one of the account's transparent receivers. */ suspend fun proposeShielding( account: Account, shieldingThreshold: Zatoshi, - memo: String - ): Proposal + memo: String, + transparentReceiver: String? + ): Proposal? /** * Creates the transactions in the given proposal. diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt index 3d5143b3..45fd421a 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt @@ -53,8 +53,9 @@ internal class OutboundTransactionManagerImpl( override suspend fun proposeShielding( account: Account, shieldingThreshold: Zatoshi, - memo: String - ): Proposal = encoder.proposeShielding(account, shieldingThreshold, memo.toByteArray()) + memo: String, + transparentReceiver: String? + ): Proposal? = encoder.proposeShielding(account, shieldingThreshold, memo.toByteArray(), transparentReceiver) override suspend fun createProposedTransactions( proposal: Proposal, diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoder.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoder.kt index cd1845d4..c0aa8cea 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoder.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoder.kt @@ -64,12 +64,23 @@ internal interface TransactionEncoder { * @param shieldingThreshold the minimum transparent balance required before a * proposal will be created. * @param memo the optional memo to include as part of the proposal's transactions. + * @param transparentReceiver a specific transparent receiver within the account that + * should be the source of transparent funds. Default is + * null which will select whichever of the account's + * transparent receivers has funds to shield. + * + * @return the proposal, or null if the transparent balance that would be shielded is + * zero or below `shieldingThreshold`. + * + * @throws Exception if `transparentReceiver` is null and there are transparent funds + * in more than one of the account's transparent receivers. */ suspend fun proposeShielding( account: Account, shieldingThreshold: Zatoshi, - memo: ByteArray? = byteArrayOf() - ): Proposal + memo: ByteArray? = byteArrayOf(), + transparentReceiver: String? = null + ): Proposal? /** * Creates the transactions in the given proposal. diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt index 9509dca3..671c089a 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt @@ -1,5 +1,6 @@ package cash.z.ecc.android.sdk.internal.transaction +import cash.z.ecc.android.sdk.exception.SdkException import cash.z.ecc.android.sdk.exception.TransactionEncoderException import cash.z.ecc.android.sdk.ext.masked import cash.z.ecc.android.sdk.internal.SaplingParamTool @@ -97,11 +98,12 @@ internal class TransactionEncoderImpl( override suspend fun proposeShielding( account: Account, shieldingThreshold: Zatoshi, - memo: ByteArray? - ): Proposal { + memo: ByteArray?, + transparentReceiver: String? + ): Proposal? { @Suppress("TooGenericExceptionCaught") return try { - backend.proposeShielding(account, shieldingThreshold.value, memo) + backend.proposeShielding(account, shieldingThreshold.value, memo, transparentReceiver) } catch (t: Throwable) { // TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee) // then consider custom error that says no UTXOs existed to shield @@ -239,7 +241,9 @@ internal class TransactionEncoderImpl( return try { saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory) Twig.debug { "params exist! attempting to shield..." } - val proposal = backend.proposeShielding(usk.account, 100000, memo) + val proposal = + backend.proposeShielding(usk.account, 100000, memo) + ?: throw SdkException("Insufficient balance (have 0, need 100000 including fee)", null) backend.createProposedTransaction(proposal, usk) } catch (t: Throwable) { // TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee) From 99e91fa8b7d87d1fc5a71d0eb4fb2b29e5f0ac63 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 1 Mar 2024 18:51:54 +0000 Subject: [PATCH 06/12] Migrate to `zcash_client_sqlite 0.9.0` The backend now supports proposals that create multiple transactions. It still does not generate such proposals itself, and we assert this inside the now-deprecated APIs. --- backend-lib/Cargo.lock | 47 +++++++----- backend-lib/Cargo.toml | 17 +---- .../z/ecc/android/sdk/internal/Backend.kt | 4 +- .../android/sdk/internal/jni/RustBackend.kt | 12 +-- .../sdk/internal/model/ProposalUnsafe.kt | 6 +- backend-lib/src/main/proto/proposal.proto | 75 +++++++++++++++---- backend-lib/src/main/rust/lib.rs | 43 ++++++----- backend-lib/src/main/rust/utils.rs | 5 +- .../cash/z/ecc/fixture/FakeRustBackend.kt | 4 +- .../android/sdk/internal/TypesafeBackend.kt | 4 +- .../sdk/internal/TypesafeBackendImpl.kt | 14 ++-- .../transaction/TransactionEncoderImpl.kt | 22 ++++-- .../cash/z/ecc/android/sdk/model/Proposal.kt | 8 +- 13 files changed, 157 insertions(+), 104 deletions(-) diff --git a/backend-lib/Cargo.lock b/backend-lib/Cargo.lock index 0d35da5a..fbb678cd 100644 --- a/backend-lib/Cargo.lock +++ b/backend-lib/Cargo.lock @@ -469,7 +469,8 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "equihash" version = "0.2.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab579d7cf78477773b03e80bc2f89702ef02d7112c711d54ca93dcdce68533d5" dependencies = [ "blake2b_simd", "byteorder", @@ -494,7 +495,8 @@ dependencies = [ [[package]] name = "f4jumble" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a83e8d7fd0c526af4aad893b7c9fe41e2699ed8a776a6c74aecdeafe05afc75" dependencies = [ "blake2b_simd", ] @@ -1035,9 +1037,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "orchard" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c801aeaccd19bb6916d71f25694b62d223061872900e8022221c1ad8dcad2d" +checksum = "1fb255c3ffdccd3c84fe9ebed72aef64fdc72e6a3e4180dd411002d47abaad42" dependencies = [ "aes", "bitvec", @@ -1455,9 +1457,9 @@ dependencies = [ [[package]] name = "sapling-crypto" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f5de898a7cdb7f6d9c8fb888341b6ae6e2aeae88227b7f435f1dda49ecf9e62" +checksum = "d183012062dfdde85f7e3e758328fcf6e9846d8dd3fce35b04d0efcb6677b0e0" dependencies = [ "aes", "bellman", @@ -2169,7 +2171,6 @@ dependencies = [ "jni", "libc", "log-panics", - "orchard", "paranoid-android", "prost", "rayon", @@ -2190,7 +2191,8 @@ dependencies = [ [[package]] name = "zcash_address" version = "0.3.1" -source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce173f1d9ed4f806e310bc3a873301531e7a6dc209928584d6404e3f8228ef4" dependencies = [ "bech32", "bs58", @@ -2200,8 +2202,9 @@ dependencies = [ [[package]] name = "zcash_client_backend" -version = "0.10.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185913267d824529b9547c933674963fca2b5bd84ad377a59d0f8ab6159ce798" dependencies = [ "base64", "bech32", @@ -2239,8 +2242,9 @@ dependencies = [ [[package]] name = "zcash_client_sqlite" -version = "0.8.1" -source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e883405989b8d7275a0e1180000b7568bb3fa33e36b4806c174eb802678e2dbf" dependencies = [ "bs58", "byteorder", @@ -2250,7 +2254,6 @@ dependencies = [ "incrementalmerkletree", "jubjub", "maybe-rayon", - "orchard", "prost", "rusqlite", "sapling-crypto", @@ -2271,7 +2274,8 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.2.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f03391b81727875efa6ac0661a20883022b6fba92365dc121c48fa9b00c5aac0" dependencies = [ "byteorder", "nonempty", @@ -2279,8 +2283,9 @@ dependencies = [ [[package]] name = "zcash_keys" -version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48390513c05a4090533e2a1aca486f0fb6b684493b8aefedda6ee01742b3f8f6" dependencies = [ "bech32", "bls12_381", @@ -2317,8 +2322,9 @@ dependencies = [ [[package]] name = "zcash_primitives" -version = "0.13.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9070e084570bb78aed4f8d71fd6254492e62c87a5d01e084183980e98117092d" dependencies = [ "aes", "bip0039", @@ -2354,8 +2360,9 @@ dependencies = [ [[package]] name = "zcash_proofs" -version = "0.13.0" -source = "git+https://github.com/zcash/librustzcash.git?rev=9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a#9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a02eb1f151d9b9a6e16408d2c55ff440bd2fb232b7377277146d0fa2df9bc8" dependencies = [ "bellman", "blake2b_simd", diff --git a/backend-lib/Cargo.toml b/backend-lib/Cargo.toml index 8885ab59..ca135271 100644 --- a/backend-lib/Cargo.toml +++ b/backend-lib/Cargo.toml @@ -23,11 +23,10 @@ schemer = "0.2" secp256k1 = "0.26" secrecy = "0.8" zcash_address = "0.3" -zcash_client_backend = { version = "0.10", features = ["transparent-inputs", "unstable"] } -zcash_client_sqlite = { version = "^0.8.1", features = ["transparent-inputs", "unstable"] } -zcash_primitives = "0.13" -zcash_proofs = "0.13" -orchard = { version = "0.7", default-features = false } +zcash_client_backend = { version = "0.11", features = ["transparent-inputs", "unstable"] } +zcash_client_sqlite = { version = "0.9", features = ["transparent-inputs", "unstable"] } +zcash_primitives = "0.14" +zcash_proofs = "0.14" # Initialization rayon = "1.7" @@ -62,11 +61,3 @@ libc = "0.2" name = "zcashwalletsdk" path = "src/main/rust/lib.rs" crate-type = ["staticlib", "cdylib"] - -[patch.crates-io] -# Tag `ecc_sdk-20240130a` -zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" } -zcash_client_backend = { git = "https://github.com/zcash/librustzcash.git", rev = "9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" } -zcash_client_sqlite = { git = "https://github.com/zcash/librustzcash.git", rev = "9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" } -zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" } -zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "9b5abe1a386f2c2c625d8f3fb3b6e9ce9aff641a" } 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 6ba1375b..0fef4da1 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 @@ -35,10 +35,10 @@ interface Backend { transparentReceiver: String? = null ): ProposalUnsafe? - suspend fun createProposedTransaction( + suspend fun createProposedTransactions( proposal: ProposalUnsafe, unifiedSpendingKey: ByteArray - ): ByteArray + ): List suspend fun decryptAndStoreTransaction(tx: ByteArray) 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 e0cfffde..82f5f0bc 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 @@ -328,19 +328,19 @@ class RustBackend private constructor( } } - override suspend fun createProposedTransaction( + override suspend fun createProposedTransactions( proposal: ProposalUnsafe, unifiedSpendingKey: ByteArray - ): ByteArray = + ): List = withContext(SdkDispatchers.DATABASE_IO) { - createProposedTransaction( + createProposedTransactions( dataDbFile.absolutePath, proposal.toByteArray(), unifiedSpendingKey, spendParamsPath = saplingSpendFile.absolutePath, outputParamsPath = saplingOutputFile.absolutePath, networkId = networkId - ) + ).asList() } override suspend fun putUtxo( @@ -599,14 +599,14 @@ class RustBackend private constructor( @JvmStatic @Suppress("LongParameterList") - private external fun createProposedTransaction( + private external fun createProposedTransactions( dbDataPath: String, proposal: ByteArray, usk: ByteArray, spendParamsPath: String, outputParamsPath: String, networkId: Int - ): ByteArray + ): Array @JvmStatic private external fun branchIdForHeight( diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ProposalUnsafe.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ProposalUnsafe.kt index f9af1fa6..5ce80d3f 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ProposalUnsafe.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ProposalUnsafe.kt @@ -38,9 +38,9 @@ class ProposalUnsafe( } /** - * Returns the fee required by this proposal. + * Returns the total fee required by this proposal for its transactions. */ - fun feeRequired(): Long { - return inner.balance.feeRequired + fun totalFeeRequired(): Long { + return inner.stepsList.fold(0) { acc, step -> acc + step.balance.feeRequired } } } diff --git a/backend-lib/src/main/proto/proposal.proto b/backend-lib/src/main/proto/proposal.proto index 84a8aed4..4084efb2 100644 --- a/backend-lib/src/main/proto/proposal.proto +++ b/backend-lib/src/main/proto/proposal.proto @@ -6,12 +6,29 @@ syntax = "proto3"; package cash.z.wallet.sdk.ffi; option java_package = "cash.z.wallet.sdk.internal.ffi"; +// A data structure that describes a series of transactions to be created. +message Proposal { + // The version of this serialization format. + uint32 protoVersion = 1; + // The fee rule used in constructing this proposal + FeeRule feeRule = 2; + // The target height for which the proposal was constructed + // + // The chain must contain at least this many blocks in order for the proposal to + // be executed. + uint32 minTargetHeight = 3; + // The series of transactions to be created. + repeated ProposalStep steps = 4; +} + // A data structure that describes the inputs to be consumed and outputs to // be produced in a proposed transaction. -message Proposal { - uint32 protoVersion = 1; +message ProposalStep { // ZIP 321 serialized transaction request - string transactionRequest = 2; + string transactionRequest = 1; + // The vector of selected payment index / output pool mappings. Payment index + // 0 corresponds to the payment with no explicit index. + repeated PaymentOutputPool paymentOutputPools = 2; // The anchor height to be used in creating the transaction, if any. // Setting the anchor height to zero will disallow the use of any shielded // inputs. @@ -21,16 +38,9 @@ message Proposal { // The total value, fee value, and change outputs of the proposed // transaction TransactionBalance balance = 5; - // The fee rule used in constructing this proposal - FeeRule feeRule = 6; - // The target height for which the proposal was constructed - // - // The chain must contain at least this many blocks in order for the proposal to - // be executed. - uint32 minTargetHeight = 7; - // A flag indicating whether the proposal is for a shielding transaction, + // A flag indicating whether the step is for a shielding transaction, // used for determining which OVK to select for wallet-internal outputs. - bool isShielding = 8; + bool isShielding = 6; } enum ValuePool { @@ -47,14 +57,45 @@ enum ValuePool { Orchard = 3; } -// The unique identifier and value for each proposed input. -message ProposedInput { +// A mapping from ZIP 321 payment index to the output pool that has been chosen +// for that payment, based upon the payment address and the selected inputs to +// the transaction. +message PaymentOutputPool { + uint32 paymentIndex = 1; + ValuePool valuePool = 2; +} + +// The unique identifier and value for each proposed input that does not +// require a back-reference to a prior step of the proposal. +message ReceivedOutput { bytes txid = 1; ValuePool valuePool = 2; uint32 index = 3; uint64 value = 4; } +// A reference a payment in a prior step of the proposal. This payment must +// belong to the wallet. +message PriorStepOutput { + uint32 stepIndex = 1; + uint32 paymentIndex = 2; +} + +// A reference a change output from a prior step of the proposal. +message PriorStepChange { + uint32 stepIndex = 1; + uint32 changeIndex = 2; +} + +// The unique identifier and value for an input to be used in the transaction. +message ProposedInput { + oneof value { + ReceivedOutput receivedOutput = 1; + PriorStepOutput priorStepOutput = 2; + PriorStepChange priorStepChange = 3; + } +} + // The fee rule used in constructing a Proposal enum FeeRule { // Protobuf requires that enums have a zero discriminant as the default @@ -72,15 +113,21 @@ enum FeeRule { // The proposed change outputs and fee value. message TransactionBalance { + // A list of change output values. repeated ChangeValue proposedChange = 1; + // The fee to be paid by the proposed transaction, in zatoshis. uint64 feeRequired = 2; } // A proposed change output. If the transparent value pool is selected, // the `memo` field must be null. message ChangeValue { + // The value of a change output to be created, in zatoshis. uint64 value = 1; + // The value pool in which the change output should be created. ValuePool valuePool = 2; + // The optional memo that should be associated with the newly created change output. + // Memos must not be present for transparent change outputs. MemoBytes memo = 3; } diff --git a/backend-lib/src/main/rust/lib.rs b/backend-lib/src/main/rust/lib.rs index 2890400c..02861c03 100644 --- a/backend-lib/src/main/rust/lib.rs +++ b/backend-lib/src/main/rust/lib.rs @@ -24,7 +24,7 @@ use zcash_client_backend::{ chain::{scan_cached_blocks, CommitmentTreeRoot, ScanSummary}, scanning::{ScanPriority, ScanRange}, wallet::{ - create_proposed_transaction, decrypt_and_store_transaction, + create_proposed_transactions, decrypt_and_store_transaction, input_selection::GreedyInputSelector, propose_shielding, propose_transfer, }, AccountBalance, AccountBirthday, InputSource, WalletCommitmentTrees, WalletRead, @@ -524,10 +524,10 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getTransp if let Some(taddr) = ua.0.transparent() { let taddr = match taddr { - TransparentAddress::PublicKey(data) => { + TransparentAddress::PublicKeyHash(data) => { ZcashAddress::from_transparent_p2pkh(network, *data) } - TransparentAddress::Script(data) => { + TransparentAddress::ScriptHash(data) => { ZcashAddress::from_transparent_p2sh(network, *data) } }; @@ -1145,7 +1145,7 @@ fn encode_account_balance<'a>( /// pending. fn encode_wallet_summary<'a>( env: &mut JNIEnv<'a>, - summary: WalletSummary, + summary: WalletSummary, ) -> jni::errors::Result> { let account_balances = utils::rust_vec_to_java( env, @@ -1397,7 +1397,7 @@ fn zip317_helper( StandardFeeRule::PreZip313 }; GreedyInputSelector::new( - SingleOutputChangeStrategy::new(fee_rule, change_memo), + SingleOutputChangeStrategy::new(fee_rule, change_memo, ShieldedProtocol::Sapling), DustOutputPolicy::default(), ) } @@ -1463,13 +1463,13 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeTr ) .map_err(|e| format_err!("Error creating transaction proposal: {}", e))?; - utils::rust_bytes_to_java( + Ok(utils::rust_bytes_to_java( &env, Proposal::from_standard_proposal(&network, &proposal) .encode_to_vec() .as_ref(), - ) - .map(|arr| arr.into_raw()) + )? + .into_raw()) }); unwrap_exc_or(&mut env, res, ptr::null_mut()) } @@ -1574,19 +1574,19 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_proposeSh ) .map_err(|e| format_err!("Error while shielding transaction: {}", e))?; - utils::rust_bytes_to_java( + Ok(utils::rust_bytes_to_java( &env, Proposal::from_standard_proposal(&network, &proposal) .encode_to_vec() .as_ref(), - ) - .map(|arr| arr.into_raw()) + )? + .into_raw()) }); unwrap_exc_or(&mut env, res, ptr::null_mut()) } #[no_mangle] -pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createProposedTransaction< +pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createProposedTransactions< 'local, >( mut env: JNIEnv<'local>, @@ -1597,7 +1597,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createPro spend_params: JString<'local>, output_params: JString<'local>, network_id: jint, -) -> jbyteArray { +) -> jobjectArray { let res = catch_unwind(&mut env, |env| { let _span = tracing::info_span!("RustBackend.createProposedTransaction").entered(); let network = parse_network(network_id as u32)?; @@ -1612,7 +1612,7 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createPro .map_err(|e| format_err!("Invalid proposal: {}", e))? .try_into_standard_proposal(&network, &db_data)?; - let txid = create_proposed_transaction::<_, _, Infallible, _, _>( + let txids = create_proposed_transactions::<_, _, Infallible, _, _>( &mut db_data, &network, &prover, @@ -1621,9 +1621,16 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_createPro OvkPolicy::Sender, &proposal, ) - .map_err(|e| format_err!("Error while creating transaction: {}", e))?; + .map_err(|e| format_err!("Error while creating transactions: {}", e))?; - utils::rust_bytes_to_java(&env, txid.as_ref()).map(|arr| arr.into_raw()) + Ok(utils::rust_vec_to_java( + env, + txids.into(), + "[B", + |env, txid| utils::rust_bytes_to_java(env, txid.as_ref()), + |env| env.new_byte_array(32), + )? + .into_raw()) }); unwrap_exc_or(&mut env, res, ptr::null_mut()) } @@ -1699,10 +1706,10 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_listTrans .iter() .map(|(taddr, _)| { let taddr = match taddr { - TransparentAddress::PublicKey(data) => { + TransparentAddress::PublicKeyHash(data) => { ZcashAddress::from_transparent_p2pkh(zcash_network, *data) } - TransparentAddress::Script(data) => { + TransparentAddress::ScriptHash(data) => { ZcashAddress::from_transparent_p2sh(zcash_network, *data) } }; diff --git a/backend-lib/src/main/rust/utils.rs b/backend-lib/src/main/rust/utils.rs index ef1ed9aa..e1867926 100644 --- a/backend-lib/src/main/rust/utils.rs +++ b/backend-lib/src/main/rust/utils.rs @@ -42,10 +42,7 @@ pub(crate) fn java_nullable_string_to_rust(env: &mut JNIEnv, jstring: &JString) (!jstring.is_null()).then(|| java_string_to_rust(env, jstring)) } -pub(crate) fn rust_bytes_to_java<'a>( - env: &JNIEnv<'a>, - data: &[u8], -) -> Result, failure::Error> { +pub(crate) fn rust_bytes_to_java<'a>(env: &JNIEnv<'a>, data: &[u8]) -> JNIResult> { // SAFETY: jbyte (i8) has the same size and alignment as u8, and a well-defined // twos-complement representation with no "trap representations". let buf = unsafe { slice::from_raw_parts(data.as_ptr().cast(), data.len()) }; 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 53872421..e5f80350 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 @@ -95,10 +95,10 @@ internal class FakeRustBackend( TODO("Not yet implemented") } - override suspend fun createProposedTransaction( + override suspend fun createProposedTransactions( proposal: ProposalUnsafe, unifiedSpendingKey: ByteArray - ): ByteArray { + ): List { TODO("Not yet implemented") } 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 69a7c0b6..42861be8 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 @@ -39,10 +39,10 @@ internal interface TypesafeBackend { transparentReceiver: String? = null ): Proposal? - suspend fun createProposedTransaction( + suspend fun createProposedTransactions( proposal: Proposal, usk: UnifiedSpendingKey - ): FirstClassByteArray + ): List suspend fun getCurrentAddress(account: Account): String 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 5d66a329..1eb811f3 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 @@ -68,16 +68,14 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke ) } - override suspend fun createProposedTransaction( + override suspend fun createProposedTransactions( proposal: Proposal, usk: UnifiedSpendingKey - ): FirstClassByteArray = - FirstClassByteArray( - backend.createProposedTransaction( - proposal.toUnsafe(), - usk.copyBytes() - ) - ) + ): List = + backend.createProposedTransactions( + proposal.toUnsafe(), + usk.copyBytes() + ).map { FirstClassByteArray(it) } override suspend fun getCurrentAddress(account: Account): String { return backend.getCurrentAddress(account.value) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt index 671c089a..807b442b 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt @@ -124,11 +124,11 @@ internal class TransactionEncoderImpl( } @Suppress("TooGenericExceptionCaught") - val transactionId = + val transactionIds = try { saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory) Twig.debug { "params exist! attempting to send..." } - backend.createProposedTransaction(proposal, usk) + backend.createProposedTransactions(proposal, usk) } catch (t: Throwable) { Twig.debug(t) { "Caught exception while creating transaction." } throw t @@ -136,11 +136,13 @@ internal class TransactionEncoderImpl( Twig.debug { "result of createProposedTransactions: $result" } } - val tx = - repository.findEncodedTransactionByTxId(transactionId) - ?: throw TransactionEncoderException.TransactionNotFoundException(transactionId) + val txs = + transactionIds.map { transactionId -> + repository.findEncodedTransactionByTxId(transactionId) + ?: throw TransactionEncoderException.TransactionNotFoundException(transactionId) + } - return listOf(tx) + return txs } /** @@ -223,7 +225,9 @@ internal class TransactionEncoderImpl( amount.value, memo ) - backend.createProposedTransaction(proposal, usk) + val transactionIds = backend.createProposedTransactions(proposal, usk) + assert(transactionIds.size == 1) + transactionIds[0] } catch (t: Throwable) { Twig.debug(t) { "Caught exception while creating transaction." } throw t @@ -244,7 +248,9 @@ internal class TransactionEncoderImpl( val proposal = backend.proposeShielding(usk.account, 100000, memo) ?: throw SdkException("Insufficient balance (have 0, need 100000 including fee)", null) - backend.createProposedTransaction(proposal, usk) + val transactionIds = backend.createProposedTransactions(proposal, usk) + assert(transactionIds.size == 1) + transactionIds[0] } catch (t: Throwable) { // TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee) // then consider custom error that says no UTXOs existed to shield diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt index fc068f9b..d28361b1 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt @@ -20,7 +20,7 @@ class Proposal( // Check for type errors eagerly, to ensure that the caller won't // encounter these errors later. - typed.feeRequired() + typed.totalFeeRequired() return typed } @@ -34,9 +34,9 @@ class Proposal( } /** - * Returns the fee required by this proposal. + * Returns the total fee required by this proposal for its transactions. */ - fun feeRequired(): Zatoshi { - return Zatoshi(inner.feeRequired()) + fun totalFeeRequired(): Zatoshi { + return Zatoshi(inner.totalFeeRequired()) } } From 8e4c83627bf510cc433d93a220b0840a77c17448 Mon Sep 17 00:00:00 2001 From: Honza Date: Wed, 6 Mar 2024 15:38:20 +0100 Subject: [PATCH 07/12] Improve `createProposedTransactions` --- .../cash/z/ecc/android/sdk/SdkSynchronizer.kt | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) 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 af0183ef..1da81a75 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 @@ -68,9 +68,9 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -408,6 +408,7 @@ class SdkSynchronizer private constructor( lastScanTime = now SYNCED } + is Stopped -> STOPPED is Disconnected -> DISCONNECTED is Syncing, Initialized -> SYNCING @@ -579,25 +580,28 @@ class SdkSynchronizer private constructor( proposal: Proposal, usk: UnifiedSpendingKey ): Flow { - val transactions = txManager.createProposedTransactions(proposal, usk) - - return flow { - var submitFailed = false - for (transaction in transactions) { - if (submitFailed) { - emit(TransactionSubmitResult.NotAttempted(transaction.txId)) + // Internally, this logic submits and checks every incoming transaction, and once [Failure] or + // [NotAttempted] submission result occurs, it returns [NotAttempted] for the rest of them + var anySubmissionFailed = false + return txManager.createProposedTransactions(proposal, usk) + .asFlow() + .map { transaction -> + if (anySubmissionFailed) { + TransactionSubmitResult.NotAttempted(transaction.txId) } else { - val submitResult = txManager.submit(transaction) - when (submitResult) { - is TransactionSubmitResult.Success -> {} - else -> { - submitFailed = true + val submission = txManager.submit(transaction) + when (submission) { + is TransactionSubmitResult.Success -> { + // Expected state + } + is TransactionSubmitResult.Failure, + is TransactionSubmitResult.NotAttempted -> { + anySubmissionFailed = true } } - emit(submitResult) + submission } } - } } @Deprecated( From 8bd009c98a008c1e867901d6eb30c477d84f5d7b Mon Sep 17 00:00:00 2001 From: Honza Date: Wed, 6 Mar 2024 15:44:29 +0100 Subject: [PATCH 08/12] Improve logging inside `txManager.submit()` --- .../internal/transaction/OutboundTransactionManagerImpl.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt index 45fd421a..026365d0 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt @@ -66,10 +66,11 @@ internal class OutboundTransactionManagerImpl( return when (val response = service.submitTransaction(encodedTransaction.raw.byteArray)) { is Response.Success -> { if (response.result.code == 0) { - Twig.debug { "SUCCESS: submit transaction completed" } + Twig.info { "SUCCESS: submit transaction completed for:" + + " ${encodedTransaction.txId.byteArray.toHexReversed()}" } TransactionSubmitResult.Success(encodedTransaction.txId) } else { - Twig.debug { + Twig.error { "FAILURE! submit transaction ${encodedTransaction.txId.byteArray.toHexReversed()} " + "completed with response: ${response.result.code}: ${response.result.message}" } @@ -83,7 +84,7 @@ internal class OutboundTransactionManagerImpl( } is Response.Failure -> { - Twig.debug { + Twig.error { "FAILURE! submit transaction failed with gRPC response: ${response.code}: ${ response.description }" From c4b45c2c2f2faeb994c2e2a66a120d6a253c6c52 Mon Sep 17 00:00:00 2001 From: Honza Date: Wed, 6 Mar 2024 16:35:03 +0100 Subject: [PATCH 09/12] Eliminate the suppressions --- .../OutboundTransactionManagerImpl.kt | 6 +- .../transaction/TransactionEncoderImpl.kt | 72 +++++++++---------- 2 files changed, 38 insertions(+), 40 deletions(-) diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt index 026365d0..77404e56 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/OutboundTransactionManagerImpl.kt @@ -66,8 +66,10 @@ internal class OutboundTransactionManagerImpl( return when (val response = service.submitTransaction(encodedTransaction.raw.byteArray)) { is Response.Success -> { if (response.result.code == 0) { - Twig.info { "SUCCESS: submit transaction completed for:" + - " ${encodedTransaction.txId.byteArray.toHexReversed()}" } + Twig.info { + "SUCCESS: submit transaction completed for:" + + " ${encodedTransaction.txId.byteArray.toHexReversed()}" + } TransactionSubmitResult.Success(encodedTransaction.txId) } else { Twig.error { diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt index 807b442b..33efc2b1 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/internal/transaction/TransactionEncoderImpl.kt @@ -79,20 +79,18 @@ internal class TransactionEncoderImpl( " ${recipient.masked()} with memo: ${memo?.decodeToString()}" } - @Suppress("TooGenericExceptionCaught") - return try { + return runCatching { backend.proposeTransfer( account, recipient, amount.value, memo ) - } catch (t: Throwable) { - Twig.debug(t) { "Caught exception while creating proposal." } - throw t - }.also { result -> + }.onFailure { + Twig.error(it) { "Caught exception while creating proposal." } + }.onSuccess { result -> Twig.debug { "result of proposeTransfer: $result" } - } + }.getOrThrow() } override suspend fun proposeShielding( @@ -101,18 +99,16 @@ internal class TransactionEncoderImpl( memo: ByteArray?, transparentReceiver: String? ): Proposal? { - @Suppress("TooGenericExceptionCaught") - return try { + return runCatching { backend.proposeShielding(account, shieldingThreshold.value, memo, transparentReceiver) - } catch (t: Throwable) { + }.onFailure { // TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee) // then consider custom error that says no UTXOs existed to shield // TODO [#680]: https://github.com/zcash/zcash-android-wallet-sdk/issues/680 - Twig.debug(t) { "proposeShielding failed" } - throw t - }.also { result -> + Twig.error(it) { "proposeShielding failed" } + }.onSuccess { result -> Twig.debug { "result of proposeShielding: $result" } - } + }.getOrThrow() } override suspend fun createProposedTransactions( @@ -123,18 +119,16 @@ internal class TransactionEncoderImpl( "creating transactions for proposal" } - @Suppress("TooGenericExceptionCaught") val transactionIds = - try { + runCatching { saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory) Twig.debug { "params exist! attempting to send..." } backend.createProposedTransactions(proposal, usk) - } catch (t: Throwable) { - Twig.debug(t) { "Caught exception while creating transaction." } - throw t - }.also { result -> + }.onFailure { + Twig.error(it) { "Caught exception while creating transaction." } + }.onSuccess { result -> Twig.debug { "result of createProposedTransactions: $result" } - } + }.getOrThrow() val txs = transactionIds.map { transactionId -> @@ -214,8 +208,7 @@ internal class TransactionEncoderImpl( " ${toAddress.masked()} with memo: ${memo?.decodeToString()}" } - @Suppress("TooGenericExceptionCaught") - return try { + return runCatching { saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory) Twig.debug { "params exist! attempting to send..." } val proposal = @@ -228,37 +221,40 @@ internal class TransactionEncoderImpl( val transactionIds = backend.createProposedTransactions(proposal, usk) assert(transactionIds.size == 1) transactionIds[0] - } catch (t: Throwable) { - Twig.debug(t) { "Caught exception while creating transaction." } - throw t - }.also { result -> + }.onFailure { + Twig.error(it) { "Caught exception while creating transaction." } + }.onSuccess { result -> Twig.debug { "result of sendToAddress: $result" } - } + }.getOrThrow() } - @Suppress("MagicNumber") private suspend fun createShieldingSpend( usk: UnifiedSpendingKey, memo: ByteArray? = byteArrayOf() ): FirstClassByteArray { - @Suppress("TooGenericExceptionCaught") - return try { + return runCatching { saplingParamTool.ensureParams(saplingParamTool.properties.paramsDirectory) Twig.debug { "params exist! attempting to shield..." } val proposal = - backend.proposeShielding(usk.account, 100000, memo) - ?: throw SdkException("Insufficient balance (have 0, need 100000 including fee)", null) + backend.proposeShielding(usk.account, SHIELDING_THRESHOLD, memo) + ?: throw SdkException( + "Insufficient balance (have 0, need $SHIELDING_THRESHOLD including fee)", + null + ) val transactionIds = backend.createProposedTransactions(proposal, usk) assert(transactionIds.size == 1) transactionIds[0] - } catch (t: Throwable) { + }.onFailure { // TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee) // then consider custom error that says no UTXOs existed to shield // TODO [#680]: https://github.com/zcash/zcash-android-wallet-sdk/issues/680 - Twig.debug(t) { "Shield failed" } - throw t - }.also { result -> + Twig.error(it) { "Shield failed" } + }.onSuccess { result -> Twig.debug { "result of shieldToAddress: $result" } - } + }.getOrThrow() + } + + companion object { + private const val SHIELDING_THRESHOLD = 100000L } } From c8d0be84045c5208b0ca415e95bf60c9fd08bf62 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 7 Mar 2024 19:49:18 +0000 Subject: [PATCH 10/12] Add `Proposal.transactionCount` --- .../ecc/android/sdk/internal/model/ProposalUnsafe.kt | 12 ++++++++++++ .../java/cash/z/ecc/android/sdk/model/Proposal.kt | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ProposalUnsafe.kt b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ProposalUnsafe.kt index 5ce80d3f..87d2d0c6 100644 --- a/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ProposalUnsafe.kt +++ b/backend-lib/src/main/java/cash/z/ecc/android/sdk/internal/model/ProposalUnsafe.kt @@ -37,6 +37,18 @@ class ProposalUnsafe( return inner.toByteArray() } + /** + * Returns the number of transactions that this proposal will create. + * + * This is equal to the number of `TransactionSubmitResult`s that will be returned + * from `Synchronizer.createProposedTransactions`. + * + * Proposals always create at least one transaction. + */ + fun transactionCount(): Int { + return inner.stepsCount + } + /** * Returns the total fee required by this proposal for its transactions. */ diff --git a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt index d28361b1..0906dafb 100644 --- a/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt +++ b/sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Proposal.kt @@ -33,6 +33,18 @@ class Proposal( return inner } + /** + * Returns the number of transactions that this proposal will create. + * + * This is equal to the number of `TransactionSubmitResult`s that will be returned + * from `Synchronizer.createProposedTransactions`. + * + * Proposals always create at least one transaction. + */ + fun transactionCount(): Int { + return inner.transactionCount() + } + /** * Returns the total fee required by this proposal for its transactions. */ From f69c415e92740b89c0fbd1c407277838d4a3ac14 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 7 Mar 2024 20:41:58 +0000 Subject: [PATCH 11/12] cargo update --- backend-lib/Cargo.lock | 275 ++++++++++++++++++++--------------------- 1 file changed, 137 insertions(+), 138 deletions(-) diff --git a/backend-lib/Cargo.lock b/backend-lib/Cargo.lock index fbb678cd..21acf33f 100644 --- a/backend-lib/Cargo.lock +++ b/backend-lib/Cargo.lock @@ -29,9 +29,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", @@ -67,9 +67,9 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "arrayref" @@ -163,12 +163,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.4.2" @@ -243,9 +237,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "byteorder" @@ -270,12 +264,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cesu8" @@ -351,9 +342,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" dependencies = [ "crossbeam-utils", ] @@ -448,7 +439,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -462,9 +453,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "equihash" @@ -709,9 +700,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.4" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -748,9 +739,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.1" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", "hashbrown", @@ -804,9 +795,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -845,9 +836,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" @@ -880,9 +871,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "log-panics" @@ -926,9 +917,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] @@ -986,20 +977,25 @@ dependencies = [ ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -1031,9 +1027,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "orchard" @@ -1154,9 +1150,9 @@ checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "poly1305" @@ -1188,7 +1184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -1227,7 +1223,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.48", + "syn 2.0.52", "tempfile", "which", ] @@ -1242,7 +1238,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -1301,9 +1297,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" dependencies = [ "either", "rayon-core", @@ -1350,15 +1346,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "regex" version = "1.10.3" @@ -1373,9 +1360,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -1418,7 +1405,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" dependencies = [ - "bitflags 2.4.2", + "bitflags", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -1435,11 +1422,11 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.30" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.2", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1539,22 +1526,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -1583,7 +1570,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbf20c7a2747d9083092e3a3eeb9a7ed75577ae364896bebbc5e0bdcd4e97735" dependencies = [ - "bitflags 2.4.2", + "bitflags", "either", "incrementalmerkletree", "tracing", @@ -1626,9 +1613,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -1655,42 +1642,41 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -1698,12 +1684,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -1718,10 +1705,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" dependencies = [ + "num-conv", "time-core", ] @@ -1750,7 +1738,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -1772,7 +1760,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -1836,9 +1824,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -1891,9 +1879,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -1907,9 +1895,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1917,24 +1905,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1942,28 +1930,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -2027,7 +2015,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -2047,17 +2035,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -2068,9 +2056,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -2080,9 +2068,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -2092,9 +2080,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -2104,9 +2092,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -2116,9 +2104,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -2128,9 +2116,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -2140,9 +2128,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "wyz" @@ -2190,14 +2178,15 @@ dependencies = [ [[package]] name = "zcash_address" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bce173f1d9ed4f806e310bc3a873301531e7a6dc209928584d6404e3f8228ef4" +checksum = "827c17a1f7e3a69f0d44e991ff610c7a842228afdc9dc2325ffdd1a67fee01e9" dependencies = [ "bech32", "bs58", "f4jumble", "zcash_encoding", + "zcash_protocol", ] [[package]] @@ -2283,9 +2272,9 @@ dependencies = [ [[package]] name = "zcash_keys" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48390513c05a4090533e2a1aca486f0fb6b684493b8aefedda6ee01742b3f8f6" +checksum = "4f22d3407fdd6992b49f037f23862ab376be6013be6f2d0bc85948a635edc1f5" dependencies = [ "bech32", "bls12_381", @@ -2381,6 +2370,16 @@ dependencies = [ "zcash_primitives", ] +[[package]] +name = "zcash_protocol" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c651f9f95d319cd1e5211178108dcd3aa73063806d09af54b799e1a329a575bf" +dependencies = [ + "document-features", + "memuse", +] + [[package]] name = "zcash_spec" version = "0.1.0" @@ -2407,7 +2406,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -2427,7 +2426,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] From 34bb6557bf62755aa1c81aa00e13e37c1be924e7 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 8 Mar 2024 20:13:54 +0000 Subject: [PATCH 12/12] SDK v2.0.7 --- CHANGELOG.md | 2 ++ gradle.properties | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3faa983f..875341da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.0.7] - 2024-03-08 + ### Fixed - `Synchronizer.sendToAddress` and `Synchronizer.shieldFunds` now throw an exception if the created transaction successfully reaches `lightwalletd` but diff --git a/gradle.properties b/gradle.properties index d2ce73bd..5d8f6f47 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,7 +26,7 @@ ZCASH_ASCII_GPG_KEY= # Configures whether release is an unstable snapshot, therefore published to the snapshot repository. IS_SNAPSHOT=true -LIBRARY_VERSION=2.0.6 +LIBRARY_VERSION=2.0.7 # Kotlin compiler warnings can be considered errors, failing the build. ZCASH_IS_TREAT_WARNINGS_AS_ERRORS=true