forked from Qortal/qortal
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.
This commit is contained in:
parent
57125a91cf
commit
910191b074
@ -70,6 +70,7 @@ public class ChatResource {
|
||||
@QueryParam("txGroupId") Integer txGroupId,
|
||||
@QueryParam("involving") List<String> 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) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ public interface ChatRepository {
|
||||
* Expects EITHER non-null txGroupID OR non-null sender and recipient addresses.
|
||||
*/
|
||||
public List<ChatMessage> getMessagesMatchingCriteria(Long before, Long after,
|
||||
Integer txGroupId, byte[] reference, List<String> involving,
|
||||
Integer txGroupId, byte[] reference, byte[] chatReferenceBytes, List<String> involving,
|
||||
Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
|
||||
public ChatMessage toChatMessage(ChatTransactionData chatTransactionData) throws DataException;
|
||||
|
@ -24,7 +24,7 @@ public class HSQLDBChatRepository implements ChatRepository {
|
||||
|
||||
@Override
|
||||
public List<ChatMessage> getMessagesMatchingCriteria(Long before, Long after, Integer txGroupId, byte[] referenceBytes,
|
||||
List<String> involving, Integer limit, Integer offset, Boolean reverse)
|
||||
byte[] chatReferenceBytes, List<String> 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");
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -80,7 +80,8 @@
|
||||
"calcChainWeightTimestamp": 1620579600000,
|
||||
"transactionV5Timestamp": 1642176000000,
|
||||
"transactionV6Timestamp": 9999999999999,
|
||||
"disableReferenceTimestamp": 1655222400000
|
||||
"disableReferenceTimestamp": 1655222400000,
|
||||
"chatReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -69,7 +69,8 @@
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 9999999999999,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -72,7 +72,8 @@
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 0
|
||||
"disableReferenceTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -73,7 +73,8 @@
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -73,7 +73,8 @@
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -73,7 +73,8 @@
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -73,7 +73,8 @@
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -74,7 +74,8 @@
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"aggregateSignatureTimestamp": 0
|
||||
"aggregateSignatureTimestamp": 0,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -73,7 +73,8 @@
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -73,7 +73,8 @@
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -73,7 +73,8 @@
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -73,7 +73,8 @@
|
||||
"newConsensusTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -73,7 +73,8 @@
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
"disableReferenceTimestamp": 9999999999999,
|
||||
"chatReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
Loading…
Reference in New Issue
Block a user