From b0f963cca7d72fa605e84dbe97d5288609c32806 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 7 Aug 2021 14:59:32 +0100 Subject: [PATCH] Added duplicate message filter for chat transactions. This tracks the last 3 unique (unencrypted) messages for each address and fails validation if a duplicate is sent. --- .../chat/ChatDuplicateMessageFilter.java | 69 +++++++++++++++++++ .../qortal/transaction/ChatTransaction.java | 10 +++ .../org/qortal/transaction/Transaction.java | 1 + 3 files changed, 80 insertions(+) create mode 100644 src/main/java/org/qortal/chat/ChatDuplicateMessageFilter.java diff --git a/src/main/java/org/qortal/chat/ChatDuplicateMessageFilter.java b/src/main/java/org/qortal/chat/ChatDuplicateMessageFilter.java new file mode 100644 index 00000000..33a8784a --- /dev/null +++ b/src/main/java/org/qortal/chat/ChatDuplicateMessageFilter.java @@ -0,0 +1,69 @@ +package org.qortal.chat; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class ChatDuplicateMessageFilter { + + private static ChatDuplicateMessageFilter instance; + private volatile boolean isStopping = false; + + private static final int numberOfUniqueMessagesToMonitor = 3; + + // Maintain a short list of recent chat messages for each address, to save having to query the database every time + private Map> recentMessages = new ConcurrentHashMap<>(); + + public ChatDuplicateMessageFilter() { + + } + + public static synchronized ChatDuplicateMessageFilter getInstance() { + if (instance == null) { + instance = new ChatDuplicateMessageFilter(); + } + + return instance; + } + + public boolean isDuplicateMessage(String address, String message) { + boolean isDuplicateMessage; + boolean messagesUpdated = false; + + // Add timestamp to array for address + List messages = new ArrayList<>(); + if (this.recentMessages.containsKey(address)) { + messages = this.recentMessages.get(address); + } + + if (!messages.contains(message)) { + messages.add(message); + this.recentMessages.put(address, messages); + messagesUpdated = true; + isDuplicateMessage = false; + } + else { + // Can't add message because it already exists + isDuplicateMessage = true; + } + + // Ensure we're not tracking more messages than intended + while (messages.size() > numberOfUniqueMessagesToMonitor) { + messages.remove(0); + messagesUpdated = true; + } + + if (messagesUpdated) { + if (messages.size() > 0) { + this.recentMessages.put(address, messages); + } + else { + this.recentMessages.remove(address); + } + } + + return isDuplicateMessage; + } + +} diff --git a/src/main/java/org/qortal/transaction/ChatTransaction.java b/src/main/java/org/qortal/transaction/ChatTransaction.java index b30be577..529a1ccb 100644 --- a/src/main/java/org/qortal/transaction/ChatTransaction.java +++ b/src/main/java/org/qortal/transaction/ChatTransaction.java @@ -6,6 +6,7 @@ import java.util.List; import org.qortal.account.Account; import org.qortal.account.PublicKeyAccount; import org.qortal.asset.Asset; +import org.qortal.chat.ChatDuplicateMessageFilter; import org.qortal.chat.ChatRateLimiter; import org.qortal.crypto.Crypto; import org.qortal.crypto.MemoryPoW; @@ -19,6 +20,7 @@ import org.qortal.repository.Repository; import org.qortal.transform.TransformationException; import org.qortal.transform.transaction.ChatTransactionTransformer; import org.qortal.transform.transaction.TransactionTransformer; +import org.qortal.utils.Base58; public class ChatTransaction extends Transaction { @@ -166,6 +168,14 @@ public class ChatTransaction extends Transaction { if (rateLimiter.isAddressAboveRateLimit(chatTransactionData.getSender())) return ValidationResult.ADDRESS_ABOVE_RATE_LIMIT; + // Check for duplicate messages (unencrypted text messages only) + if (!chatTransactionData.getIsEncrypted() && chatTransactionData.getIsText()) { + ChatDuplicateMessageFilter duplicateFilter = ChatDuplicateMessageFilter.getInstance(); + String message58 = Base58.encode(chatTransactionData.getData()); + if (duplicateFilter.isDuplicateMessage(chatTransactionData.getSender(), message58)) + return ValidationResult.DUPLICATE_MESSAGE; + } + return ValidationResult.OK; } diff --git a/src/main/java/org/qortal/transaction/Transaction.java b/src/main/java/org/qortal/transaction/Transaction.java index 9217e6f3..bd463418 100644 --- a/src/main/java/org/qortal/transaction/Transaction.java +++ b/src/main/java/org/qortal/transaction/Transaction.java @@ -249,6 +249,7 @@ public abstract class Transaction { INVALID_TIMESTAMP_SIGNATURE(95), ADDRESS_IN_BLACKLIST(96), ADDRESS_ABOVE_RATE_LIMIT(97), + DUPLICATE_MESSAGE(98), INVALID_BUT_OK(999), NOT_YET_RELEASED(1000);