Skip to content

feat/txmetadata update #1344

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ buildscript {
coroutinesVersion = '1.6.4'
ok_http_version = '4.9.1'
dashjVersion = '21.1.5'
dppVersion = "1.7.1"
dppVersion = "1.7.2-SNAPSHOT"
hiltVersion = '2.51'
hiltCompilerVersion = '1.2.0'
hiltWorkVersion = '1.0.0'
Expand Down
4 changes: 4 additions & 0 deletions wallet/src/de/schildbach/wallet/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ public final class Constants {

public static final boolean IS_PROD_BUILD;

// 32-bit devices do not support platform
public static boolean SUPPORTS_PLATFORM;
// TODO: remove all references to this when invites are enabled and functional
public static boolean SUPPORTS_INVITES;
public static boolean SUPPORTS_TXMETADATA;

public static final EnumSet<MasternodeSync.SYNC_FLAGS> SYNC_FLAGS = MasternodeSync.SYNC_DEFAULT_SPV;
public static final EnumSet<MasternodeSync.VERIFY_FLAGS> VERIFY_FLAGS = MasternodeSync.VERIFY_DEFAULT_SPV;
Expand Down Expand Up @@ -92,6 +94,7 @@ public final class Constants {
org.dash.wallet.common.util.Constants.INSTANCE.setEXPLORE_GC_FILE_PATH("explore/explore.db");
SUPPORTS_PLATFORM = !is32Bit;
SUPPORTS_INVITES = false;
SUPPORTS_TXMETADATA = false;
SYNC_FLAGS.add(MasternodeSync.SYNC_FLAGS.SYNC_HEADERS_MN_LIST_FIRST);
if (SUPPORTS_PLATFORM) {
SYNC_FLAGS.add(MasternodeSync.SYNC_FLAGS.SYNC_BLOCKS_AFTER_PREPROCESSING);
Expand All @@ -110,6 +113,7 @@ public final class Constants {
WALLET_NAME_CURRENCY_CODE = "tdash";
SUPPORTS_PLATFORM = !is32Bit;
SUPPORTS_INVITES = false;
SUPPORTS_TXMETADATA = true;
SYNC_FLAGS.add(MasternodeSync.SYNC_FLAGS.SYNC_HEADERS_MN_LIST_FIRST);
if (SUPPORTS_PLATFORM) {
SYNC_FLAGS.add(MasternodeSync.SYNC_FLAGS.SYNC_BLOCKS_AFTER_PREPROCESSING);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ interface TransactionMetadataChangeCacheDao {
cacheTimestamp: Long = System.currentTimeMillis()
)

@Query("SELECT COUNT(*) FROM transaction_metadata_cache")
suspend fun count(): Int

@Query("DELETE FROM transaction_metadata_cache")
suspend fun clear()
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import de.schildbach.wallet.database.entity.TransactionMetadataCacheItem
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import okhttp3.*
import org.bitcoinj.coinjoin.utils.CoinJoinTransactionType
import org.bitcoinj.core.*
import org.bitcoinj.core.Address
import org.bitcoinj.script.ScriptPattern
Expand Down Expand Up @@ -131,8 +132,12 @@ class WalletTransactionMetadataProvider @Inject constructor(
service = platformService
)
transactionMetadataDao.insert(metadata)
// only add to the change cache if some metadata exists
if (metadata.isNotEmpty() && !isSyncingPlatform && hasChanges) {
// only add to the change cache if some metadata exists and this is not a CoinJoin tx
val isCoinJoinTx = when (CoinJoinTransactionType.fromTx(this, walletData.wallet!!)) {
CoinJoinTransactionType.None, CoinJoinTransactionType.Send -> false
else -> true
}
if (!isCoinJoinTx && metadata.isNotEmpty() && !isSyncingPlatform && hasChanges) {
transactionMetadataChangeCacheDao.insert(TransactionMetadataCacheItem(metadata))
}
log.info("txmetadata: inserting $metadata")
Expand Down Expand Up @@ -354,12 +359,16 @@ class WalletTransactionMetadataProvider @Inject constructor(
val exchangeRate = tx.exchangeRate
// sync exchange rates
if (metadata.rate != null && tx.exchangeRate == null) {
tx.exchangeRate = org.bitcoinj.utils.ExchangeRate(
Fiat.parseFiat(
metadata.currencyCode,
metadata.rate
try {
tx.exchangeRate = org.bitcoinj.utils.ExchangeRate(
Fiat.parseFiat(
metadata.currencyCode,
metadata.rate
)
)
)
} catch (e: Exception) {
log.error("Failed to parse exchange rate for metadata: {}. Error: {}", metadata, e.message, e)
}
} else if (metadata.rate == null && exchangeRate != null) {
transactionMetadataDao.updateExchangeRate(
tx.txId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,14 @@ import org.dash.wallet.common.services.TransactionMetadataProvider
import org.dash.wallet.common.services.analytics.AnalyticsService
import org.dash.wallet.common.transactions.TransactionCategory
import org.dash.wallet.common.util.TickerFlow
import org.dashj.platform.contracts.wallet.TxMetadataItem
import org.dashj.platform.contracts.wallet.TxMetadataDocument
import org.dashj.platform.dashpay.ContactRequest
import org.dashj.platform.dashpay.UsernameRequestStatus
import org.dashj.platform.dpp.identifier.Identifier
import org.dashj.platform.dpp.voting.ContestedDocumentResourceVotePoll
import org.dashj.platform.sdk.platform.DomainDocument
import org.dashj.platform.wallet.IdentityVerify
import org.dashj.platform.wallet.TxMetadataItem
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.concurrent.Executors
Expand Down Expand Up @@ -140,6 +141,9 @@ class PlatformSynchronizationService @Inject constructor(
val PUSH_PERIOD = if (BuildConfig.DEBUG) 3.minutes else 3.hours
val CUTOFF_MIN = if (BuildConfig.DEBUG) 3.minutes else 3.hours
val CUTOFF_MAX = if (BuildConfig.DEBUG) 6.minutes else 6.hours
const val MIN_TXMETADATA_BATCHSIZE = 2
const val MAX_TXMETADATA_BATCHSIZE = 35
val MIN_TXMETADATA_DURATION = 168.hours
}

private var platformSyncJob: Job? = null
Expand Down Expand Up @@ -173,18 +177,22 @@ class PlatformSynchronizationService @Inject constructor(
.launchIn(syncScope)

syncScope.launch {
val lastPush = config.get(DashPayConfig.LAST_METADATA_PUSH) ?: 0
val now = System.currentTimeMillis()

if (lastPush < now - PUSH_PERIOD.inWholeMilliseconds) {
val everythingBeforeTimestamp = random.nextLong(
now - CUTOFF_MAX.inWholeMilliseconds,
now - CUTOFF_MIN.inWholeMilliseconds
) // Choose cutoff time between 3 and 6 hours ago
publishChangeCache(everythingBeforeTimestamp)
} else {
log.info("last platform push was less than 3 hours ago, skipping")
}
maybePublishChangeCache()
}
}

private suspend fun PlatformSynchronizationService.maybePublishChangeCache() {
val lastPush = config.get(DashPayConfig.LAST_METADATA_PUSH) ?: 0
val now = System.currentTimeMillis()

if (lastPush < now - PUSH_PERIOD.inWholeMilliseconds) {
val everythingBeforeTimestamp = random.nextLong(
now - CUTOFF_MAX.inWholeMilliseconds,
now - CUTOFF_MIN.inWholeMilliseconds
) // Choose cutoff time between 3 and 6 hours ago
publishChangeCache(everythingBeforeTimestamp)
} else {
log.info("last platform push was less than 3 hours ago, skipping")
}
}

Expand Down Expand Up @@ -692,6 +700,9 @@ class PlatformSynchronizationService @Inject constructor(
}

private suspend fun updateTransactionMetadata() {
if (!Constants.SUPPORTS_TXMETADATA) {
return
}
val watch = Stopwatch.createStarted()
val myEncryptionKey = platformRepo.getWalletEncryptionKey()

Expand Down Expand Up @@ -726,7 +737,7 @@ class PlatformSynchronizationService @Inject constructor(

items.forEach { (doc, list) ->
if (transactionMetadataDocumentDao.count(doc.id) == 0) {
val timestamp = doc.createdAt!!
val timestamp = doc.updatedAt!!
log.info("processing TxMetadata: ${doc.id} with ${list.size} items")
list.forEach { metadata ->
if (metadata.isNotEmpty()) {
Expand All @@ -747,7 +758,7 @@ class PlatformSynchronizationService @Inject constructor(
val txIdAsHash = Sha256Hash.wrap(metadata.txId)
val metadataDocumentRecord = TransactionMetadataDocument(
doc.id,
doc.createdAt!!,
doc.updatedAt!!,
txIdAsHash
)
val updatedMetadata = TransactionMetadata(txIdAsHash, 0, Coin.ZERO, TransactionCategory.Invalid)
Expand All @@ -758,7 +769,7 @@ class PlatformSynchronizationService @Inject constructor(
metadataDocumentRecord.sentTimestamp = timestamp
log.info("processing TxMetadata: sent time stamp")
if (cachedItems.find {
it.txId == txIdAsHash && it.cacheTimestamp > doc.createdAt!! &&
it.txId == txIdAsHash && it.cacheTimestamp > doc.updatedAt!! &&
it.sentTimestamp != null && it.sentTimestamp != timestamp
} == null
) {
Expand All @@ -770,7 +781,7 @@ class PlatformSynchronizationService @Inject constructor(
metadataDocumentRecord.service = service
log.info("processing TxMetadata: service change")
if (cachedItems.find {
it.txId == txIdAsHash && it.cacheTimestamp > doc.createdAt!! &&
it.txId == txIdAsHash && it.cacheTimestamp > doc.updatedAt!! &&
it.service != null && it.service != service
} == null
) {
Expand All @@ -783,12 +794,12 @@ class PlatformSynchronizationService @Inject constructor(
log.info(
"processing TxMetadata: memo change: {}",
cachedItems.find {
it.txId == txIdAsHash && it.cacheTimestamp > doc.createdAt!! &&
it.txId == txIdAsHash && it.cacheTimestamp > doc.updatedAt!! &&
it.memo != null && it.memo != memo
}
)
if (cachedItems.find {
it.txId == txIdAsHash && it.cacheTimestamp > doc.createdAt!! &&
it.txId == txIdAsHash && it.cacheTimestamp > doc.updatedAt!! &&
it.memo != null && it.memo != memo
} == null
) {
Expand All @@ -801,7 +812,7 @@ class PlatformSynchronizationService @Inject constructor(
metadataDocumentRecord.taxCategory = taxCategory
log.info("processing TxMetadata: tax category change")
if (cachedItems.find {
it.txId == txIdAsHash && it.cacheTimestamp > doc.createdAt!! &&
it.txId == txIdAsHash && it.cacheTimestamp > doc.updatedAt!! &&
it.taxCategory != null && it.taxCategory?.name != taxCategoryAsString
} == null
) {
Expand All @@ -815,7 +826,7 @@ class PlatformSynchronizationService @Inject constructor(
metadataDocumentRecord.currencyCode = metadata.currencyCode

val prevItem = cachedItems.find {
it.txId == txIdAsHash && it.cacheTimestamp > doc.createdAt!! &&
it.txId == txIdAsHash && it.cacheTimestamp > doc.updatedAt!! &&
it.currencyCode != null && it.rate != null &&
(
it.currencyCode != metadata.currencyCode ||
Expand All @@ -824,7 +835,7 @@ class PlatformSynchronizationService @Inject constructor(
}
log.info("processing TxMetadata: exchange rate change change: $prevItem")
if (cachedItems.find {
it.txId == txIdAsHash && it.cacheTimestamp > doc.createdAt!! &&
it.txId == txIdAsHash && it.cacheTimestamp > doc.updatedAt!! &&
it.currencyCode != null && it.rate != null &&
(
it.currencyCode != metadata.currencyCode ||
Expand All @@ -841,7 +852,7 @@ class PlatformSynchronizationService @Inject constructor(
metadataDocumentRecord.customIconUrl = url
log.info("processing TxMetadata: custom icon url change")
if (cachedItems.find {
it.txId == txIdAsHash && it.cacheTimestamp > doc.createdAt!! &&
it.txId == txIdAsHash && it.cacheTimestamp > doc.updatedAt!! &&
it.customIconUrl != null && it.customIconUrl != url
} == null
) {
Expand All @@ -853,7 +864,7 @@ class PlatformSynchronizationService @Inject constructor(
metadataDocumentRecord.giftCardNumber = number
log.info("processing TxMetadata: gift card number change")
if (cachedItems.find {
it.txId == txIdAsHash && it.cacheTimestamp > doc.createdAt!! &&
it.txId == txIdAsHash && it.cacheTimestamp > doc.updatedAt!! &&
it.giftCardNumber != null && it.giftCardNumber != number
} == null
) {
Expand All @@ -865,7 +876,7 @@ class PlatformSynchronizationService @Inject constructor(
metadataDocumentRecord.giftCardPin = pin
log.info("processing TxMetadata: gift card pin change")
if (cachedItems.find {
it.txId == txIdAsHash && it.cacheTimestamp > doc.createdAt!! &&
it.txId == txIdAsHash && it.cacheTimestamp > doc.updatedAt!! &&
it.giftCardPin != null && it.giftCardPin != pin
} == null
) {
Expand All @@ -877,7 +888,7 @@ class PlatformSynchronizationService @Inject constructor(
metadataDocumentRecord.merchantName = name
log.info("processing TxMetadata: merchant name change")
if (cachedItems.find {
it.txId == txIdAsHash && it.cacheTimestamp > doc.createdAt!! &&
it.txId == txIdAsHash && it.cacheTimestamp > doc.updatedAt!! &&
it.merchantName != null && it.merchantName != name
} == null
) {
Expand All @@ -901,7 +912,7 @@ class PlatformSynchronizationService @Inject constructor(
metadataDocumentRecord.barcodeValue = barcodeValue
log.info("processing TxMetadata: barcode value change")
if (cachedItems.find {
it.txId == txIdAsHash && it.cacheTimestamp > doc.createdAt!! &&
it.txId == txIdAsHash && it.cacheTimestamp > doc.updatedAt!! &&
it.barcodeValue != null && it.barcodeValue != barcodeValue
} == null
) {
Expand All @@ -913,19 +924,23 @@ class PlatformSynchronizationService @Inject constructor(
metadataDocumentRecord.barcodeFormat = barcodeFormat
log.info("processing TxMetadata: barcode format change")
if (cachedItems.find {
it.txId == txIdAsHash && it.cacheTimestamp > doc.createdAt!! &&
it.txId == txIdAsHash && it.cacheTimestamp > doc.updatedAt!! &&
it.barcodeFormat != null && it.barcodeFormat != barcodeFormat
} == null
) {
log.info("processing TxMetadata: barcode value change: changing value")
giftCard.barcodeFormat = BarcodeFormat.valueOf(barcodeFormat)
try {
giftCard.barcodeFormat = BarcodeFormat.valueOf(barcodeFormat)
} catch (e: IllegalArgumentException) {
log.warn("Invalid barcode format: {}", barcodeFormat, e)
}
}
}
metadata.merchantUrl?.let { url ->
metadataDocumentRecord.merchantUrl = url
log.info("processing TxMetadata: merchant url change")
if (cachedItems.find {
it.txId == txIdAsHash && it.cacheTimestamp > doc.createdAt!! &&
it.txId == txIdAsHash && it.cacheTimestamp > doc.updatedAt!! &&
it.merchantUrl != null && it.merchantUrl != url
} == null
) {
Expand All @@ -952,7 +967,7 @@ class PlatformSynchronizationService @Inject constructor(
log.info("fetching ${items.size} tx metadata items in $watch")
}

private fun publishTransactionMetadata(txMetadataItems: List<TransactionMetadataCacheItem>) {
private suspend fun publishTransactionMetadata(txMetadataItems: List<TransactionMetadataCacheItem>) {
if (!platformRepo.hasIdentity) {
return
}
Expand All @@ -977,10 +992,14 @@ class PlatformSynchronizationService @Inject constructor(
)
}
val walletEncryptionKey = platformRepo.getWalletEncryptionKey()
platformRepo.blockchainIdentity.publishTxMetaData(metadataList, walletEncryptionKey)
val keyIndex = transactionMetadataChangeCacheDao.count() + transactionMetadataDocumentDao.countAllRequests()
platformRepo.blockchainIdentity.publishTxMetaData(metadataList, walletEncryptionKey, keyIndex, TxMetadataDocument.VERSION_PROTOBUF)
}

private suspend fun publishChangeCache(before: Long) {
if (!Constants.SUPPORTS_TXMETADATA) {
return
}
log.info("publishing updates to tx metadata items before $before")
val itemsToPublish = hashMapOf<Sha256Hash, TransactionMetadataCacheItem>()
val changedItems = transactionMetadataChangeCacheDao.findAllBefore(before)
Expand All @@ -990,6 +1009,11 @@ class PlatformSynchronizationService @Inject constructor(
return
}

// if (changedItems.size < MIN_TXMETADATA_BATCHSIZE) {
// log.info("not enough tx metadata changes before this time")
// return
// }

log.info("preparing to publish ${changedItems.size} tx metadata changes to platform")

for (changedItem in changedItems) {
Expand Down
Loading