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:
parent
315e8bf4fe
commit
7dadd57d6b
|
@ -52,6 +52,7 @@ captures/
|
||||||
.idea/protoeditor.xml
|
.idea/protoeditor.xml
|
||||||
.idea/appInsightsSettings.xml
|
.idea/appInsightsSettings.xml
|
||||||
.idea/migrations.xml
|
.idea/migrations.xml
|
||||||
|
.idea/deploymentTargetSelector.xml
|
||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
# Keystore files
|
# Keystore files
|
||||||
|
|
|
@ -94,6 +94,7 @@ interface Backend {
|
||||||
@Throws(RuntimeException::class)
|
@Throws(RuntimeException::class)
|
||||||
suspend fun getMemoAsUtf8(
|
suspend fun getMemoAsUtf8(
|
||||||
txId: ByteArray,
|
txId: ByteArray,
|
||||||
|
protocol: Int,
|
||||||
outputIndex: Int
|
outputIndex: Int
|
||||||
): String?
|
): String?
|
||||||
|
|
||||||
|
|
|
@ -125,11 +125,13 @@ class RustBackend private constructor(
|
||||||
|
|
||||||
override suspend fun getMemoAsUtf8(
|
override suspend fun getMemoAsUtf8(
|
||||||
txId: ByteArray,
|
txId: ByteArray,
|
||||||
|
protocol: Int,
|
||||||
outputIndex: Int
|
outputIndex: Int
|
||||||
) = withContext(SdkDispatchers.DATABASE_IO) {
|
) = withContext(SdkDispatchers.DATABASE_IO) {
|
||||||
getMemoAsUtf8(
|
getMemoAsUtf8(
|
||||||
dataDbFile.absolutePath,
|
dataDbFile.absolutePath,
|
||||||
txId,
|
txId,
|
||||||
|
protocol,
|
||||||
outputIndex,
|
outputIndex,
|
||||||
networkId = networkId
|
networkId = networkId
|
||||||
)
|
)
|
||||||
|
@ -501,6 +503,7 @@ class RustBackend private constructor(
|
||||||
private external fun getMemoAsUtf8(
|
private external fun getMemoAsUtf8(
|
||||||
dbDataPath: String,
|
dbDataPath: String,
|
||||||
txId: ByteArray,
|
txId: ByteArray,
|
||||||
|
poolType: Int,
|
||||||
outputIndex: Int,
|
outputIndex: Int,
|
||||||
networkId: Int
|
networkId: Int
|
||||||
): String?
|
): String?
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
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]
|
#[no_mangle]
|
||||||
pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getMemoAsUtf8<'local>(
|
pub extern "C" fn Java_cash_z_ecc_android_sdk_internal_jni_RustBackend_getMemoAsUtf8<'local>(
|
||||||
mut env: JNIEnv<'local>,
|
mut env: JNIEnv<'local>,
|
||||||
_: JClass<'local>,
|
_: JClass<'local>,
|
||||||
db_data: JString<'local>,
|
db_data: JString<'local>,
|
||||||
txid_bytes: JByteArray<'local>,
|
txid_bytes: JByteArray<'local>,
|
||||||
|
pool_type: jint,
|
||||||
output_index: jint,
|
output_index: jint,
|
||||||
network_id: jint,
|
network_id: jint,
|
||||||
) -> jstring {
|
) -> jstring {
|
||||||
|
|
||||||
let res = catch_unwind(&mut env, |env| {
|
let res = catch_unwind(&mut env, |env| {
|
||||||
let _span = tracing::info_span!("RustBackend.getMemoAsUtf8").entered();
|
let _span = tracing::info_span!("RustBackend.getMemoAsUtf8").entered();
|
||||||
let network = parse_network(network_id as u32)?;
|
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_bytes = env.convert_byte_array(txid_bytes)?;
|
||||||
let txid = TxId::read(&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 output_index = u16::try_from(output_index)?;
|
||||||
|
|
||||||
let memo = (&db_data)
|
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))
|
.map_err(|e| format_err!("An error occurred retrieving the memo, {}", e))
|
||||||
.and_then(|memo| match memo {
|
.and_then(|memo| match memo {
|
||||||
Some(Memo::Empty) => Ok("".to_string()),
|
Some(Memo::Empty) => Ok("".to_string()),
|
||||||
|
|
|
@ -150,6 +150,7 @@ internal class FakeRustBackend(
|
||||||
|
|
||||||
override suspend fun getMemoAsUtf8(
|
override suspend fun getMemoAsUtf8(
|
||||||
txId: ByteArray,
|
txId: ByteArray,
|
||||||
|
protocol: Int,
|
||||||
outputIndex: Int
|
outputIndex: Int
|
||||||
): String? = error("Intentionally not implemented in mocked FakeRustBackend implementation.")
|
): String? = error("Intentionally not implemented in mocked FakeRustBackend implementation.")
|
||||||
|
|
||||||
|
|
|
@ -327,9 +327,16 @@ class SdkSynchronizer private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMemos(transactionOverview: TransactionOverview): Flow<String> {
|
override fun getMemos(transactionOverview: TransactionOverview): Flow<String> {
|
||||||
return storage.getSaplingOutputIndices(transactionOverview.rawId).map {
|
return storage.getOutputProperties(transactionOverview.rawId).map { properties ->
|
||||||
|
if (!properties.protocol.isShielded()) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
runCatching {
|
runCatching {
|
||||||
backend.getMemoAsUtf8(transactionOverview.rawId.byteArray, it)
|
backend.getMemoAsUtf8(
|
||||||
|
txId = transactionOverview.rawId.byteArray,
|
||||||
|
protocol = properties.protocol,
|
||||||
|
outputIndex = properties.index
|
||||||
|
)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Twig.error { "Failed to get memo with: $it" }
|
Twig.error { "Failed to get memo with: $it" }
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
|
@ -340,6 +347,7 @@ class SdkSynchronizer private constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getRecipients(transactionOverview: TransactionOverview): Flow<TransactionRecipient> {
|
override fun getRecipients(transactionOverview: TransactionOverview): Flow<TransactionRecipient> {
|
||||||
require(transactionOverview.isSentTransaction) { "Recipients can only be queried for sent transactions" }
|
require(transactionOverview.isSentTransaction) { "Recipients can only be queried for sent transactions" }
|
||||||
|
|
|
@ -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.SubtreeRoot
|
||||||
import cash.z.ecc.android.sdk.internal.model.TreeState
|
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.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.Account
|
||||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
||||||
|
@ -75,6 +76,7 @@ internal interface TypesafeBackend {
|
||||||
|
|
||||||
suspend fun getMemoAsUtf8(
|
suspend fun getMemoAsUtf8(
|
||||||
txId: ByteArray,
|
txId: ByteArray,
|
||||||
|
protocol: ZcashProtocol,
|
||||||
outputIndex: Int
|
outputIndex: Int
|
||||||
): String?
|
): String?
|
||||||
|
|
||||||
|
|
|
@ -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.SubtreeRoot
|
||||||
import cash.z.ecc.android.sdk.internal.model.TreeState
|
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.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.Account
|
||||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
||||||
|
@ -147,8 +148,14 @@ internal class TypesafeBackendImpl(private val backend: Backend) : TypesafeBacke
|
||||||
|
|
||||||
override suspend fun getMemoAsUtf8(
|
override suspend fun getMemoAsUtf8(
|
||||||
txId: ByteArray,
|
txId: ByteArray,
|
||||||
|
protocol: ZcashProtocol,
|
||||||
outputIndex: Int
|
outputIndex: Int
|
||||||
): String? = backend.getMemoAsUtf8(txId, outputIndex)
|
): String? =
|
||||||
|
backend.getMemoAsUtf8(
|
||||||
|
txId = txId,
|
||||||
|
protocol = protocol.poolCode,
|
||||||
|
outputIndex = outputIndex
|
||||||
|
)
|
||||||
|
|
||||||
override suspend fun initDataDb(seed: ByteArray?) {
|
override suspend fun initDataDb(seed: ByteArray?) {
|
||||||
val ret = backend.initDataDb(seed)
|
val ret = backend.initDataDb(seed)
|
||||||
|
|
|
@ -53,8 +53,9 @@ internal class DbDerivedDataRepository(
|
||||||
override val allTransactions: Flow<List<DbTransactionOverview>>
|
override val allTransactions: Flow<List<DbTransactionOverview>>
|
||||||
get() = invalidatingFlow.map { derivedDataDb.allTransactionView.getAllTransactions().toList() }
|
get() = invalidatingFlow.map { derivedDataDb.allTransactionView.getAllTransactions().toList() }
|
||||||
|
|
||||||
override fun getSaplingOutputIndices(transactionId: FirstClassByteArray) =
|
override fun getOutputProperties(transactionId: FirstClassByteArray) =
|
||||||
derivedDataDb.txOutputsView.getSaplingOutputIndices(transactionId)
|
derivedDataDb.txOutputsView
|
||||||
|
.getOutputProperties(transactionId)
|
||||||
|
|
||||||
override fun getRecipients(transactionId: FirstClassByteArray): Flow<TransactionRecipient> {
|
override fun getRecipients(transactionId: FirstClassByteArray): Flow<TransactionRecipient> {
|
||||||
return derivedDataDb.txOutputsView.getRecipients(transactionId)
|
return derivedDataDb.txOutputsView.getRecipients(transactionId)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package cash.z.ecc.android.sdk.internal.db.derived
|
||||||
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
import cash.z.ecc.android.sdk.internal.db.queryAndMap
|
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.Account
|
||||||
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
||||||
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
||||||
|
@ -22,7 +23,11 @@ internal class TxOutputsView(
|
||||||
TxOutputsViewDefinition.COLUMN_BLOB_TRANSACTION_ID
|
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 =
|
private val PROJECTION_RECIPIENT =
|
||||||
arrayOf(
|
arrayOf(
|
||||||
|
@ -40,17 +45,22 @@ internal class TxOutputsView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSaplingOutputIndices(transactionId: FirstClassByteArray) =
|
fun getOutputProperties(transactionId: FirstClassByteArray) =
|
||||||
sqliteDatabase.queryAndMap(
|
sqliteDatabase.queryAndMap(
|
||||||
table = TxOutputsViewDefinition.VIEW_NAME,
|
table = TxOutputsViewDefinition.VIEW_NAME,
|
||||||
columns = PROJECTION_OUTPUT_INDEX,
|
columns = PROJECTION_OUTPUT_PROPERTIES,
|
||||||
selection = SELECT_BY_TRANSACTION_ID_AND_NOT_CHANGE,
|
selection = SELECT_BY_TRANSACTION_ID_AND_NOT_CHANGE,
|
||||||
selectionArgs = arrayOf(transactionId.byteArray),
|
selectionArgs = arrayOf(transactionId.byteArray),
|
||||||
orderBy = ORDER_BY,
|
orderBy = ORDER_BY,
|
||||||
cursorParser = {
|
cursorParser = {
|
||||||
val idColumnOutputIndex = it.getColumnIndex(TxOutputsViewDefinition.COLUMN_INTEGER_OUTPUT_INDEX)
|
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)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.DbTransactionOverview
|
||||||
import cash.z.ecc.android.sdk.internal.model.EncodedTransaction
|
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.BlockHeight
|
||||||
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
import cash.z.ecc.android.sdk.model.FirstClassByteArray
|
||||||
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
||||||
|
@ -81,7 +82,7 @@ internal interface DerivedDataRepository {
|
||||||
|
|
||||||
val allTransactions: Flow<List<DbTransactionOverview>>
|
val allTransactions: Flow<List<DbTransactionOverview>>
|
||||||
|
|
||||||
fun getSaplingOutputIndices(transactionId: FirstClassByteArray): Flow<Int>
|
fun getOutputProperties(transactionId: FirstClassByteArray): Flow<OutputProperties>
|
||||||
|
|
||||||
fun getRecipients(transactionId: FirstClassByteArray): Flow<TransactionRecipient>
|
fun getRecipients(transactionId: FirstClassByteArray): Flow<TransactionRecipient>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue