From 910191b07443dc4ea42d90f466e9f4ca8bb8c596 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 21 Oct 2022 15:58:23 +0100 Subject: [PATCH 1/5] Added optional chatReference field to CHAT transactions. This allows one message to reference another, e.g. for replies, edits, and reactions. We can't use the existing reference field as this is used for encryption and generally points to the user's lastReference at the time of signing. "chatReference" is based on the "nameReference" field used in various name transactions, for similar purposes. This needs a feature trigger timestamp to activate, and that same timestamp will need to be used in the UI since that is responsible for building the chat transactions. --- .../org/qortal/api/resource/ChatResource.java | 6 ++++ .../api/websocket/ChatMessagesWebSocket.java | 2 ++ .../java/org/qortal/block/BlockChain.java | 7 ++++- .../data/transaction/ChatTransactionData.java | 9 +++++- .../org/qortal/repository/ChatRepository.java | 2 +- .../hsqldb/HSQLDBChatRepository.java | 7 ++++- .../hsqldb/HSQLDBDatabaseUpdates.java | 6 ++++ .../HSQLDBChatTransactionRepository.java | 5 +-- .../ChatTransactionTransformer.java | 31 +++++++++++++++++-- src/main/resources/blockchain.json | 3 +- .../test-chain-v2-block-timestamps.json | 3 +- .../test-chain-v2-disable-reference.json | 3 +- .../test-chain-v2-founder-rewards.json | 3 +- .../test-chain-v2-leftover-reward.json | 3 +- src/test/resources/test-chain-v2-minting.json | 3 +- .../test-chain-v2-qora-holder-extremes.json | 3 +- .../test-chain-v2-qora-holder-reduction.json | 3 +- .../resources/test-chain-v2-qora-holder.json | 3 +- .../test-chain-v2-reward-levels.json | 3 +- .../test-chain-v2-reward-scaling.json | 3 +- .../test-chain-v2-reward-shares.json | 3 +- src/test/resources/test-chain-v2.json | 3 +- 22 files changed, 93 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/qortal/api/resource/ChatResource.java b/src/main/java/org/qortal/api/resource/ChatResource.java index ee2a8599..8c0f94c3 100644 --- a/src/main/java/org/qortal/api/resource/ChatResource.java +++ b/src/main/java/org/qortal/api/resource/ChatResource.java @@ -70,6 +70,7 @@ public class ChatResource { @QueryParam("txGroupId") Integer txGroupId, @QueryParam("involving") List involvingAddresses, @QueryParam("reference") String reference, + @QueryParam("chatreference") String chatReference, @Parameter(ref = "limit") @QueryParam("limit") Integer limit, @Parameter(ref = "offset") @QueryParam("offset") Integer offset, @Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse) { @@ -92,12 +93,17 @@ public class ChatResource { if (reference != null) referenceBytes = Base58.decode(reference); + byte[] chatReferenceBytes = null; + if (chatReference != null) + chatReferenceBytes = Base58.decode(chatReference); + try (final Repository repository = RepositoryManager.getRepository()) { return repository.getChatRepository().getMessagesMatchingCriteria( before, after, txGroupId, referenceBytes, + chatReferenceBytes, involvingAddresses, limit, offset, reverse); } catch (DataException e) { diff --git a/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java b/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java index 9760b7f0..dbe36d9f 100644 --- a/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java @@ -47,6 +47,7 @@ public class ChatMessagesWebSocket extends ApiWebSocket { txGroupId, null, null, + null, null, null, null); sendMessages(session, chatMessages); @@ -74,6 +75,7 @@ public class ChatMessagesWebSocket extends ApiWebSocket { null, null, null, + null, involvingAddresses, null, null, null); diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index 42692a18..d483f8d7 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -73,7 +73,8 @@ public class BlockChain { calcChainWeightTimestamp, transactionV5Timestamp, transactionV6Timestamp, - disableReferenceTimestamp; + disableReferenceTimestamp, + chatReferenceTimestamp; } // Custom transaction fees @@ -486,6 +487,10 @@ public class BlockChain { return this.featureTriggers.get(FeatureTrigger.disableReferenceTimestamp.name()).longValue(); } + public long getChatReferenceTimestamp() { + return this.featureTriggers.get(FeatureTrigger.chatReferenceTimestamp.name()).longValue(); + } + // More complex getters for aspects that change by height or timestamp diff --git a/src/main/java/org/qortal/data/transaction/ChatTransactionData.java b/src/main/java/org/qortal/data/transaction/ChatTransactionData.java index 36ce6124..81bdb2b7 100644 --- a/src/main/java/org/qortal/data/transaction/ChatTransactionData.java +++ b/src/main/java/org/qortal/data/transaction/ChatTransactionData.java @@ -26,6 +26,8 @@ public class ChatTransactionData extends TransactionData { private String recipient; // can be null + private byte[] chatReference; // can be null + @Schema(description = "raw message data, possibly UTF8 text", example = "2yGEbwRFyhPZZckKA") private byte[] data; @@ -44,13 +46,14 @@ public class ChatTransactionData extends TransactionData { } public ChatTransactionData(BaseTransactionData baseTransactionData, - String sender, int nonce, String recipient, byte[] data, boolean isText, boolean isEncrypted) { + String sender, int nonce, String recipient, byte[] chatReference, byte[] data, boolean isText, boolean isEncrypted) { super(TransactionType.CHAT, baseTransactionData); this.senderPublicKey = baseTransactionData.creatorPublicKey; this.sender = sender; this.nonce = nonce; this.recipient = recipient; + this.chatReference = chatReference; this.data = data; this.isText = isText; this.isEncrypted = isEncrypted; @@ -78,6 +81,10 @@ public class ChatTransactionData extends TransactionData { return this.recipient; } + public byte[] getChatReference() { + return this.chatReference; + } + public byte[] getData() { return this.data; } diff --git a/src/main/java/org/qortal/repository/ChatRepository.java b/src/main/java/org/qortal/repository/ChatRepository.java index 2ecd8a34..ebdc22e4 100644 --- a/src/main/java/org/qortal/repository/ChatRepository.java +++ b/src/main/java/org/qortal/repository/ChatRepository.java @@ -14,7 +14,7 @@ public interface ChatRepository { * Expects EITHER non-null txGroupID OR non-null sender and recipient addresses. */ public List getMessagesMatchingCriteria(Long before, Long after, - Integer txGroupId, byte[] reference, List involving, + Integer txGroupId, byte[] reference, byte[] chatReferenceBytes, List involving, Integer limit, Integer offset, Boolean reverse) throws DataException; public ChatMessage toChatMessage(ChatTransactionData chatTransactionData) throws DataException; diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java index 2f570686..d4c9d7e0 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java @@ -24,7 +24,7 @@ public class HSQLDBChatRepository implements ChatRepository { @Override public List getMessagesMatchingCriteria(Long before, Long after, Integer txGroupId, byte[] referenceBytes, - List involving, Integer limit, Integer offset, Boolean reverse) + byte[] chatReferenceBytes, List involving, Integer limit, Integer offset, Boolean reverse) throws DataException { // Check args meet expectations if ((txGroupId != null && involving != null && !involving.isEmpty()) @@ -62,6 +62,11 @@ public class HSQLDBChatRepository implements ChatRepository { bindParams.add(referenceBytes); } + if (chatReferenceBytes != null) { + whereClauses.add("chat_reference = ?"); + bindParams.add(chatReferenceBytes); + } + if (txGroupId != null) { whereClauses.add("tx_group_id = " + txGroupId); // int safe to use literally whereClauses.add("recipient IS NULL"); diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java index 1174f5c8..53458484 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -975,6 +975,12 @@ public class HSQLDBDatabaseUpdates { stmt.execute("ALTER TABLE TradeBotStates ALTER COLUMN receiving_account_info SET DATA TYPE VARBINARY(128)"); break; + case 44: + // Add a chat reference, to allow one message to reference another, and for this to be easily + // searchable. Null values are allowed as most transactions won't have a reference. + stmt.execute("ALTER TABLE ChatTransactions ADD chat_reference Signature"); + break; + default: // nothing to do return false; diff --git a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBChatTransactionRepository.java b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBChatTransactionRepository.java index 449922f4..0dd3c0e3 100644 --- a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBChatTransactionRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBChatTransactionRepository.java @@ -17,7 +17,7 @@ public class HSQLDBChatTransactionRepository extends HSQLDBTransactionRepository } TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException { - String sql = "SELECT sender, nonce, recipient, is_text, is_encrypted, data FROM ChatTransactions WHERE signature = ?"; + String sql = "SELECT sender, nonce, recipient, is_text, is_encrypted, data, chat_reference FROM ChatTransactions WHERE signature = ?"; try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) { if (resultSet == null) @@ -29,8 +29,9 @@ public class HSQLDBChatTransactionRepository extends HSQLDBTransactionRepository boolean isText = resultSet.getBoolean(4); boolean isEncrypted = resultSet.getBoolean(5); byte[] data = resultSet.getBytes(6); + byte[] chatReference = resultSet.getBytes(7); - return new ChatTransactionData(baseTransactionData, sender, nonce, recipient, data, isText, isEncrypted); + return new ChatTransactionData(baseTransactionData, sender, nonce, recipient, chatReference, data, isText, isEncrypted); } catch (SQLException e) { throw new DataException("Unable to fetch chat transaction from repository", e); } diff --git a/src/main/java/org/qortal/transform/transaction/ChatTransactionTransformer.java b/src/main/java/org/qortal/transform/transaction/ChatTransactionTransformer.java index 69a9ef5b..d482dacd 100644 --- a/src/main/java/org/qortal/transform/transaction/ChatTransactionTransformer.java +++ b/src/main/java/org/qortal/transform/transaction/ChatTransactionTransformer.java @@ -4,6 +4,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import org.qortal.block.BlockChain; import org.qortal.crypto.Crypto; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.data.transaction.ChatTransactionData; @@ -22,11 +23,13 @@ public class ChatTransactionTransformer extends TransactionTransformer { private static final int NONCE_LENGTH = INT_LENGTH; private static final int HAS_RECIPIENT_LENGTH = BOOLEAN_LENGTH; private static final int RECIPIENT_LENGTH = ADDRESS_LENGTH; + private static final int HAS_CHAT_REFERENCE_LENGTH = BOOLEAN_LENGTH; + private static final int CHAT_REFERENCE_LENGTH = SIGNATURE_LENGTH; private static final int DATA_SIZE_LENGTH = INT_LENGTH; private static final int IS_TEXT_LENGTH = BOOLEAN_LENGTH; private static final int IS_ENCRYPTED_LENGTH = BOOLEAN_LENGTH; - private static final int EXTRAS_LENGTH = NONCE_LENGTH + HAS_RECIPIENT_LENGTH + DATA_SIZE_LENGTH + IS_ENCRYPTED_LENGTH + IS_TEXT_LENGTH; + private static final int EXTRAS_LENGTH = NONCE_LENGTH + HAS_RECIPIENT_LENGTH + HAS_CHAT_REFERENCE_LENGTH + DATA_SIZE_LENGTH + IS_ENCRYPTED_LENGTH + IS_TEXT_LENGTH; protected static final TransactionLayout layout; @@ -63,6 +66,17 @@ public class ChatTransactionTransformer extends TransactionTransformer { boolean hasRecipient = byteBuffer.get() != 0; String recipient = hasRecipient ? Serialization.deserializeAddress(byteBuffer) : null; + byte[] chatReference = null; + + if (timestamp >= BlockChain.getInstance().getChatReferenceTimestamp()) { + boolean hasChatReference = byteBuffer.get() != 0; + + if (hasChatReference) { + chatReference = new byte[CHAT_REFERENCE_LENGTH]; + byteBuffer.get(chatReference); + } + } + int dataSize = byteBuffer.getInt(); // Don't allow invalid dataSize here to avoid run-time issues if (dataSize > ChatTransaction.MAX_DATA_SIZE) @@ -83,7 +97,7 @@ public class ChatTransactionTransformer extends TransactionTransformer { BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, senderPublicKey, fee, signature); String sender = Crypto.toAddress(senderPublicKey); - return new ChatTransactionData(baseTransactionData, sender, nonce, recipient, data, isText, isEncrypted); + return new ChatTransactionData(baseTransactionData, sender, nonce, recipient, chatReference, data, isText, isEncrypted); } public static int getDataLength(TransactionData transactionData) { @@ -94,6 +108,9 @@ public class ChatTransactionTransformer extends TransactionTransformer { if (chatTransactionData.getRecipient() != null) dataLength += RECIPIENT_LENGTH; + if (chatTransactionData.getChatReference() != null) + dataLength += CHAT_REFERENCE_LENGTH; + return dataLength; } @@ -114,6 +131,16 @@ public class ChatTransactionTransformer extends TransactionTransformer { bytes.write((byte) 0); } + if (transactionData.getTimestamp() >= BlockChain.getInstance().getChatReferenceTimestamp()) { + // Include chat reference if it's not null + if (chatTransactionData.getChatReference() != null) { + bytes.write((byte) 1); + bytes.write(chatTransactionData.getChatReference()); + } else { + bytes.write((byte) 0); + } + } + bytes.write(Ints.toByteArray(chatTransactionData.getData().length)); bytes.write(chatTransactionData.getData()); diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index e9f1500d..2255c0a8 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -80,7 +80,8 @@ "calcChainWeightTimestamp": 1620579600000, "transactionV5Timestamp": 1642176000000, "transactionV6Timestamp": 9999999999999, - "disableReferenceTimestamp": 1655222400000 + "disableReferenceTimestamp": 1655222400000, + "chatReferenceTimestamp": 9999999999999 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-block-timestamps.json b/src/test/resources/test-chain-v2-block-timestamps.json index 37224684..028519f8 100644 --- a/src/test/resources/test-chain-v2-block-timestamps.json +++ b/src/test/resources/test-chain-v2-block-timestamps.json @@ -69,7 +69,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 9999999999999, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-disable-reference.json b/src/test/resources/test-chain-v2-disable-reference.json index 7ea0b86d..541ce779 100644 --- a/src/test/resources/test-chain-v2-disable-reference.json +++ b/src/test/resources/test-chain-v2-disable-reference.json @@ -72,7 +72,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 0 + "disableReferenceTimestamp": 0, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-founder-rewards.json b/src/test/resources/test-chain-v2-founder-rewards.json index 85a50f83..7392d5ae 100644 --- a/src/test/resources/test-chain-v2-founder-rewards.json +++ b/src/test/resources/test-chain-v2-founder-rewards.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-leftover-reward.json b/src/test/resources/test-chain-v2-leftover-reward.json index ebc3ccfa..ed46cd56 100644 --- a/src/test/resources/test-chain-v2-leftover-reward.json +++ b/src/test/resources/test-chain-v2-leftover-reward.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-minting.json b/src/test/resources/test-chain-v2-minting.json index cc91f993..a705016c 100644 --- a/src/test/resources/test-chain-v2-minting.json +++ b/src/test/resources/test-chain-v2-minting.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-qora-holder-extremes.json b/src/test/resources/test-chain-v2-qora-holder-extremes.json index 085d1dbf..880af61b 100644 --- a/src/test/resources/test-chain-v2-qora-holder-extremes.json +++ b/src/test/resources/test-chain-v2-qora-holder-extremes.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-qora-holder-reduction.json b/src/test/resources/test-chain-v2-qora-holder-reduction.json index 75858057..27451201 100644 --- a/src/test/resources/test-chain-v2-qora-holder-reduction.json +++ b/src/test/resources/test-chain-v2-qora-holder-reduction.json @@ -74,7 +74,8 @@ "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, "disableReferenceTimestamp": 9999999999999, - "aggregateSignatureTimestamp": 0 + "aggregateSignatureTimestamp": 0, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-qora-holder.json b/src/test/resources/test-chain-v2-qora-holder.json index 0706c5bb..fbc58d80 100644 --- a/src/test/resources/test-chain-v2-qora-holder.json +++ b/src/test/resources/test-chain-v2-qora-holder.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-reward-levels.json b/src/test/resources/test-chain-v2-reward-levels.json index b3644d6b..0f7adf6f 100644 --- a/src/test/resources/test-chain-v2-reward-levels.json +++ b/src/test/resources/test-chain-v2-reward-levels.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-reward-scaling.json b/src/test/resources/test-chain-v2-reward-scaling.json index 1c68dda4..802ad8fe 100644 --- a/src/test/resources/test-chain-v2-reward-scaling.json +++ b/src/test/resources/test-chain-v2-reward-scaling.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-reward-shares.json b/src/test/resources/test-chain-v2-reward-shares.json index 10d2aab3..2becc875 100644 --- a/src/test/resources/test-chain-v2-reward-shares.json +++ b/src/test/resources/test-chain-v2-reward-shares.json @@ -73,7 +73,8 @@ "newConsensusTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2.json b/src/test/resources/test-chain-v2.json index 5f439602..c3b740ff 100644 --- a/src/test/resources/test-chain-v2.json +++ b/src/test/resources/test-chain-v2.json @@ -73,7 +73,8 @@ "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, - "disableReferenceTimestamp": 9999999999999 + "disableReferenceTimestamp": 9999999999999, + "chatReferenceTimestamp": 0 }, "genesisInfo": { "version": 4, From a4759a0ef4f169b6934240c562bda218bfc285a6 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 22 Oct 2022 12:43:40 +0100 Subject: [PATCH 2/5] Re-ordered chat transaction transformation, to simplify UI code. New additions are now at the end of the data bytes. --- .../ChatTransactionTransformer.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/qortal/transform/transaction/ChatTransactionTransformer.java b/src/main/java/org/qortal/transform/transaction/ChatTransactionTransformer.java index d482dacd..b966ed2b 100644 --- a/src/main/java/org/qortal/transform/transaction/ChatTransactionTransformer.java +++ b/src/main/java/org/qortal/transform/transaction/ChatTransactionTransformer.java @@ -29,7 +29,7 @@ public class ChatTransactionTransformer extends TransactionTransformer { private static final int IS_TEXT_LENGTH = BOOLEAN_LENGTH; private static final int IS_ENCRYPTED_LENGTH = BOOLEAN_LENGTH; - private static final int EXTRAS_LENGTH = NONCE_LENGTH + HAS_RECIPIENT_LENGTH + HAS_CHAT_REFERENCE_LENGTH + DATA_SIZE_LENGTH + IS_ENCRYPTED_LENGTH + IS_TEXT_LENGTH; + private static final int EXTRAS_LENGTH = NONCE_LENGTH + HAS_RECIPIENT_LENGTH + DATA_SIZE_LENGTH + IS_ENCRYPTED_LENGTH + IS_TEXT_LENGTH + HAS_CHAT_REFERENCE_LENGTH; protected static final TransactionLayout layout; @@ -66,17 +66,6 @@ public class ChatTransactionTransformer extends TransactionTransformer { boolean hasRecipient = byteBuffer.get() != 0; String recipient = hasRecipient ? Serialization.deserializeAddress(byteBuffer) : null; - byte[] chatReference = null; - - if (timestamp >= BlockChain.getInstance().getChatReferenceTimestamp()) { - boolean hasChatReference = byteBuffer.get() != 0; - - if (hasChatReference) { - chatReference = new byte[CHAT_REFERENCE_LENGTH]; - byteBuffer.get(chatReference); - } - } - int dataSize = byteBuffer.getInt(); // Don't allow invalid dataSize here to avoid run-time issues if (dataSize > ChatTransaction.MAX_DATA_SIZE) @@ -91,6 +80,17 @@ public class ChatTransactionTransformer extends TransactionTransformer { long fee = byteBuffer.getLong(); + byte[] chatReference = null; + + if (timestamp >= BlockChain.getInstance().getChatReferenceTimestamp()) { + boolean hasChatReference = byteBuffer.get() != 0; + + if (hasChatReference) { + chatReference = new byte[CHAT_REFERENCE_LENGTH]; + byteBuffer.get(chatReference); + } + } + byte[] signature = new byte[SIGNATURE_LENGTH]; byteBuffer.get(signature); @@ -131,16 +131,6 @@ public class ChatTransactionTransformer extends TransactionTransformer { bytes.write((byte) 0); } - if (transactionData.getTimestamp() >= BlockChain.getInstance().getChatReferenceTimestamp()) { - // Include chat reference if it's not null - if (chatTransactionData.getChatReference() != null) { - bytes.write((byte) 1); - bytes.write(chatTransactionData.getChatReference()); - } else { - bytes.write((byte) 0); - } - } - bytes.write(Ints.toByteArray(chatTransactionData.getData().length)); bytes.write(chatTransactionData.getData()); @@ -151,6 +141,16 @@ public class ChatTransactionTransformer extends TransactionTransformer { bytes.write(Longs.toByteArray(chatTransactionData.getFee())); + if (transactionData.getTimestamp() >= BlockChain.getInstance().getChatReferenceTimestamp()) { + // Include chat reference if it's not null + if (chatTransactionData.getChatReference() != null) { + bytes.write((byte) 1); + bytes.write(chatTransactionData.getChatReference()); + } else { + bytes.write((byte) 0); + } + } + if (chatTransactionData.getSignature() != null) bytes.write(chatTransactionData.getSignature()); From 23a5c5f9b495bc74c8af2c42f402d328bc6190e8 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 22 Oct 2022 12:50:28 +0100 Subject: [PATCH 3/5] Fixed bug in original commit - we need to save the chat reference to the db. --- .../hsqldb/transaction/HSQLDBChatTransactionRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBChatTransactionRepository.java b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBChatTransactionRepository.java index 0dd3c0e3..79e798a9 100644 --- a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBChatTransactionRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBChatTransactionRepository.java @@ -46,7 +46,7 @@ public class HSQLDBChatTransactionRepository extends HSQLDBTransactionRepository saveHelper.bind("signature", chatTransactionData.getSignature()).bind("nonce", chatTransactionData.getNonce()) .bind("sender", chatTransactionData.getSender()).bind("recipient", chatTransactionData.getRecipient()) .bind("is_text", chatTransactionData.getIsText()).bind("is_encrypted", chatTransactionData.getIsEncrypted()) - .bind("data", chatTransactionData.getData()); + .bind("data", chatTransactionData.getData()).bind("chat_reference", chatTransactionData.getChatReference()); try { saveHelper.execute(this.repository); From 09014d07e0fc1b18cf37827698f3064e568f16dc Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 23 Oct 2022 19:29:31 +0100 Subject: [PATCH 4/5] Fixed issues retrieving chatReference from the db. --- .../java/org/qortal/data/chat/ChatMessage.java | 11 +++++++++-- .../repository/hsqldb/HSQLDBChatRepository.java | 16 +++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/qortal/data/chat/ChatMessage.java b/src/main/java/org/qortal/data/chat/ChatMessage.java index 26df1da4..5d16bb7c 100644 --- a/src/main/java/org/qortal/data/chat/ChatMessage.java +++ b/src/main/java/org/qortal/data/chat/ChatMessage.java @@ -27,6 +27,8 @@ public class ChatMessage { private String recipientName; + private byte[] chatReference; + private byte[] data; private boolean isText; @@ -42,8 +44,8 @@ public class ChatMessage { // For repository use public ChatMessage(long timestamp, int txGroupId, byte[] reference, byte[] senderPublicKey, String sender, - String senderName, String recipient, String recipientName, byte[] data, boolean isText, - boolean isEncrypted, byte[] signature) { + String senderName, String recipient, String recipientName, byte[] chatReference, byte[] data, + boolean isText, boolean isEncrypted, byte[] signature) { this.timestamp = timestamp; this.txGroupId = txGroupId; this.reference = reference; @@ -52,6 +54,7 @@ public class ChatMessage { this.senderName = senderName; this.recipient = recipient; this.recipientName = recipientName; + this.chatReference = chatReference; this.data = data; this.isText = isText; this.isEncrypted = isEncrypted; @@ -90,6 +93,10 @@ public class ChatMessage { return this.recipientName; } + public byte[] getChatReference() { + return this.chatReference; + } + public byte[] getData() { return this.data; } diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java index d4c9d7e0..178a4c24 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java @@ -35,7 +35,7 @@ public class HSQLDBChatRepository implements ChatRepository { sql.append("SELECT created_when, tx_group_id, Transactions.reference, creator, " + "sender, SenderNames.name, recipient, RecipientNames.name, " - + "data, is_text, is_encrypted, signature " + + "chat_reference, data, is_text, is_encrypted, signature " + "FROM ChatTransactions " + "JOIN Transactions USING (signature) " + "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender " @@ -108,13 +108,14 @@ public class HSQLDBChatRepository implements ChatRepository { String senderName = resultSet.getString(6); String recipient = resultSet.getString(7); String recipientName = resultSet.getString(8); - byte[] data = resultSet.getBytes(9); - boolean isText = resultSet.getBoolean(10); - boolean isEncrypted = resultSet.getBoolean(11); - byte[] signature = resultSet.getBytes(12); + byte[] chatReference = resultSet.getBytes(9); + byte[] data = resultSet.getBytes(10); + boolean isText = resultSet.getBoolean(11); + boolean isEncrypted = resultSet.getBoolean(12); + byte[] signature = resultSet.getBytes(13); ChatMessage chatMessage = new ChatMessage(timestamp, groupId, reference, senderPublicKey, sender, - senderName, recipient, recipientName, data, isText, isEncrypted, signature); + senderName, recipient, recipientName, chatReference, data, isText, isEncrypted, signature); chatMessages.add(chatMessage); } while (resultSet.next()); @@ -146,13 +147,14 @@ public class HSQLDBChatRepository implements ChatRepository { byte[] senderPublicKey = chatTransactionData.getSenderPublicKey(); String sender = chatTransactionData.getSender(); String recipient = chatTransactionData.getRecipient(); + byte[] chatReference = chatTransactionData.getChatReference(); byte[] data = chatTransactionData.getData(); boolean isText = chatTransactionData.getIsText(); boolean isEncrypted = chatTransactionData.getIsEncrypted(); byte[] signature = chatTransactionData.getSignature(); return new ChatMessage(timestamp, groupId, reference, senderPublicKey, sender, - senderName, recipient, recipientName, data, isText, isEncrypted, signature); + senderName, recipient, recipientName, chatReference, data, isText, isEncrypted, signature); } catch (SQLException e) { throw new DataException("Unable to fetch convert chat transaction from repository", e); } From 9d74f0eec0b4a8f208c8e8713d6bb2a07c74e1fa Mon Sep 17 00:00:00 2001 From: CalDescent Date: Mon, 24 Oct 2022 19:21:29 +0100 Subject: [PATCH 5/5] Added haschatreference, with possible values of true, false, or null, to allow optional filtering by the presence or absense of a chat reference. --- .../java/org/qortal/api/resource/ChatResource.java | 2 ++ .../qortal/api/websocket/ChatMessagesWebSocket.java | 2 ++ .../java/org/qortal/repository/ChatRepository.java | 4 ++-- .../repository/hsqldb/HSQLDBChatRepository.java | 11 +++++++++-- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/qortal/api/resource/ChatResource.java b/src/main/java/org/qortal/api/resource/ChatResource.java index 8c0f94c3..2601e938 100644 --- a/src/main/java/org/qortal/api/resource/ChatResource.java +++ b/src/main/java/org/qortal/api/resource/ChatResource.java @@ -71,6 +71,7 @@ public class ChatResource { @QueryParam("involving") List involvingAddresses, @QueryParam("reference") String reference, @QueryParam("chatreference") String chatReference, + @QueryParam("haschatreference") Boolean hasChatReference, @Parameter(ref = "limit") @QueryParam("limit") Integer limit, @Parameter(ref = "offset") @QueryParam("offset") Integer offset, @Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse) { @@ -104,6 +105,7 @@ public class ChatResource { txGroupId, referenceBytes, chatReferenceBytes, + hasChatReference, involvingAddresses, limit, offset, reverse); } catch (DataException e) { diff --git a/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java b/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java index dbe36d9f..76ed936c 100644 --- a/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java @@ -48,6 +48,7 @@ public class ChatMessagesWebSocket extends ApiWebSocket { null, null, null, + null, null, null, null); sendMessages(session, chatMessages); @@ -76,6 +77,7 @@ public class ChatMessagesWebSocket extends ApiWebSocket { null, null, null, + null, involvingAddresses, null, null, null); diff --git a/src/main/java/org/qortal/repository/ChatRepository.java b/src/main/java/org/qortal/repository/ChatRepository.java index ebdc22e4..c4541907 100644 --- a/src/main/java/org/qortal/repository/ChatRepository.java +++ b/src/main/java/org/qortal/repository/ChatRepository.java @@ -14,8 +14,8 @@ public interface ChatRepository { * Expects EITHER non-null txGroupID OR non-null sender and recipient addresses. */ public List getMessagesMatchingCriteria(Long before, Long after, - Integer txGroupId, byte[] reference, byte[] chatReferenceBytes, List involving, - Integer limit, Integer offset, Boolean reverse) throws DataException; + Integer txGroupId, byte[] reference, byte[] chatReferenceBytes, Boolean hasChatReference, + List involving, Integer limit, Integer offset, Boolean reverse) throws DataException; public ChatMessage toChatMessage(ChatTransactionData chatTransactionData) throws DataException; diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java index 178a4c24..08226d53 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java @@ -24,8 +24,8 @@ public class HSQLDBChatRepository implements ChatRepository { @Override public List getMessagesMatchingCriteria(Long before, Long after, Integer txGroupId, byte[] referenceBytes, - byte[] chatReferenceBytes, List involving, Integer limit, Integer offset, Boolean reverse) - throws DataException { + byte[] chatReferenceBytes, Boolean hasChatReference, List involving, + Integer limit, Integer offset, Boolean reverse) throws DataException { // Check args meet expectations if ((txGroupId != null && involving != null && !involving.isEmpty()) || (txGroupId == null && (involving == null || involving.size() != 2))) @@ -67,6 +67,13 @@ public class HSQLDBChatRepository implements ChatRepository { bindParams.add(chatReferenceBytes); } + if (hasChatReference != null && hasChatReference == true) { + whereClauses.add("chat_reference IS NOT NULL"); + } + else if (hasChatReference != null && hasChatReference == false) { + whereClauses.add("chat_reference IS NULL"); + } + if (txGroupId != null) { whereClauses.add("tx_group_id = " + txGroupId); // int safe to use literally whereClauses.add("recipient IS NULL");