[#743] Add API for reading memos
This commit is contained in:
parent
4c2807aefd
commit
f169b112ef
|
@ -9,6 +9,7 @@ Change Log
|
|||
- `Synchronizer.getLegacySaplingAddress`
|
||||
- `Synchronizer.getLegacyTransparentAddress`
|
||||
- `Synchronizer.isValidUnifiedAddr`
|
||||
- `Synchronizer.getMemos(TransactionOverview)`
|
||||
- `cash.z.ecc.android.sdk.model`:
|
||||
- `Account`
|
||||
- `FirstClassByteArray`
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue