mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-25 19:29:54 +01:00
Add import and tombstones for mobile coin payments.
This commit is contained in:
@@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupCall
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.IndividualCall
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.MessageAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.PaymentNotification
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Quote
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Reaction
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.SendStatus
|
||||
@@ -46,22 +47,31 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailureSet
|
||||
import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil
|
||||
import org.thoughtcrime.securesms.database.model.Mention
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.CryptoValue
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.PaymentTombstone
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileChangeDetails
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel
|
||||
import org.thoughtcrime.securesms.payments.CryptoValueUtil
|
||||
import org.thoughtcrime.securesms.payments.Direction
|
||||
import org.thoughtcrime.securesms.payments.State
|
||||
import org.thoughtcrime.securesms.payments.proto.PaymentMetaData
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import org.whispersystems.signalservice.api.backup.MediaName
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
|
||||
import org.whispersystems.signalservice.api.payments.Money
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||
import org.whispersystems.signalservice.internal.push.DataMessage
|
||||
import java.math.BigInteger
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* An object that will ingest all fo the [ChatItem]s you want to write, buffer them until hitting a specified batch size, and then batch insert them
|
||||
@@ -283,6 +293,22 @@ class ChatItemImportInserter(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.paymentNotification != null) {
|
||||
followUp = { messageRowId ->
|
||||
val uuid = tryRestorePayment(this, chatRecipientId)
|
||||
if (uuid != null) {
|
||||
db.update(
|
||||
MessageTable.TABLE_NAME,
|
||||
contentValuesOf(
|
||||
MessageTable.BODY to uuid.toString(),
|
||||
MessageTable.TYPE to ((contentValues.getAsLong(MessageTable.TYPE) and MessageTypes.SPECIAL_TYPES_MASK.inv()) or MessageTypes.SPECIAL_TYPE_PAYMENTS_NOTIFICATION)
|
||||
),
|
||||
"${MessageTable.ID}=?",
|
||||
SqlUtil.buildArgs(messageRowId)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.standardMessage != null) {
|
||||
val bodyRanges = this.standardMessage.text?.bodyRanges
|
||||
if (!bodyRanges.isNullOrEmpty()) {
|
||||
@@ -370,11 +396,42 @@ class ChatItemImportInserter(
|
||||
this.standardMessage != null -> contentValues.addStandardMessage(this.standardMessage)
|
||||
this.remoteDeletedMessage != null -> contentValues.put(MessageTable.REMOTE_DELETED, 1)
|
||||
this.updateMessage != null -> contentValues.addUpdateMessage(this.updateMessage)
|
||||
this.paymentNotification != null -> contentValues.addPaymentNotification(this, chatRecipientId)
|
||||
}
|
||||
|
||||
return contentValues
|
||||
}
|
||||
|
||||
private fun tryRestorePayment(chatItem: ChatItem, chatRecipientId: RecipientId): UUID? {
|
||||
val paymentNotification = chatItem.paymentNotification!!
|
||||
|
||||
val amount = paymentNotification.amountMob?.tryParseMoney() ?: return null
|
||||
val fee = paymentNotification.feeMob?.tryParseMoney() ?: return null
|
||||
|
||||
if (paymentNotification.transactionDetails?.failedTransaction != null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val transaction = paymentNotification.transactionDetails?.transaction
|
||||
|
||||
val mobileCoinIdentification = transaction?.mobileCoinIdentification?.toLocal() ?: return null
|
||||
|
||||
return SignalDatabase.payments.restoreFromBackup(
|
||||
chatRecipientId,
|
||||
transaction.timestamp ?: 0,
|
||||
transaction.blockIndex ?: 0,
|
||||
paymentNotification.note ?: "",
|
||||
if (chatItem.outgoing != null) Direction.SENT else Direction.RECEIVED,
|
||||
transaction.status.toLocalStatus(),
|
||||
amount,
|
||||
fee,
|
||||
transaction.transaction?.toByteArray(),
|
||||
transaction.receipt?.toByteArray(),
|
||||
mobileCoinIdentification,
|
||||
chatItem.incoming?.read ?: true
|
||||
)
|
||||
}
|
||||
|
||||
private fun ChatItem.toReactionContentValues(messageId: Long): List<ContentValues> {
|
||||
val reactions: List<Reaction> = when {
|
||||
this.standardMessage != null -> this.standardMessage.reactions
|
||||
@@ -551,6 +608,104 @@ class ChatItemImportInserter(
|
||||
this.put(MessageTable.TYPE, typeFlags)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the payment notification to the chat item.
|
||||
*
|
||||
* Note we add a tombstone first, then post insertion update it to a proper notification
|
||||
*/
|
||||
private fun ContentValues.addPaymentNotification(chatItem: ChatItem, chatRecipientId: RecipientId) {
|
||||
val paymentNotification = chatItem.paymentNotification!!
|
||||
if (chatItem.paymentNotification.amountMob.isNullOrEmpty()) {
|
||||
addPaymentTombstoneNoAmount()
|
||||
return
|
||||
}
|
||||
val amount = paymentNotification.amountMob?.tryParseMoney() ?: return addPaymentTombstoneNoAmount()
|
||||
val fee = paymentNotification.feeMob?.tryParseMoney() ?: return addPaymentTombstoneNoAmount()
|
||||
|
||||
if (chatItem.paymentNotification.transactionDetails?.failedTransaction != null) {
|
||||
addFailedPaymentNotification(chatItem, amount, fee, chatRecipientId)
|
||||
return
|
||||
}
|
||||
addPaymentTombstoneNoMetadata(chatItem.paymentNotification)
|
||||
}
|
||||
|
||||
private fun PaymentNotification.TransactionDetails.MobileCoinTxoIdentification.toLocal(): PaymentMetaData {
|
||||
return PaymentMetaData(
|
||||
mobileCoinTxoIdentification = PaymentMetaData.MobileCoinTxoIdentification(
|
||||
publicKey = this.publicKey,
|
||||
keyImages = this.keyImages
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun ContentValues.addFailedPaymentNotification(chatItem: ChatItem, amount: Money, fee: Money, chatRecipientId: RecipientId) {
|
||||
val uuid = SignalDatabase.payments.restoreFromBackup(
|
||||
chatRecipientId,
|
||||
0,
|
||||
0,
|
||||
chatItem.paymentNotification?.note ?: "",
|
||||
if (chatItem.outgoing != null) Direction.SENT else Direction.RECEIVED,
|
||||
State.FAILED,
|
||||
amount,
|
||||
fee,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
chatItem.incoming?.read ?: true
|
||||
)
|
||||
if (uuid != null) {
|
||||
put(MessageTable.BODY, uuid.toString())
|
||||
put(MessageTable.TYPE, getAsLong(MessageTable.TYPE) or MessageTypes.SPECIAL_TYPE_PAYMENTS_NOTIFICATION)
|
||||
} else {
|
||||
addPaymentTombstoneNoMetadata(chatItem.paymentNotification!!)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ContentValues.addPaymentTombstoneNoAmount() {
|
||||
put(MessageTable.TYPE, getAsLong(MessageTable.TYPE) or MessageTypes.SPECIAL_TYPE_PAYMENTS_TOMBSTONE)
|
||||
}
|
||||
|
||||
private fun ContentValues.addPaymentTombstoneNoMetadata(paymentNotification: PaymentNotification) {
|
||||
put(MessageTable.TYPE, getAsLong(MessageTable.TYPE) or MessageTypes.SPECIAL_TYPE_PAYMENTS_TOMBSTONE)
|
||||
val amount = tryParseCryptoValue(paymentNotification.amountMob)
|
||||
val fee = tryParseCryptoValue(paymentNotification.feeMob)
|
||||
put(
|
||||
MessageTable.MESSAGE_EXTRAS,
|
||||
MessageExtras(
|
||||
paymentTombstone = PaymentTombstone(
|
||||
note = paymentNotification.note,
|
||||
amount = amount,
|
||||
fee = fee
|
||||
)
|
||||
).encode()
|
||||
)
|
||||
}
|
||||
|
||||
private fun String?.tryParseMoney(): Money? {
|
||||
if (this.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val amountCryptoValue = tryParseCryptoValue(this)
|
||||
return if (amountCryptoValue != null) {
|
||||
CryptoValueUtil.cryptoValueToMoney(amountCryptoValue)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryParseCryptoValue(bigIntegerString: String?): CryptoValue? {
|
||||
if (bigIntegerString == null) {
|
||||
return null
|
||||
}
|
||||
val amount = try {
|
||||
BigInteger(bigIntegerString).toString()
|
||||
} catch (e: NumberFormatException) {
|
||||
return null
|
||||
}
|
||||
return CryptoValue(mobileCoinValue = CryptoValue.MobileCoinValue(picoMobileCoin = amount))
|
||||
}
|
||||
|
||||
private fun ContentValues.addQuote(quote: Quote) {
|
||||
this.put(MessageTable.QUOTE_ID, quote.targetSentTimestamp ?: MessageTable.QUOTE_TARGET_MISSING_ID)
|
||||
this.put(MessageTable.QUOTE_AUTHOR, backupState.backupToLocalRecipientId[quote.authorId]!!.serialize())
|
||||
@@ -561,6 +716,15 @@ class ChatItemImportInserter(
|
||||
this.put(MessageTable.QUOTE_MISSING, (quote.targetSentTimestamp == null).toInt())
|
||||
}
|
||||
|
||||
private fun PaymentNotification.TransactionDetails.Transaction.Status?.toLocalStatus(): State {
|
||||
return when (this) {
|
||||
PaymentNotification.TransactionDetails.Transaction.Status.INITIAL -> State.INITIAL
|
||||
PaymentNotification.TransactionDetails.Transaction.Status.SUBMITTED -> State.SUBMITTED
|
||||
PaymentNotification.TransactionDetails.Transaction.Status.SUCCESSFUL -> State.SUCCESSFUL
|
||||
else -> State.INITIAL
|
||||
}
|
||||
}
|
||||
|
||||
private fun Quote.Type.toLocalQuoteType(): Int {
|
||||
return when (this) {
|
||||
Quote.Type.UNKNOWN -> QuoteModel.Type.NORMAL.code
|
||||
|
||||
Reference in New Issue
Block a user