[#743] Add API for reading memos

This commit is contained in:
Carter Jernigan 2022-10-24 09:09:29 -04:00 committed by Carter Jernigan
parent 4c2807aefd
commit f169b112ef
12 changed files with 187 additions and 12 deletions

View File

@ -9,6 +9,7 @@ Change Log
- `Synchronizer.getLegacySaplingAddress`
- `Synchronizer.getLegacyTransparentAddress`
- `Synchronizer.isValidUnifiedAddr`
- `Synchronizer.getMemos(TransactionOverview)`
- `cash.z.ecc.android.sdk.model`:
- `Account`
- `FirstClassByteArray`

View File

@ -45,6 +45,7 @@ import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.PendingTransaction
import cash.z.ecc.android.sdk.model.TransactionOverview
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
@ -74,8 +75,10 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
@ -99,7 +102,8 @@ import kotlin.coroutines.EmptyCoroutineContext
class SdkSynchronizer internal constructor(
private val storage: DerivedDataRepository,
private val txManager: OutboundTransactionManager,
val processor: CompactBlockProcessor
val processor: CompactBlockProcessor,
private val rustBackend: RustBackend
) : Synchronizer {
// pools
@ -318,6 +322,21 @@ class SdkSynchronizer internal constructor(
processor.quickRewind()
}
override fun getMemos(transactionOverview: TransactionOverview): Flow<String> {
return when (transactionOverview.isSentTransaction) {
true -> {
val sentNoteIds = storage.getSentNoteIds(transactionOverview.id)
sentNoteIds.map { rustBackend.getSentMemoAsUtf8(it) }.filterNotNull()
}
false -> {
val receivedNoteIds = storage.getReceivedNoteIds(transactionOverview.id)
receivedNoteIds.map { rustBackend.getReceivedMemoAsUtf8(it) }.filterNotNull()
}
}
}
//
// Storage APIs
//
@ -708,12 +727,14 @@ internal object DefaultSynchronizerFactory {
fun new(
repository: DerivedDataRepository,
txManager: OutboundTransactionManager,
processor: CompactBlockProcessor
processor: CompactBlockProcessor,
rustBackend: RustBackend
): Synchronizer {
return SdkSynchronizer(
repository,
txManager,
processor
processor,
rustBackend
)
}

View File

@ -359,6 +359,11 @@ interface Synchronizer {
suspend fun quickRewind()
/**
* Returns a list of memos for a transaction.
*/
fun getMemos(transactionOverview: TransactionOverview): Flow<String>
//
// Error Handling
//
@ -547,7 +552,8 @@ interface Synchronizer {
return SdkSynchronizer(
repository,
txManager,
processor
processor,
rustBackend
)
}

View File

@ -63,6 +63,11 @@ internal class DbDerivedDataRepository(
override val allTransactions: Flow<List<TransactionOverview>>
get() = invalidatingFlow.map { derivedDataDb.allTransactionView.getAllTransactions().toList() }
override fun getSentNoteIds(transactionId: Long) = derivedDataDb.sentNotesTable.getSentNoteIds(transactionId)
override fun getReceivedNoteIds(transactionId: Long) =
derivedDataDb.receivedNotesTable.getReceivedNoteIds(transactionId)
override suspend fun close() {
derivedDataDb.close()
}

View File

@ -13,7 +13,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
internal class DerivedDataDb private constructor(
private val zcashNetwork: ZcashNetwork,
zcashNetwork: ZcashNetwork,
private val sqliteDatabase: SupportSQLiteDatabase
) {
val accountTable = AccountTable(sqliteDatabase)
@ -28,6 +28,10 @@ internal class DerivedDataDb private constructor(
val receivedTransactionView = ReceivedTransactionView(zcashNetwork, sqliteDatabase)
val sentNotesTable = SentNoteTable(zcashNetwork, sqliteDatabase)
val receivedNotesTable = ReceivedNoteTable(zcashNetwork, sqliteDatabase)
suspend fun close() {
withContext(Dispatchers.IO) {
sqliteDatabase.close()

View File

@ -0,0 +1,68 @@
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.model.ZcashNetwork
import java.util.Locale
internal class ReceivedNoteTable(
@Suppress("UnusedPrivateMember")
private val zcashNetwork: ZcashNetwork,
private val sqliteDatabase: SupportSQLiteDatabase
) {
companion object {
private val ORDER_BY = String.format(
Locale.ROOT,
"%s ASC", // $NON-NLS
ReceivedNoteTableDefinition.COLUMN_INTEGER_ID
)
private val PROJECTION_ID = arrayOf(ReceivedNoteTableDefinition.COLUMN_INTEGER_ID)
private val SELECT_BY_TRANSACTION_ID = String.format(
Locale.ROOT,
"%s = ?", // $NON-NLS
ReceivedNoteTableDefinition.COLUMN_INTEGER_TRANSACTION_ID
)
}
fun getReceivedNoteIds(transactionId: Long) =
sqliteDatabase.queryAndMap(
table = ReceivedNoteTableDefinition.TABLE_NAME,
columns = PROJECTION_ID,
selection = SELECT_BY_TRANSACTION_ID,
selectionArgs = arrayOf(transactionId),
orderBy = ORDER_BY,
cursorParser = {
val idColumnIndex = it.getColumnIndex(ReceivedNoteTableDefinition.COLUMN_INTEGER_ID)
it.getLong(idColumnIndex)
}
)
}
// https://github.com/zcash/librustzcash/blob/277d07c79c7a08907b05a6b29730b74cdb238b97/zcash_client_sqlite/src/wallet/init.rs#L364
internal object ReceivedNoteTableDefinition {
const val TABLE_NAME = "received_notes" // $NON-NLS
const val COLUMN_INTEGER_ID = "id_note" // $NON-NLS
const val COLUMN_INTEGER_TRANSACTION_ID = "tx" // $NON-NLS
const val COLUMN_INTEGER_OUTPUT_INDEX = "output_index" // $NON-NLS
const val COLUMN_INTEGER_ACCOUNT = "account" // $NON-NLS
const val COLUMN_BLOB_DIVERSIFIER = "diversifier" // $NON-NLS
const val COLUMN_INTEGER_VALUE = "value" // $NON-NLS
const val COLUMN_BLOB_RCM = "rcm" // $NON-NLS
const val COLUMN_BLOB_NF = "nf" // $NON-NLS
const val COLUMN_BLOB_MEMO = "memo" // $NON-NLS
const val COLUMN_INTEGER_SPENT = "spent" // $NON-NLS
}

View File

@ -28,7 +28,7 @@ internal class ReceivedTransactionView(
}
suspend fun count() = sqliteDatabase.queryAndMap(
AccountTableDefinition.TABLE_NAME,
ReceivedTransactionViewDefinition.VIEW_NAME,
columns = PROJECTION_COUNT,
cursorParser = { it.getLong(0) }
).first()

View File

@ -0,0 +1,66 @@
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.model.ZcashNetwork
import java.util.Locale
internal class SentNoteTable(
@Suppress("UnusedPrivateMember")
private val zcashNetwork: ZcashNetwork,
private val sqliteDatabase: SupportSQLiteDatabase
) {
companion object {
private val ORDER_BY = String.format(
Locale.ROOT,
"%s ASC", // $NON-NLS
SentNoteTableDefinition.COLUMN_INTEGER_ID
)
private val PROJECTION_ID = arrayOf(SentNoteTableDefinition.COLUMN_INTEGER_ID)
private val SELECT_BY_TRANSACTION_ID = String.format(
Locale.ROOT,
"%s = ?", // $NON-NLS
SentNoteTableDefinition.COLUMN_INTEGER_TRANSACTION_ID
)
}
fun getSentNoteIds(transactionId: Long) =
sqliteDatabase.queryAndMap(
table = SentNoteTableDefinition.TABLE_NAME,
columns = PROJECTION_ID,
selection = SELECT_BY_TRANSACTION_ID,
selectionArgs = arrayOf(transactionId),
orderBy = ORDER_BY,
cursorParser = {
val idColumnIndex = it.getColumnIndex(SentNoteTableDefinition.COLUMN_INTEGER_ID)
it.getLong(idColumnIndex)
}
)
}
// https://github.com/zcash/librustzcash/blob/277d07c79c7a08907b05a6b29730b74cdb238b97/zcash_client_sqlite/src/wallet/init.rs#L393
internal object SentNoteTableDefinition {
const val TABLE_NAME = "sent_notes" // $NON-NLS
const val COLUMN_INTEGER_ID = "id_note" // $NON-NLS
const val COLUMN_INTEGER_TRANSACTION_ID = "tx" // $NON-NLS
const val COLUMN_INTEGER_OUTPUT_POOL = "output_pool" // $NON-NLS
const val COLUMN_INTEGER_OUTPUT_INDEX = "output_index" // $NON-NLS
const val COLUMN_INTEGER_FROM_ACCOUNT = "from_account" // $NON-NLS
const val COLUMN_STRING_TO_ADDRESS = "to_address" // $NON-NLS
const val COLUMN_INTEGER_TO_ACCOUNT = "to_account" // $NON-NLS
const val COLUMN_INTEGER_VALUE = "value" // $NON-NLS
const val COLUMN_BLOB_MEMO = "memo" // $NON-NLS
}

View File

@ -104,5 +104,9 @@ internal interface DerivedDataRepository {
/** A flow of all the inbound and outbound confirmed transactions */
val allTransactions: Flow<List<TransactionOverview>>
fun getSentNoteIds(transactionId: Long): Flow<Long>
fun getReceivedNoteIds(transactionId: Long): Flow<Long>
suspend fun close()
}

View File

@ -441,14 +441,14 @@ internal class RustBackend private constructor(
dbDataPath: String,
idNote: Long,
networkId: Int
): String
): String?
@JvmStatic
private external fun getSentMemoAsUtf8(
dbDataPath: String,
dNote: Long,
networkId: Int
): String
): String?
@JvmStatic
private external fun validateCombinedChain(

View File

@ -64,9 +64,9 @@ internal interface RustBackendWelding {
fun getBranchIdForHeight(height: BlockHeight): Long
suspend fun getReceivedMemoAsUtf8(idNote: Long): String
suspend fun getReceivedMemoAsUtf8(idNote: Long): String?
suspend fun getSentMemoAsUtf8(idNote: Long): String
suspend fun getSentMemoAsUtf8(idNote: Long): String?
suspend fun getVerifiedBalance(account: Int = 0): Zatoshi

View File

@ -5,8 +5,8 @@ package cash.z.ecc.android.sdk.model
*
* Note that both sent and received transactions will have a positive net value. Consumers of this class must
*/
data class TransactionOverview(
val id: Long,
data class TransactionOverview internal constructor(
internal val id: Long,
val rawId: FirstClassByteArray,
val minedHeight: BlockHeight,
val expiryHeight: BlockHeight,