Skip to content

Commit

Permalink
New usage of setTransactionStatus
Browse files Browse the repository at this point in the history
- Distinct between decryptAndStoreTransaction and setTransactionStatus path by minedHeight
- Convert from RawStatusUnsafe to TransactionStatus
- Add RawTransaction object and related functions
- These changes resolve older issue #1254
  • Loading branch information
HonzaR committed Aug 15, 2024
1 parent 0ad7012 commit 4b5c96a
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,10 @@ class RustBackend private constructor(
)
}

override suspend fun setTransactionStatus(txId: ByteArray, status: Long) = withContext(SdkDispatchers.DATABASE_IO) {
override suspend fun setTransactionStatus(
txId: ByteArray,
status: Long
) = withContext(SdkDispatchers.DATABASE_IO) {
Companion.setTransactionStatus(
dataDbFile.absolutePath,
txId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ interface JniTransactionDataRequest {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,17 @@ sealed class RawTransactionUnsafe(open val data: ByteArray) {

companion object {
fun new(rawTransaction: RawTransaction): RawTransactionUnsafe {
val data = rawTransaction.data.toByteArray();
val data = rawTransaction.data.toByteArray()
return when (rawTransaction.height) {
-1L -> OrphanedBlock(data)
0L -> Mempool(data)
else -> MainChain(data, BlockHeightUnsafe(rawTransaction.height))
}
}
}

/**
* This is a safe [toString] function that prints only non-sensitive parts
*/
override fun toString() = "RawTransactionUnsafe: type: ${this::class.simpleName}"
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ internal class FakeRustBackend(
TODO("Not yet implemented")
}

override suspend fun setTransactionStatus(txId: ByteArray, status: Long) {
override suspend fun setTransactionStatus(
txId: ByteArray,
status: Long
) {
TODO("Not yet implemented")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import cash.z.ecc.android.sdk.block.processor.model.VerifySuggestedScanRange
import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException
import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException.EnhanceTransactionError.EnhanceTxDecryptError
import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException.EnhanceTransactionError.EnhanceTxDownloadError
import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException.EnhanceTransactionError.EnhanceTxSetStatusError
import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException.MismatchedConsensusBranch
import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException.MismatchedNetwork
import cash.z.ecc.android.sdk.exception.InitializeException
Expand All @@ -43,14 +44,17 @@ import cash.z.ecc.android.sdk.internal.model.JniBlockMeta
import cash.z.ecc.android.sdk.internal.model.ScanRange
import cash.z.ecc.android.sdk.internal.model.SubtreeRoot
import cash.z.ecc.android.sdk.internal.model.SuggestScanRangePriority
import cash.z.ecc.android.sdk.internal.model.TransactionStatus
import cash.z.ecc.android.sdk.internal.model.TreeState
import cash.z.ecc.android.sdk.internal.model.WalletSummary
import cash.z.ecc.android.sdk.internal.model.ext.from
import cash.z.ecc.android.sdk.internal.model.ext.toBlockHeight
import cash.z.ecc.android.sdk.internal.model.ext.toTransactionStatus
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.PercentDecimal
import cash.z.ecc.android.sdk.model.RawTransaction
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
Expand Down Expand Up @@ -1872,6 +1876,7 @@ class CompactBlockProcessor internal constructor(
emit(SyncingResult.EnhanceSuccess)
}

@Suppress("LongMethod")
private suspend fun enhanceTransaction(
transaction: DbTransactionOverview,
backend: TypesafeBackend,
Expand All @@ -1894,30 +1899,51 @@ class CompactBlockProcessor internal constructor(
"Fetching transaction (txid:${transaction.txIdString()} block:${transaction
.minedHeight})"
}
val transactionData =
val rawTransactionUnsafe =
fetchTransaction(
transactionId = transaction.txIdString(),
rawTransactionId = transaction.rawId.byteArray,
minedHeight = transaction.minedHeight,
transactionOverview = transaction,
downloader = downloader,
network = network
)

// Decrypting and storing transaction is run just once, since we consider it more stable
Twig.verbose {
"Decrypting and storing transaction " +
"(txid:${transaction.txIdString()} block:${transaction.minedHeight})"
Twig.debug { "Transaction fetched: $rawTransactionUnsafe" }

// We need to distinct between three possible state of the fetched transaction
when (rawTransactionUnsafe) {
is RawTransactionUnsafe.MainChain -> {
// Decrypting and storing transaction is run just once, since we consider it more stable
Twig.verbose {
"Decrypting and storing transaction (txid:${transaction.txIdString()}, " +
"block:${transaction.minedHeight})"
}
decryptTransaction(
rawTransaction =
RawTransaction.new(
rawTransactionUnsafe = rawTransactionUnsafe,
network = network
),
backend = backend
)
}
is RawTransactionUnsafe.Mempool,
is RawTransactionUnsafe.OrphanedBlock -> {
Twig.verbose {
"Setting status of transaction (txid:${transaction.txIdString()}, " +
"block:${transaction.minedHeight})"
}
setTransactionStatus(
transactionRawId = transaction.rawId.byteArray,
status = rawTransactionUnsafe.toTransactionStatus(network),
height = transaction.minedHeight,
backend = backend
)
}
}
decryptTransaction(
transactionData = transactionData.first,
minedHeight = transactionData.second,
backend = backend
)

Twig.debug {
"Done enhancing transaction (txid:${transaction.txIdString()} block:${transaction
.minedHeight})"
}

SyncingResult.EnhanceSuccess
} catch (exception: CompactBlockProcessorException.EnhanceTransactionError) {
SyncingResult.EnhanceFailed(
Expand All @@ -1929,61 +1955,74 @@ class CompactBlockProcessor internal constructor(
return result
}

// TODO [#1254]: CompactblockProcessor.fetchTransaction pass txId twice
// TODO [#1254]: https://github.com/zcash/zcash-android-wallet-sdk/issues/1254
@Throws(EnhanceTxDownloadError::class)
private suspend fun fetchTransaction(
transactionId: String,
rawTransactionId: ByteArray,
minedHeight: BlockHeight,
transactionOverview: DbTransactionOverview,
downloader: CompactBlockDownloader,
network: ZcashNetwork
): Pair<ByteArray, BlockHeight?> {
): RawTransactionUnsafe {
var transactionResult: RawTransactionUnsafe? = null
val traceScope = TraceScope("CompactBlockProcessor.fetchTransaction")
var transactionDataResult: Pair<ByteArray, BlockHeight?>? = null

retryUpToAndThrow(TRANSACTION_FETCH_RETRIES) { failedAttempts ->
if (failedAttempts == 0) {
Twig.debug { "Starting to fetch transaction (txid:$transactionId, block:$minedHeight)" }
Twig.debug {
"Starting to fetch transaction (txid:${transactionOverview.txIdString()}, " +
"block:${transactionOverview.minedHeight})"
}
} else {
Twig.warn {
"Retrying to fetch transaction (txid:$transactionId, block:$minedHeight) after" +
" $failedAttempts failure(s)..."
"Retrying to fetch transaction (txid:${transactionOverview.txIdString()}, " +
"block:${transactionOverview.minedHeight} after $failedAttempts failure(s)..."
}
}
when (val response = downloader.fetchTransaction(rawTransactionId)) {
is Response.Success -> {
val currentMinedHeight = when (response.result) {
is RawTransactionUnsafe.MainChain -> runCatching {
(response.result as RawTransactionUnsafe.MainChain).height.toBlockHeight(
network
)
}.getOrNull()
else -> null

transactionResult =
when (
val response =
downloader.fetchTransaction(
transactionOverview.rawId
.byteArray
)
) {
is Response.Success -> response.result
is Response.Failure -> {
throw EnhanceTxDownloadError(transactionOverview.minedHeight, response.toThrowable())
}
transactionDataResult = Pair(response.result.data, currentMinedHeight)
}
is Response.Failure -> {
throw EnhanceTxDownloadError(minedHeight, response.toThrowable())
}
}
}
traceScope.end()
// Result is fetched or EnhanceTxDownloadError is thrown after all attempts failed at this point
return transactionDataResult!!
return transactionResult!!
}

@Throws(EnhanceTxDecryptError::class)
private suspend fun decryptTransaction(
transactionData: ByteArray,
minedHeight: BlockHeight?,
rawTransaction: RawTransaction,
backend: TypesafeBackend,
) {
val traceScope = TraceScope("CompactBlockProcessor.decryptTransaction")
runCatching {
backend.decryptAndStoreTransaction(transactionData, minedHeight)
backend.decryptAndStoreTransaction(rawTransaction.data, rawTransaction.height)
}.onFailure {
traceScope.end()
throw EnhanceTxDecryptError(rawTransaction.height, it)
}
traceScope.end()
}

@Throws(EnhanceTxSetStatusError::class)
private suspend fun setTransactionStatus(
transactionRawId: ByteArray,
status: TransactionStatus,
height: BlockHeight,
backend: TypesafeBackend,
) {
val traceScope = TraceScope("CompactBlockProcessor.setTransactionStatus")
runCatching {
backend.setTransactionStatus(transactionRawId, status)
}.onFailure {
traceScope.end()
throw EnhanceTxDecryptError(minedHeight, it)
throw EnhanceTxSetStatusError(height, it)
}
traceScope.end()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ sealed class CompactBlockProcessorException(message: String, cause: Throwable? =
cause: Throwable
) : CompactBlockProcessorException(message, cause) {
class EnhanceTxDownloadError(
height: BlockHeight,
height: BlockHeight?,
cause: Throwable
) : EnhanceTransactionError(
"Error while attempting to download a transaction to enhance",
Expand All @@ -118,6 +118,15 @@ sealed class CompactBlockProcessorException(message: String, cause: Throwable? =
height,
cause
)

class EnhanceTxSetStatusError(
height: BlockHeight?,
cause: Throwable
) : EnhanceTransactionError(
"Error while attempting to set status of a transaction to the Rust backend",
height,
cause
)
}

class MismatchedNetwork(clientNetwork: String?, serverNetwork: String?) : CompactBlockProcessorException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,15 +260,13 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke
) = backend
.decryptAndStoreTransaction(tx, minedHeight?.value)

override suspend fun setTransactionStatus(txId: ByteArray, status: TransactionStatus) = backend
.setTransactionStatus(
txId, when (status) {
is TransactionStatus.Mined -> status.height.value
is TransactionStatus.NotInMainChain -> -1L
// TxidNotRecognized
else -> -2L
}
)
override suspend fun setTransactionStatus(
txId: ByteArray,
status: TransactionStatus
) = backend.setTransactionStatus(
txId = txId,
status = status.toPrimitiveValue()
)

override fun getSaplingReceiver(ua: String): String? = backend.getSaplingReceiver(ua)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cash.z.ecc.android.sdk.internal.model

import cash.z.ecc.android.sdk.exception.SdkException
import cash.z.ecc.android.sdk.internal.ext.isInUIntRange
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.ZcashNetwork

Expand Down Expand Up @@ -35,18 +34,19 @@ interface TransactionDataRequest {
return when (jni) {
is JniTransactionDataRequest.GetStatus -> GetStatus(jni.txid)
is JniTransactionDataRequest.Enhancement -> Enhancement(jni.txid)
is JniTransactionDataRequest.SpendsFromAddress -> SpendsFromAddress(
jni.address,
BlockHeight.new(zcashNetwork, jni.startHeight),
if (jni.endHeight == -1L) {
null
} else {
BlockHeight.new(zcashNetwork, jni.endHeight)
}
)
is JniTransactionDataRequest.SpendsFromAddress ->
SpendsFromAddress(
jni.address,
BlockHeight.new(zcashNetwork, jni.startHeight),
if (jni.endHeight == -1L) {
null
} else {
BlockHeight.new(zcashNetwork, jni.endHeight)
}
)

else -> throw SdkException("Unknown JniTransactionDataRequest variant", cause = null)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,22 @@ package cash.z.ecc.android.sdk.internal.model

import cash.z.ecc.android.sdk.model.BlockHeight

interface TransactionStatus {
class TxidNotRecognized : TransactionStatus
class NotInMainChain : TransactionStatus
data class Mined(val height: BlockHeight) : TransactionStatus
}
sealed class TransactionStatus {
abstract fun toPrimitiveValue(): Long

data class Mined(val height: BlockHeight) : TransactionStatus() {
override fun toPrimitiveValue() = height.value
}

data object NotInMainChain : TransactionStatus() {
private const val NOT_IN_MAIN_CHAIN = -1L

override fun toPrimitiveValue() = NOT_IN_MAIN_CHAIN
}

data object TxidNotRecognized : TransactionStatus() {
private const val TXID_NOT_RECOGNIZED = -2L

override fun toPrimitiveValue() = TXID_NOT_RECOGNIZED
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cash.z.ecc.android.sdk.internal.model.ext

import cash.z.ecc.android.sdk.internal.model.TransactionStatus
import cash.z.ecc.android.sdk.model.ZcashNetwork
import co.electriccoin.lightwallet.client.model.RawTransactionUnsafe
import co.electriccoin.lightwallet.client.model.RawTransactionUnsafe.MainChain
import co.electriccoin.lightwallet.client.model.RawTransactionUnsafe.Mempool
import co.electriccoin.lightwallet.client.model.RawTransactionUnsafe.OrphanedBlock

internal fun RawTransactionUnsafe.toTransactionStatus(network: ZcashNetwork): TransactionStatus {
return when (this) {
is MainChain -> TransactionStatus.Mined(height.toBlockHeight(network))
is Mempool -> TransactionStatus.NotInMainChain
is OrphanedBlock -> TransactionStatus.TxidNotRecognized
}
}
Loading

0 comments on commit 4b5c96a

Please sign in to comment.