Skip to content

Commit

Permalink
Add Synchronizer side of PCZT logic
Browse files Browse the repository at this point in the history
  • Loading branch information
HonzaR committed Dec 12, 2024
1 parent 11e231e commit ca032f4
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 1 deletion.
18 changes: 18 additions & 0 deletions sdk-lib/src/main/java/cash/z/ecc/android/sdk/SdkSynchronizer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import cash.z.ecc.android.sdk.model.FastestServersResult
import cash.z.ecc.android.sdk.model.FetchFiatCurrencyResult
import cash.z.ecc.android.sdk.model.FirstClassByteArray
import cash.z.ecc.android.sdk.model.ObserveFiatCurrencyResult
import cash.z.ecc.android.sdk.model.Pczt
import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionOutput
Expand Down Expand Up @@ -93,6 +94,7 @@ import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
Expand Down Expand Up @@ -857,6 +859,22 @@ class SdkSynchronizer private constructor(
}
}

override suspend fun createPcztFromProposal(
accountUuid: AccountUuid,
proposal: Proposal
) = txManager.createPcztFromProposal(accountUuid, proposal)

override suspend fun addProofsToPczt(pczt: Pczt) = txManager.addProofsToPczt(pczt)

override suspend fun createTransactionFromPczt(
pcztWithProofs: Pczt,
pcztWithSignatures: Pczt
): Flow<TransactionSubmitResult> {
// Internally, this logic submits and checks the newly stored and encoded transaction
return flowOf(txManager.extractAndStoreTxFromPczt(pcztWithProofs, pcztWithSignatures))
.map { transaction -> txManager.submit(transaction) }
}

override suspend fun refreshUtxos(
account: Account,
since: BlockHeight
Expand Down
50 changes: 50 additions & 0 deletions sdk-lib/src/main/java/cash/z/ecc/android/sdk/Synchronizer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import cash.z.ecc.android.sdk.WalletInitMode.NewWallet
import cash.z.ecc.android.sdk.WalletInitMode.RestoreWallet
import cash.z.ecc.android.sdk.block.processor.CompactBlockProcessor
import cash.z.ecc.android.sdk.exception.InitializeException
import cash.z.ecc.android.sdk.exception.PcztException
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.internal.FastestServerFetcher
import cash.z.ecc.android.sdk.internal.Files
Expand All @@ -22,6 +23,7 @@ import cash.z.ecc.android.sdk.model.AccountUuid
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.FastestServersResult
import cash.z.ecc.android.sdk.model.ObserveFiatCurrencyResult
import cash.z.ecc.android.sdk.model.Pczt
import cash.z.ecc.android.sdk.model.PercentDecimal
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionOutput
Expand Down Expand Up @@ -301,6 +303,54 @@ interface Synchronizer {
usk: UnifiedSpendingKey
): Flow<TransactionSubmitResult>

/**
* Creates a partially-created (unsigned without proofs) transaction from the given proposal.
*
* Do not call this multiple times in parallel, or you will generate PCZT instances that, if
* finalized, would double-spend the same notes.
*
* @param accountUuid The account for which the proposal was created.
* @param proposal The proposal for which to create the transaction.
*
* @return The partially created transaction in [Pczt] format.
*
* @throws PcztException.CreatePcztFromProposalException as a common indicator of the operation failure
*/
@Throws(PcztException.CreatePcztFromProposalException::class)
suspend fun createPcztFromProposal(
accountUuid: AccountUuid,
proposal: Proposal
): Pczt

/**
* Adds proofs to the given PCZT.
*
* @param pczt The partially created transaction in its serialized format.
*
* @return The updated PCZT in its serialized format.
*
* @throws PcztException.AddProofsToPcztException as a common indicator of the operation failure
*/
@Throws(PcztException.AddProofsToPcztException::class)
suspend fun addProofsToPczt(pczt: Pczt): Pczt

/**
* Takes a PCZT that has been separately proven and signed, finalizes it, and stores
* it in the wallet. Internally, this logic also submits and checks the newly stored and encoded transaction.
*
* @param pcztWithProofs
* @param pcztWithSignatures
*
* @return The submission result of the completed transaction.
*
* @throws PcztException.ExtractAndStoreTxFromPcztException as a common indicator of the operation failure
*/
@Throws(PcztException.ExtractAndStoreTxFromPcztException::class)
suspend fun createTransactionFromPczt(
pcztWithProofs: Pczt,
pcztWithSignatures: Pczt,
): Flow<TransactionSubmitResult>

// TODO [#1534]: Add RustLayerException.ValidateAddressException
// TODO [#1534]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/1534

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,38 @@ sealed class LightWalletException(message: String, cause: Throwable? = null) : S
)
}

/**
* Potentially user-facing exceptions thrown while creating transactions
*/
sealed class PcztException(
message: String,
cause: Throwable? = null
) : SdkException(message, cause) {
class CreatePcztFromProposalException internal constructor(
description: String?,
cause: Throwable?
) : PcztException(
"Failed to create PCZT from proposal with message: ${description ?: "-"}",
cause
)

class AddProofsToPcztException internal constructor(
description: String?,
cause: Throwable?
) : PcztException(
"Failed to add proofs to PCZT with message: ${description ?: "-"}",
cause
)

class ExtractAndStoreTxFromPcztException internal constructor(
description: String?,
cause: Throwable?
) : PcztException(
"Failed to extract and store transaction from PCZT with message: ${description ?: "-"}",
cause
)
}

