Provide the pool type when retrieving a memo. (#1436)

* WIP: Provide the pool type when retrieving a memo.

* Gitignore new idea file

* Let TypesafeBackend work with typesafe class

* Query and use protocol for memo obtaining

---------

Co-authored-by: Honza <rychnovsky.honza@gmail.com>
This commit is contained in:
Kris Nuttycombe 2024-04-14 13:28:07 -06:00 committed by GitHub
parent 315e8bf4fe
commit 7dadd57d6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 132 additions and 20 deletions

1
.gitignore vendored
View File

@ -52,6 +52,7 @@ captures/
.idea/protoeditor.xml
.idea/appInsightsSettings.xml
.idea/migrations.xml
.idea/deploymentTargetSelector.xml
*.iml
# Keystore files

View File

@ -94,6 +94,7 @@ interface Backend {
@Throws(RuntimeException::class)
suspend fun getMemoAsUtf8(
txId: ByteArray,
protocol: Int,
outputIndex: Int
): String?

View File

@ -125,11 +125,13 @@ class RustBackend private constructor(
override suspend fun getMemoAsUtf8(
txId: ByteArray,
protocol: Int,
outputIndex: Int
) = withContext(SdkDispatchers.DATABASE_IO) {
getMemoAsUtf8(
dataDbFile.absolutePath,
txId,
protocol,
outputIndex,
networkId = networkId
)
@ -501,6 +503,7 @@ class RustBackend private constructor(
private external fun getMemoAsUtf8(
dbDataPath: String,
txId: ByteArray,
poolType: Int,
outputIndex: Int,
networkId: Int
): String?

View File

@ -0,0 +1,41 @@
package cash.z.ecc.android.sdk.internal.model
/**
* An enumeration of supported Zcash protocols.
*/
@Suppress("MagicNumber")
enum class ZcashProtocol {
TRANSPARENT {
override val poolCode = 0
},
SAPLING {
override val poolCode = 2
},
ORCHARD {
override val poolCode = 3
};
abstract val poolCode: Int
fun isShielded() = this == SAPLING || this == ORCHARD
companion object {
fun validate(poolTypeCode: Int): Boolean {
return when (poolTypeCode) {
TRANSPARENT.poolCode,
SAPLING.poolCode,
ORCHARD.poolCode -> true
else -> false
}
}
fun fromPoolType(poolCode: Int): ZcashProtocol {
return when (poolCode) {
TRANSPARENT.poolCode -> TRANSPARENT
SAPLING.poolCode -> SAPLING
ORCHARD.poolCode -> ORCHARD
else -> error("Unsupported pool type: $poolCode")
}
}
}
}

View File

@ -799,15 +799,25 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getTotalT
unwrap_exc_or(&mut env, res, -1)
}
fn parse_protocol(code: i32) -> Option<ShieldedProtocol> {
match code {
2 => Some(ShieldedProtocol::Sapling),
3 => Some(ShieldedProtocol::Orchard),
_ => None
}
}
#[no_mangle]
pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getMemoAsUtf8<'local>(
mut env: JNIEnv<'local>,
_: JClass<'local>,
db_data: JString<'local>,
txid_bytes: JByteArray<'local>,
pool_type: jint,
output_index: jint,
network_id: jint,
) -> jstring {
let res = catch_unwind(&mut env, |env| {
let _span = tracing::info_span!("RustBackend.getMemoAsUtf8").entered();
let network = parse_network(network_id as u32)?;
@ -815,10 +825,11 @@ pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getMemoAs
let txid_bytes = env.convert_byte_array(txid_bytes)?;
let txid = TxId::read(&txid_bytes[..])?;
let protocol = parse_protocol(pool_type).ok_or(format_err!("Shielded protocol not recognized: {}", pool_type))?;
let output_index = u16::try_from(output_index)?;
let memo = (&db_data)
.get_memo(NoteId::new(txid, ShieldedProtocol::Sapling, output_index))
.get_memo(NoteId::new(txid, protocol, output_index))
.map_err(|e| format_err!("An error occurred retrieving the memo, {}", e))
.and_then(|memo| match memo {
Some(Memo::Empty) => Ok("".to_string()),

View File

@ -150,6 +150,7 @@ internal class FakeRustBackend(
override suspend fun getMemoAsUtf8(
txId: ByteArray,
protocol: Int,
outputIndex: Int
): String? = error("Intentionally not implemented in mocked FakeRustBackend implementation.")

View File

@ -327,17 +327,25 @@ class SdkSynchronizer private constructor(
}
override fun getMemos(transactionOverview: TransactionOverview): Flow<String> {
return storage.getSaplingOutputIndices(transactionOverview.rawId).map {
runCatching {
backend.getMemoAsUtf8(transactionOverview.rawId.byteArray, it)
}.onFailure {
Twig.error { "Failed to get memo with: $it" }
}.onSuccess {
Twig.debug { "Transaction memo queried: $it" }
}.fold(
onSuccess = { it ?: "" },
onFailure = { "" }
)
return storage.getOutputProperties(transactionOverview.rawId).map { properties ->
if (!properties.protocol.isShielded()) {
""
} else {
runCatching {
backend.getMemoAsUtf8(
txId = transactionOverview.rawId.byteArray,
protocol = properties.protocol,
outputIndex = properties.index
)
}.onFailure {
Twig.error { "Failed to get memo with: $it" }
}.onSuccess {
Twig.debug { "Transaction memo queried: $it" }
}.fold(
onSuccess = { it ?: "" },
onFailure = { "" }
)
}
}
}

View File

@ -7,6 +7,7 @@ import cash.z.ecc.android.sdk.internal.model.ScanSummary
import cash.z.ecc.android.sdk.internal.model.SubtreeRoot
import cash.z.ecc.android.sdk.internal.model.TreeState
import cash.z.ecc.android.sdk.internal.model.WalletSummary
import cash.z.ecc.android.sdk.internal.model.ZcashProtocol
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.FirstClassByteArray
@ -75,6 +76,7 @@ internal interface TypesafeBackend {
suspend fun getMemoAsUtf8(
txId: ByteArray,
protocol: ZcashProtocol,
outputIndex: Int
): String?

View File

@ -8,6 +8,7 @@ import cash.z.ecc.android.sdk.internal.model.ScanSummary
import cash.z.ecc.android.sdk.internal.model.SubtreeRoot
import cash.z.ecc.android.sdk.internal.model.TreeState
import cash.z.ecc.android.sdk.internal.model.WalletSummary
import cash.z.ecc.android.sdk.internal.model.ZcashProtocol
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.FirstClassByteArray
@ -147,8 +148,14 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke
override suspend fun getMemoAsUtf8(
txId: ByteArray,
protocol: ZcashProtocol,
outputIndex: Int
): String? = backend.getMemoAsUtf8(txId, outputIndex)
): String? =
backend.getMemoAsUtf8(
txId = txId,
protocol = protocol.poolCode,
outputIndex = outputIndex
)
override suspend fun initDataDb(seed: ByteArray?) {
val ret = backend.initDataDb(seed)

View File

@ -53,8 +53,9 @@ internal class DbDerivedDataRepository(
override val allTransactions: Flow<List<DbTransactionOverview>>
get() = invalidatingFlow.map { derivedDataDb.allTransactionView.getAllTransactions().toList() }
override fun getSaplingOutputIndices(transactionId: FirstClassByteArray) =
derivedDataDb.txOutputsView.getSaplingOutputIndices(transactionId)
override fun getOutputProperties(transactionId: FirstClassByteArray) =
derivedDataDb.txOutputsView
.getOutputProperties(transactionId)
override fun getRecipients(transactionId: FirstClassByteArray): Flow<TransactionRecipient> {
return derivedDataDb.txOutputsView.getRecipients(transactionId)

View File

@ -2,6 +2,7 @@ package cash.z.ecc.android.sdk.internal.db.derived
import androidx.sqlite.db.SupportSQLiteDatabase
import cash.z.ecc.android.sdk.internal.db.queryAndMap
import cash.z.ecc.android.sdk.internal.model.OutputProperties
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.FirstClassByteArray
import cash.z.ecc.android.sdk.model.TransactionRecipient
@ -22,7 +23,11 @@ internal class TxOutputsView(
TxOutputsViewDefinition.COLUMN_BLOB_TRANSACTION_ID
)
private val PROJECTION_OUTPUT_INDEX = arrayOf(TxOutputsViewDefinition.COLUMN_INTEGER_OUTPUT_INDEX)
private val PROJECTION_OUTPUT_PROPERTIES =
arrayOf(
TxOutputsViewDefinition.COLUMN_INTEGER_OUTPUT_INDEX,
TxOutputsViewDefinition.COLUMN_INTEGER_OUTPUT_POOL,
)
private val PROJECTION_RECIPIENT =
arrayOf(
@ -40,17 +45,22 @@ internal class TxOutputsView(
)
}
fun getSaplingOutputIndices(transactionId: FirstClassByteArray) =
fun getOutputProperties(transactionId: FirstClassByteArray) =
sqliteDatabase.queryAndMap(
table = TxOutputsViewDefinition.VIEW_NAME,
columns = PROJECTION_OUTPUT_INDEX,
columns = PROJECTION_OUTPUT_PROPERTIES,
selection = SELECT_BY_TRANSACTION_ID_AND_NOT_CHANGE,
selectionArgs = arrayOf(transactionId.byteArray),
orderBy = ORDER_BY,
cursorParser = {
val idColumnOutputIndex = it.getColumnIndex(TxOutputsViewDefinition.COLUMN_INTEGER_OUTPUT_INDEX)
val idColumnOutputPoolIndex = it.getColumnIndex(TxOutputsViewDefinition.COLUMN_INTEGER_OUTPUT_POOL)
it.getInt(idColumnOutputIndex)
OutputProperties.new(
index = it.getInt(idColumnOutputIndex),
// Converting blob to Int
poolType = it.getInt(idColumnOutputPoolIndex)
)
}
)

View File

@ -0,0 +1,25 @@
package cash.z.ecc.android.sdk.internal.model
/**
* Typesafe model class representing different properties from [TxOutputsView]
*/
internal class OutputProperties(
val index: Int,
val protocol: ZcashProtocol
) {
companion object {
fun new(
index: Int,
poolType: Int
): OutputProperties {
require(index >= 0) { "Output index: $index must be greater or equal to zero" }
require(ZcashProtocol.validate(poolType)) { "Output poolType: $poolType unknown" }
return OutputProperties(
index = index,
protocol = ZcashProtocol.fromPoolType(poolType)
)
}
}
}

View File

@ -2,6 +2,7 @@ package cash.z.ecc.android.sdk.internal.repository
import cash.z.ecc.android.sdk.internal.model.DbTransactionOverview
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
import cash.z.ecc.android.sdk.internal.model.OutputProperties
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.FirstClassByteArray
import cash.z.ecc.android.sdk.model.TransactionRecipient
@ -81,7 +82,7 @@ internal interface DerivedDataRepository {
val allTransactions: Flow<List<DbTransactionOverview>>
fun getSaplingOutputIndices(transactionId: FirstClassByteArray): Flow<Int>
fun getOutputProperties(transactionId: FirstClassByteArray): Flow<OutputProperties>
fun getRecipients(transactionId: FirstClassByteArray): Flow<TransactionRecipient>