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.

This commit only alters the API to support the above; the functional
changes require modifying the FFI and Rust backend, which will happen
in a separate commit.
This commit is contained in:
Jack Grigg 2024-02-28 16:23:12 +00:00
parent 136a3116b9
commit 84ac6252fe
10 changed files with 108 additions and 58 deletions

View File

@ -619,8 +619,13 @@ actor ZcashRustBackend: ZcashRustBackendWelding {
func proposeShielding(
account: Int32,
memo: MemoBytes?,
shieldingThreshold: Zatoshi
) async throws -> FfiProposal {
shieldingThreshold: Zatoshi,
transparentReceiver: String?
) async throws -> FfiProposal? {
if transparentReceiver != nil {
throw ZcashError.rustScanBlocks("TODO: Implement transparentReceiver support in FFI")
}
globalDBLock.lock()
let proposal = zcashlc_propose_shielding(
dbData.0,

View File

@ -212,14 +212,22 @@ protocol ZcashRustBackendWelding {
/// that can then be authorized and made ready for submission to the network with
/// `createProposedTransaction`.
///
/// Returns the proposal, or `nil` if the transparent balance that would be shielded
/// is zero or below `shieldingThreshold`.
///
/// - parameter account: index of the given account
/// - Parameter memo: the `Memo` for this transaction
/// - Parameter transparentReceiver: a specific transparent receiver within the account
/// that should be the source of transparent funds. Default is `nil` which
/// will select whichever of the account's transparent receivers has funds
/// to shield.
/// - Throws: `rustShieldFunds` if rust layer returns error.
func proposeShielding(
account: Int32,
memo: MemoBytes?,
shieldingThreshold: Zatoshi
) async throws -> FfiProposal
shieldingThreshold: Zatoshi,
transparentReceiver: String?
) async throws -> FfiProposal?
/// Creates a transaction from the given proposal.
/// - Parameter proposal: the transaction proposal.

View File

@ -176,14 +176,22 @@ public protocol Synchronizer: AnyObject {
/// - Parameter accountIndex: the account for which to shield funds.
/// - Parameter shieldingThreshold: the minimum transparent balance required before a proposal will be created.
/// - Parameter memo: an optional memo to include as part of the proposal's transactions.
/// - Parameter transparentReceiver: a specific transparent receiver within the account
/// that should be the source of transparent funds. Default is `nil` which
/// will select whichever of the account's transparent receivers has funds
/// to shield.
///
/// Returns the proposal, or `nil` if the transparent balance that would be shielded
/// is zero or below `shieldingThreshold`.
///
/// If `prepare()` hasn't already been called since creation of the synchronizer instance or since the last wipe then this method throws
/// `SynchronizerErrors.notPrepared`.
func proposeShielding(
accountIndex: Int,
shieldingThreshold: Zatoshi,
memo: Memo
) async throws -> Proposal
memo: Memo,
transparentReceiver: TransparentAddress?
) async throws -> Proposal?
/// Creates the transactions in the given proposal.
///

View File

@ -86,10 +86,16 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer {
accountIndex: Int,
shieldingThreshold: Zatoshi,
memo: Memo,
completion: @escaping (Result<Proposal, Error>) -> Void
transparentReceiver: TransparentAddress? = nil,
completion: @escaping (Result<Proposal?, Error>) -> Void
) {
AsyncToClosureGateway.executeThrowingAction(completion) {
try await self.synchronizer.proposeShielding(accountIndex: accountIndex, shieldingThreshold: shieldingThreshold, memo: memo)
try await self.synchronizer.proposeShielding(
accountIndex: accountIndex,
shieldingThreshold: shieldingThreshold,
memo: memo,
transparentReceiver: transparentReceiver
)
}
}

View File

@ -83,10 +83,16 @@ extension CombineSDKSynchronizer: CombineSynchronizer {
public func proposeShielding(
accountIndex: Int,
shieldingThreshold: Zatoshi,
memo: Memo
) -> SinglePublisher<Proposal, Error> {
memo: Memo,
transparentReceiver: TransparentAddress? = nil
) -> SinglePublisher<Proposal?, Error> {
AsyncToCombineGateway.executeThrowingAction() {
try await self.synchronizer.proposeShielding(accountIndex: accountIndex, shieldingThreshold: shieldingThreshold, memo: memo)
try await self.synchronizer.proposeShielding(
accountIndex: accountIndex,
shieldingThreshold: shieldingThreshold,
memo: memo,
transparentReceiver: transparentReceiver
)
}
}

View File

@ -282,13 +282,19 @@ public class SDKSynchronizer: Synchronizer {
return proposal
}
public func proposeShielding(accountIndex: Int, shieldingThreshold: Zatoshi, memo: Memo) async throws -> Proposal {
public func proposeShielding(
accountIndex: Int,
shieldingThreshold: Zatoshi,
memo: Memo,
transparentReceiver: TransparentAddress? = nil
) async throws -> Proposal? {
try throwIfUnprepared()
let proposal = try await transactionEncoder.proposeShielding(
accountIndex: accountIndex,
shieldingThreshold: shieldingThreshold,
memoBytes: memo.asMemoBytes()
memoBytes: memo.asMemoBytes(),
transparentReceiver: transparentReceiver?.stringEncoded
)
return proposal
@ -384,11 +390,12 @@ public class SDKSynchronizer: Synchronizer {
throw ZcashError.synchronizerShieldFundsInsuficientTransparentFunds
}
let proposal = try await transactionEncoder.proposeShielding(
guard let proposal = try await transactionEncoder.proposeShielding(
accountIndex: Int(spendingKey.account),
shieldingThreshold: shieldingThreshold,
memoBytes: memo.asMemoBytes()
)
memoBytes: memo.asMemoBytes(),
transparentReceiver: nil
) else { throw ZcashError.synchronizerShieldFundsInsuficientTransparentFunds }
let transactions = try await transactionEncoder.createProposedTransactions(
proposal: proposal,

View File

@ -40,14 +40,22 @@ protocol TransactionEncoder {
/// - Parameter accountIndex: the account for which to shield funds.
/// - Parameter shieldingThreshold: the minimum transparent balance required before a proposal will be created.
/// - Parameter memoBytes: an optional memo to include as part of the proposal's transactions.
/// - Parameter transparentReceiver: a specific transparent receiver within the account
/// that should be the source of transparent funds. Default is `nil` which
/// will select whichever of the account's transparent receivers has funds
/// to shield.
///
/// Returns the proposal, or `nil` if the transparent balance that would be shielded
/// is zero or below `shieldingThreshold`.
///
/// If `prepare()` hasn't already been called since creation of the synchronizer instance or since the last wipe then this method throws
/// `SynchronizerErrors.notPrepared`.
func proposeShielding(
accountIndex: Int,
shieldingThreshold: Zatoshi,
memoBytes: MemoBytes?
) async throws -> Proposal
memoBytes: MemoBytes?,
transparentReceiver: String?
) async throws -> Proposal?
/// Creates the transactions in the given proposal.
///

View File

@ -74,13 +74,15 @@ class WalletTransactionEncoder: TransactionEncoder {
func proposeShielding(
accountIndex: Int,
shieldingThreshold: Zatoshi,
memoBytes: MemoBytes?
) async throws -> Proposal {
let proposal = try await rustBackend.proposeShielding(
memoBytes: MemoBytes?,
transparentReceiver: String? = nil
) async throws -> Proposal? {
guard let proposal = try await rustBackend.proposeShielding(
account: Int32(accountIndex),
memo: memoBytes,
shieldingThreshold: shieldingThreshold
)
shieldingThreshold: shieldingThreshold,
transparentReceiver: transparentReceiver
) else { return nil }
return Proposal(inner: proposal)
}

View File

@ -1423,25 +1423,25 @@ class SynchronizerMock: Synchronizer {
// MARK: - proposeShielding
var proposeShieldingAccountIndexShieldingThresholdMemoThrowableError: Error?
var proposeShieldingAccountIndexShieldingThresholdMemoCallsCount = 0
var proposeShieldingAccountIndexShieldingThresholdMemoCalled: Bool {
return proposeShieldingAccountIndexShieldingThresholdMemoCallsCount > 0
var proposeShieldingAccountIndexShieldingThresholdMemoTransparentReceiverThrowableError: Error?
var proposeShieldingAccountIndexShieldingThresholdMemoTransparentReceiverCallsCount = 0
var proposeShieldingAccountIndexShieldingThresholdMemoTransparentReceiverCalled: Bool {
return proposeShieldingAccountIndexShieldingThresholdMemoTransparentReceiverCallsCount > 0
}
var proposeShieldingAccountIndexShieldingThresholdMemoReceivedArguments: (accountIndex: Int, shieldingThreshold: Zatoshi, memo: Memo)?
var proposeShieldingAccountIndexShieldingThresholdMemoReturnValue: Proposal!
var proposeShieldingAccountIndexShieldingThresholdMemoClosure: ((Int, Zatoshi, Memo) async throws -> Proposal)?
var proposeShieldingAccountIndexShieldingThresholdMemoTransparentReceiverReceivedArguments: (accountIndex: Int, shieldingThreshold: Zatoshi, memo: Memo, transparentReceiver: TransparentAddress?)?
var proposeShieldingAccountIndexShieldingThresholdMemoTransparentReceiverReturnValue: Proposal?
var proposeShieldingAccountIndexShieldingThresholdMemoTransparentReceiverClosure: ((Int, Zatoshi, Memo, TransparentAddress?) async throws -> Proposal?)?
func proposeShielding(accountIndex: Int, shieldingThreshold: Zatoshi, memo: Memo) async throws -> Proposal {
if let error = proposeShieldingAccountIndexShieldingThresholdMemoThrowableError {
func proposeShielding(accountIndex: Int, shieldingThreshold: Zatoshi, memo: Memo, transparentReceiver: TransparentAddress?) async throws -> Proposal? {
if let error = proposeShieldingAccountIndexShieldingThresholdMemoTransparentReceiverThrowableError {
throw error
}
proposeShieldingAccountIndexShieldingThresholdMemoCallsCount += 1
proposeShieldingAccountIndexShieldingThresholdMemoReceivedArguments = (accountIndex: accountIndex, shieldingThreshold: shieldingThreshold, memo: memo)
if let closure = proposeShieldingAccountIndexShieldingThresholdMemoClosure {
return try await closure(accountIndex, shieldingThreshold, memo)
proposeShieldingAccountIndexShieldingThresholdMemoTransparentReceiverCallsCount += 1
proposeShieldingAccountIndexShieldingThresholdMemoTransparentReceiverReceivedArguments = (accountIndex: accountIndex, shieldingThreshold: shieldingThreshold, memo: memo, transparentReceiver: transparentReceiver)
if let closure = proposeShieldingAccountIndexShieldingThresholdMemoTransparentReceiverClosure {
return try await closure(accountIndex, shieldingThreshold, memo, transparentReceiver)
} else {
return proposeShieldingAccountIndexShieldingThresholdMemoReturnValue
return proposeShieldingAccountIndexShieldingThresholdMemoTransparentReceiverReturnValue
}
}
@ -2828,34 +2828,34 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding {
// MARK: - proposeShielding
var proposeShieldingAccountMemoShieldingThresholdThrowableError: Error?
func setProposeShieldingAccountMemoShieldingThresholdThrowableError(_ param: Error?) async {
proposeShieldingAccountMemoShieldingThresholdThrowableError = param
var proposeShieldingAccountMemoShieldingThresholdTransparentReceiverThrowableError: Error?
func setProposeShieldingAccountMemoShieldingThresholdTransparentReceiverThrowableError(_ param: Error?) async {
proposeShieldingAccountMemoShieldingThresholdTransparentReceiverThrowableError = param
}
var proposeShieldingAccountMemoShieldingThresholdCallsCount = 0
var proposeShieldingAccountMemoShieldingThresholdCalled: Bool {
return proposeShieldingAccountMemoShieldingThresholdCallsCount > 0
var proposeShieldingAccountMemoShieldingThresholdTransparentReceiverCallsCount = 0
var proposeShieldingAccountMemoShieldingThresholdTransparentReceiverCalled: Bool {
return proposeShieldingAccountMemoShieldingThresholdTransparentReceiverCallsCount > 0
}
var proposeShieldingAccountMemoShieldingThresholdReceivedArguments: (account: Int32, memo: MemoBytes?, shieldingThreshold: Zatoshi)?
var proposeShieldingAccountMemoShieldingThresholdReturnValue: FfiProposal!
func setProposeShieldingAccountMemoShieldingThresholdReturnValue(_ param: FfiProposal) async {
proposeShieldingAccountMemoShieldingThresholdReturnValue = param
var proposeShieldingAccountMemoShieldingThresholdTransparentReceiverReceivedArguments: (account: Int32, memo: MemoBytes?, shieldingThreshold: Zatoshi, transparentReceiver: String?)?
var proposeShieldingAccountMemoShieldingThresholdTransparentReceiverReturnValue: FfiProposal?
func setProposeShieldingAccountMemoShieldingThresholdTransparentReceiverReturnValue(_ param: FfiProposal?) async {
proposeShieldingAccountMemoShieldingThresholdTransparentReceiverReturnValue = param
}
var proposeShieldingAccountMemoShieldingThresholdClosure: ((Int32, MemoBytes?, Zatoshi) async throws -> FfiProposal)?
func setProposeShieldingAccountMemoShieldingThresholdClosure(_ param: ((Int32, MemoBytes?, Zatoshi) async throws -> FfiProposal)?) async {
proposeShieldingAccountMemoShieldingThresholdClosure = param
var proposeShieldingAccountMemoShieldingThresholdTransparentReceiverClosure: ((Int32, MemoBytes?, Zatoshi, String?) async throws -> FfiProposal?)?
func setProposeShieldingAccountMemoShieldingThresholdTransparentReceiverClosure(_ param: ((Int32, MemoBytes?, Zatoshi, String?) async throws -> FfiProposal?)?) async {
proposeShieldingAccountMemoShieldingThresholdTransparentReceiverClosure = param
}
func proposeShielding(account: Int32, memo: MemoBytes?, shieldingThreshold: Zatoshi) async throws -> FfiProposal {
if let error = proposeShieldingAccountMemoShieldingThresholdThrowableError {
func proposeShielding(account: Int32, memo: MemoBytes?, shieldingThreshold: Zatoshi, transparentReceiver: String?) async throws -> FfiProposal? {
if let error = proposeShieldingAccountMemoShieldingThresholdTransparentReceiverThrowableError {
throw error
}
proposeShieldingAccountMemoShieldingThresholdCallsCount += 1
proposeShieldingAccountMemoShieldingThresholdReceivedArguments = (account: account, memo: memo, shieldingThreshold: shieldingThreshold)
if let closure = proposeShieldingAccountMemoShieldingThresholdClosure {
return try await closure(account, memo, shieldingThreshold)
proposeShieldingAccountMemoShieldingThresholdTransparentReceiverCallsCount += 1
proposeShieldingAccountMemoShieldingThresholdTransparentReceiverReceivedArguments = (account: account, memo: memo, shieldingThreshold: shieldingThreshold, transparentReceiver: transparentReceiver)
if let closure = proposeShieldingAccountMemoShieldingThresholdTransparentReceiverClosure {
return try await closure(account, memo, shieldingThreshold, transparentReceiver)
} else {
return proposeShieldingAccountMemoShieldingThresholdReturnValue
return proposeShieldingAccountMemoShieldingThresholdTransparentReceiverReturnValue
}
}

View File

@ -84,7 +84,7 @@ class RustBackendMockHelper {
await rustBackendMock.setGetNearestRewindHeightHeightReturnValue(-1)
await rustBackendMock.setPutUnspentTransparentOutputTxidIndexScriptValueHeightClosure() { _, _, _, _, _ in }
await rustBackendMock.setProposeTransferAccountToValueMemoThrowableError(ZcashError.rustCreateToAddress("mocked error"))
await rustBackendMock.setProposeShieldingAccountMemoShieldingThresholdThrowableError(ZcashError.rustShieldFunds("mocked error"))
await rustBackendMock.setProposeShieldingAccountMemoShieldingThresholdTransparentReceiverThrowableError(ZcashError.rustShieldFunds("mocked error"))
await rustBackendMock.setCreateProposedTransactionProposalUskThrowableError(ZcashError.rustCreateToAddress("mocked error"))
await rustBackendMock.setDecryptAndStoreTransactionTxBytesMinedHeightThrowableError(ZcashError.rustDecryptAndStoreTransaction("mock fail"))