/**
* Potentially user-facing exceptions thrown while encoding transactions.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke
pcztWithProofs: Pczt,
pcztWithSignatures: Pczt
): FirstClassByteArray =
FirstClassByteArray(backend.extractAndStoreTxFromPczt(pcztWithProofs.toByteArray(), pcztWithSignatures.toByteArray()))
FirstClassByteArray(
backend.extractAndStoreTxFromPczt(
pcztWithProofs.toByteArray(), pcztWithSignatures.toByteArray()
)
)

override suspend fun getCurrentAddress(account: Account): String {
return runCatching {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ 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.AccountUuid
import cash.z.ecc.android.sdk.model.Pczt
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
Expand Down Expand Up @@ -114,6 +116,18 @@ internal interface OutboundTransactionManager {
*/
suspend fun submit(encodedTransaction: EncodedTransaction): TransactionSubmitResult

suspend fun createPcztFromProposal(
accountUuid: AccountUuid,
proposal: Proposal
): Pczt

suspend fun addProofsToPczt(pczt: Pczt): Pczt

suspend fun extractAndStoreTxFromPczt(
pcztWithProofs: Pczt,
pcztWithSignatures: Pczt
): EncodedTransaction

/**
* Return true when the given address is a valid t-addr.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ 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.AccountUuid
import cash.z.ecc.android.sdk.model.Pczt
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.TransactionSubmitResult
Expand Down Expand Up @@ -138,6 +140,18 @@ internal class OutboundTransactionManagerImpl(
}
}

override suspend fun createPcztFromProposal(
accountUuid: AccountUuid,
proposal: Proposal
) = encoder.createPcztFromProposal(accountUuid, proposal)

override suspend fun addProofsToPczt(pczt: Pczt) = encoder.addProofsToPczt(pczt)

override suspend fun extractAndStoreTxFromPczt(
pcztWithProofs: Pczt,
pcztWithSignatures: Pczt
) = encoder.extractAndStoreTxFromPczt(pcztWithProofs, pcztWithSignatures)

override suspend fun isValidShieldedAddress(address: String) = encoder.isValidShieldedAddress(address)

override suspend fun isValidTransparentAddress(address: String) = encoder.isValidTransparentAddress(address)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package cash.z.ecc.android.sdk.internal.transaction
import cash.z.ecc.android.sdk.exception.TransactionEncoderException
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.AccountUuid
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.Pczt
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
Expand Down Expand Up @@ -126,6 +128,18 @@ internal interface TransactionEncoder {
usk: UnifiedSpendingKey
): List<EncodedTransaction>

suspend fun createPcztFromProposal(
accountUuid: AccountUuid,
proposal: Proposal
): Pczt

suspend fun addProofsToPczt(pczt: Pczt): Pczt

suspend fun extractAndStoreTxFromPczt(
pcztWithProofs: Pczt,
pcztWithSignatures: Pczt
): EncodedTransaction

/**
* 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package cash.z.ecc.android.sdk.internal.transaction

import cash.z.ecc.android.sdk.exception.PcztException
import cash.z.ecc.android.sdk.exception.SdkException
import cash.z.ecc.android.sdk.exception.TransactionEncoderException
import cash.z.ecc.android.sdk.ext.masked
Expand All @@ -9,8 +10,10 @@ 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.AccountUuid
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.FirstClassByteArray
import cash.z.ecc.android.sdk.model.Pczt
import cash.z.ecc.android.sdk.model.Proposal
import cash.z.ecc.android.sdk.model.TransactionRecipient
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
Expand Down Expand Up @@ -200,6 +203,60 @@ internal class TransactionEncoderImpl(
return txs
}

override suspend fun createPcztFromProposal(
accountUuid: AccountUuid,
proposal: Proposal
): Pczt {
return runCatching {
backend.createPcztFromProposal(
account = Account.new(accountUuid),
proposal = proposal
)
}.onSuccess {
Twig.debug { "Result of createPcztFromProposal: $it" }
}.onFailure {
Twig.error(it) { "Caught exception while creating PCZT." }
}.getOrElse {
throw PcztException.CreatePcztFromProposalException(it.message, it.cause)
}
}

override suspend fun addProofsToPczt(pczt: Pczt): Pczt {
return runCatching {
backend.addProofsToPczt(
pczt = pczt
)
}.onSuccess {
Twig.debug { "Result of addProofsToPczt: $it" }
}.onFailure {
Twig.error(it) { "Caught exception while adding proofs to PCZT." }
}.getOrElse {
throw PcztException.AddProofsToPcztException(it.message, it.cause)
}
}

override suspend fun extractAndStoreTxFromPczt(
pcztWithProofs: Pczt,
pcztWithSignatures: Pczt
): EncodedTransaction {
val txId =
runCatching {
backend.extractAndStoreTxFromPczt(
pcztWithProofs = pcztWithProofs,
pcztWithSignatures = pcztWithSignatures
)
}.onSuccess {
Twig.debug { "Result of extractAndStoreTxFromPczt: $it" }
}.onFailure {
Twig.error(it) { "Caught exception while extracting and storing transaction from PCZT." }
}.getOrElse {
throw PcztException.ExtractAndStoreTxFromPcztException(it.message, it.cause)
}

return repository.findEncodedTransactionByTxId(txId)
?: throw TransactionEncoderException.TransactionNotFoundException(txId)
}

/**
* 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.
Expand Down
3 changes: 3 additions & 0 deletions sdk-lib/src/main/java/cash/z/ecc/android/sdk/model/Pczt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ class Pczt(
fun toByteArray(): ByteArray {
return inner
}

// Override to prevent leaking data in logs
override fun toString() = "Pczt(size=${inner.size})"
}

0 comments on commit ca032f4

Please sign in to comment.