Converted Message Transactions

This commit is contained in:
catbref 2018-06-13 15:40:58 +01:00
parent 16a92305d2
commit f2d7a3d0cd
7 changed files with 373 additions and 287 deletions

View File

@ -0,0 +1,74 @@
package data.transaction;
import java.math.BigDecimal;
import qora.assets.Asset;
import qora.transaction.Transaction.TransactionType;
public class MessageTransactionData extends TransactionData {
// Properties
protected int version;
protected byte[] senderPublicKey;
protected String recipient;
protected Long assetId;
protected BigDecimal amount;
protected byte[] data;
protected boolean isText;
protected boolean isEncrypted;
// Constructors
public MessageTransactionData(int version, byte[] senderPublicKey, String recipient, Long assetId, BigDecimal amount, BigDecimal fee, byte[] data,
boolean isText, boolean isEncrypted, long timestamp, byte[] reference, byte[] signature) {
super(TransactionType.MESSAGE, fee, senderPublicKey, timestamp, reference, signature);
this.version = version;
this.senderPublicKey = senderPublicKey;
this.recipient = recipient;
if (assetId != null)
this.assetId = assetId;
else
this.assetId = Asset.QORA;
this.amount = amount;
this.data = data;
this.isText = isText;
this.isEncrypted = isEncrypted;
}
// Getters/Setters
public int getVersion() {
return this.version;
}
public byte[] getSenderPublicKey() {
return this.senderPublicKey;
}
public String getRecipient() {
return this.recipient;
}
public Long getAssetId() {
return this.assetId;
}
public BigDecimal getAmount() {
return this.amount;
}
public byte[] getData() {
return this.data;
}
public boolean getIsText() {
return this.isText;
}
public boolean getIsEncrypted() {
return this.isEncrypted;
}
}

View File

@ -1,375 +1,142 @@
package qora.transaction;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import org.json.simple.JSONObject;
import com.google.common.hash.HashCode;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import database.DB;
import database.NoDataFoundException;
import data.transaction.MessageTransactionData;
import data.transaction.TransactionData;
import qora.account.Account;
import qora.account.PublicKeyAccount;
import qora.assets.Asset;
import qora.block.Block;
import qora.block.BlockChain;
import qora.crypto.Crypto;
import repository.hsqldb.HSQLDBSaver;
import transform.TransformationException;
import utils.Base58;
import utils.Serialization;
import repository.DataException;
import repository.Repository;
public class MessageTransaction extends TransactionHandler {
public class MessageTransaction extends Transaction {
// Properties
protected int version;
protected PublicKeyAccount sender;
protected Account recipient;
protected Long assetId;
protected BigDecimal amount;
protected byte[] data;
protected boolean isText;
protected boolean isEncrypted;
// Property lengths
private static final int SENDER_LENGTH = 32;
private static final int AMOUNT_LENGTH = 8;
private static final int ASSET_ID_LENGTH = 8;
private static final int DATA_SIZE_LENGTH = 4;
private static final int IS_TEXT_LENGTH = 1;
private static final int IS_ENCRYPTED_LENGTH = 1;
private static final int TYPELESS_DATALESS_LENGTH_V1 = BASE_TYPELESS_LENGTH + SENDER_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH + DATA_SIZE_LENGTH
+ IS_TEXT_LENGTH + IS_ENCRYPTED_LENGTH;
private static final int TYPELESS_DATALESS_LENGTH_V3 = BASE_TYPELESS_LENGTH + SENDER_LENGTH + RECIPIENT_LENGTH + ASSET_ID_LENGTH + AMOUNT_LENGTH
+ DATA_SIZE_LENGTH + IS_TEXT_LENGTH + IS_ENCRYPTED_LENGTH;
// Other property lengths
private static final int MAX_DATA_SIZE = 4000;
// Constructors
public MessageTransaction(PublicKeyAccount sender, String recipient, Long assetId, BigDecimal amount, BigDecimal fee, byte[] data, boolean isText,
boolean isEncrypted, long timestamp, byte[] reference, byte[] signature) {
super(TransactionType.MESSAGE, fee, sender, timestamp, reference, signature);
this.version = TransactionHandler.getVersionByTimestamp(this.timestamp);
this.sender = sender;
this.recipient = new Account(recipient);
if (assetId != null)
this.assetId = assetId;
else
this.assetId = Asset.QORA;
this.amount = amount;
this.data = data;
this.isText = isText;
this.isEncrypted = isEncrypted;
}
// Getters/Setters
public int getVersion() {
return this.version;
}
public Account getSender() {
return this.sender;
}
public Account getRecipient() {
return this.recipient;
}
public Long getAssetId() {
return this.assetId;
}
public BigDecimal getAmount() {
return this.amount;
}
public byte[] getData() {
return this.data;
}
public boolean isText() {
return this.isText;
}
public boolean isEncrypted() {
return this.isEncrypted;
}
// More information
public int getDataLength() {
if (this.version == 1)
return TYPE_LENGTH + TYPELESS_DATALESS_LENGTH_V1 + this.data.length;
else
return TYPE_LENGTH + TYPELESS_DATALESS_LENGTH_V3 + this.data.length;
}
// Load/Save
/**
* Construct MessageTransaction from DB using signature.
*
* @param signature
* @throws NoDataFoundException
* if no matching row found
* @throws SQLException
*/
protected MessageTransaction(byte[] signature) throws SQLException {
super(TransactionType.MESSAGE, signature);
ResultSet rs = DB.checkedExecute(
"SELECT version, sender, recipient, is_text, is_encrypted, amount, asset_id, data FROM MessageTransactions WHERE signature = ?", signature);
if (rs == null)
throw new NoDataFoundException();
this.version = rs.getInt(1);
this.sender = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(2), CREATOR_LENGTH));
this.recipient = new Account(rs.getString(3));
this.isText = rs.getBoolean(4);
this.isEncrypted = rs.getBoolean(5);
this.amount = rs.getBigDecimal(6).setScale(8);
this.assetId = rs.getLong(7);
this.data = DB.getResultSetBytes(rs.getBinaryStream(8));
}
/**
* Load MessageTransaction from DB using signature.
*
* @param signature
* @return MessageTransaction, or null if not found
* @throws SQLException
*/
public static MessageTransaction fromSignature(byte[] signature) throws SQLException {
try {
return new MessageTransaction(signature);
} catch (NoDataFoundException e) {
return null;
}
}
@Override
public void save() throws SQLException {
super.save();
HSQLDBSaver saveHelper = new HSQLDBSaver("MessageTransactions");
saveHelper.bind("signature", this.signature).bind("version", this.version).bind("sender", this.sender.getPublicKey())
.bind("recipient", this.recipient.getAddress()).bind("is_text", this.isText).bind("is_encrypted", this.isEncrypted).bind("amount", this.amount)
.bind("asset_id", this.assetId).bind("data", this.data);
saveHelper.execute();
}
// Converters
protected static TransactionHandler parse(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TIMESTAMP_LENGTH)
throw new TransformationException("Byte data too short for MessageTransaction");
long timestamp = byteBuffer.getLong();
int version = TransactionHandler.getVersionByTimestamp(timestamp);
int minimumRemaining = version == 1 ? TYPELESS_DATALESS_LENGTH_V1 : TYPELESS_DATALESS_LENGTH_V3;
minimumRemaining -= TIMESTAMP_LENGTH; // Already read above
if (byteBuffer.remaining() < minimumRemaining)
throw new TransformationException("Byte data too short for MessageTransaction");
byte[] reference = new byte[REFERENCE_LENGTH];
byteBuffer.get(reference);
PublicKeyAccount sender = Serialization.deserializePublicKey(byteBuffer);
String recipient = Serialization.deserializeRecipient(byteBuffer);
long assetId;
if (version == 1)
assetId = Asset.QORA;
else
assetId = byteBuffer.getLong();
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
int dataSize = byteBuffer.getInt(0);
// Don't allow invalid dataSize here to avoid run-time issues
if (dataSize > MAX_DATA_SIZE)
throw new TransformationException("MessageTransaction data size too large");
byte[] data = new byte[dataSize];
byteBuffer.get(data);
boolean isEncrypted = byteBuffer.get() != 0;
boolean isText = byteBuffer.get() != 0;
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
byte[] signature = new byte[SIGNATURE_LENGTH];
byteBuffer.get(signature);
return new MessageTransaction(sender, recipient, assetId, amount, fee, data, isText, isEncrypted, timestamp, reference, signature);
}
@SuppressWarnings("unchecked")
@Override
public JSONObject toJSON() throws SQLException {
JSONObject json = getBaseJSON();
json.put("version", this.version);
json.put("sender", this.sender.getAddress());
json.put("senderPublicKey", HashCode.fromBytes(this.sender.getPublicKey()).toString());
json.put("recipient", this.recipient.getAddress());
json.put("amount", this.amount.toPlainString());
json.put("assetId", this.assetId);
json.put("isText", this.isText);
json.put("isEncrypted", this.isEncrypted);
// We can only show plain text as unencoded
if (this.isText && !this.isEncrypted)
json.put("data", new String(this.data, Charset.forName("UTF-8")));
else
json.put("data", HashCode.fromBytes(this.data).toString());
return json;
}
public byte[] toBytes() {
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(getDataLength());
bytes.write(Ints.toByteArray(this.type.value));
bytes.write(Longs.toByteArray(this.timestamp));
bytes.write(this.reference);
bytes.write(this.sender.getPublicKey());
bytes.write(Base58.decode(this.recipient.getAddress()));
if (this.version != 1)
bytes.write(Longs.toByteArray(this.assetId));
bytes.write(Serialization.serializeBigDecimal(this.amount));
bytes.write(Ints.toByteArray(this.data.length));
bytes.write(this.data);
bytes.write((byte) (this.isEncrypted ? 1 : 0));
bytes.write((byte) (this.isText ? 1 : 0));
bytes.write(Serialization.serializeBigDecimal(this.fee));
bytes.write(this.signature);
return bytes.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
public MessageTransaction(Repository repository, TransactionData transactionData) {
super(repository, transactionData);
}
// Processing
public ValidationResult isValid() throws SQLException {
public ValidationResult isValid() throws DataException {
// Lowest cost checks first
MessageTransactionData messageTransactionData = (MessageTransactionData) this.transactionData;
// Are message transactions even allowed at this point?
if (this.version != TransactionHandler.getVersionByTimestamp(this.timestamp))
if (messageTransactionData.getVersion() != MessageTransaction.getVersionByTimestamp(messageTransactionData.getTimestamp()))
return ValidationResult.NOT_YET_RELEASED;
if (BlockChain.getHeight() < Block.MESSAGE_RELEASE_HEIGHT)
if (this.repository.getBlockRepository().getBlockchainHeight() < Block.MESSAGE_RELEASE_HEIGHT)
return ValidationResult.NOT_YET_RELEASED;
// Check data length
if (this.data.length < 1 || this.data.length > MAX_DATA_SIZE)
if (messageTransactionData.getData().length < 1 || messageTransactionData.getData().length > MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH;
// Check recipient is a valid address
if (!Crypto.isValidAddress(this.recipient.getAddress()))
if (!Crypto.isValidAddress(messageTransactionData.getRecipient()))
return ValidationResult.INVALID_ADDRESS;
if (this.version == 1) {
if (messageTransactionData.getVersion() == 1) {
// Check amount is positive (V1)
if (this.amount.compareTo(BigDecimal.ZERO) <= 0)
if (messageTransactionData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_AMOUNT;
} else {
// Check amount is not negative (V3) as sending messages without a payment is OK
if (this.amount.compareTo(BigDecimal.ZERO) < 0)
if (messageTransactionData.getAmount().compareTo(BigDecimal.ZERO) < 0)
return ValidationResult.NEGATIVE_AMOUNT;
}
// Check fee is positive
if (this.fee.compareTo(BigDecimal.ZERO) <= 0)
if (messageTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check reference is correct
if (!Arrays.equals(this.sender.getLastReference(), this.reference))
Account sender = new PublicKeyAccount(this.repository, messageTransactionData.getSenderPublicKey());
if (!Arrays.equals(sender.getLastReference(), messageTransactionData.getReference()))
return ValidationResult.INVALID_REFERENCE;
// Does asset exist? (This test not present in gen1)
if (this.assetId != Asset.QORA && !Asset.exists(this.assetId))
long assetId = messageTransactionData.getAssetId();
if (assetId != Asset.QORA && !this.repository.getAssetRepository().assetExists(assetId))
return ValidationResult.ASSET_DOES_NOT_EXIST;
// If asset is QORA then we need to check amount + fee in one go
if (this.assetId == Asset.QORA) {
if (assetId == Asset.QORA) {
// Check sender has enough funds for amount + fee in QORA
if (this.sender.getConfirmedBalance(Asset.QORA).compareTo(this.amount.add(this.fee)) == -1)
if (sender.getConfirmedBalance(Asset.QORA).compareTo(messageTransactionData.getAmount().add(messageTransactionData.getFee())) == -1)
return ValidationResult.NO_BALANCE;
} else {
// Check sender has enough funds for amount in whatever asset
if (this.sender.getConfirmedBalance(this.assetId).compareTo(this.amount) == -1)
if (sender.getConfirmedBalance(assetId).compareTo(messageTransactionData.getAmount()) == -1)
return ValidationResult.NO_BALANCE;
// Check sender has enough funds for fee in QORA
if (this.sender.getConfirmedBalance(Asset.QORA).compareTo(this.fee) == -1)
if (sender.getConfirmedBalance(Asset.QORA).compareTo(messageTransactionData.getFee()) == -1)
return ValidationResult.NO_BALANCE;
}
return ValidationResult.OK;
}
public void process() throws SQLException {
this.save();
public void process() throws DataException {
MessageTransactionData messageTransactionData = (MessageTransactionData) this.transactionData;
long assetId = messageTransactionData.getAssetId();
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
// Update sender's balance due to amount
this.sender.setConfirmedBalance(this.assetId, this.sender.getConfirmedBalance(this.assetId).subtract(this.amount));
Account sender = new PublicKeyAccount(this.repository, messageTransactionData.getSenderPublicKey());
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(messageTransactionData.getAmount()));
// Update sender's balance due to fee
this.sender.setConfirmedBalance(Asset.QORA, this.sender.getConfirmedBalance(Asset.QORA).subtract(this.fee));
sender.setConfirmedBalance(Asset.QORA, sender.getConfirmedBalance(Asset.QORA).subtract(messageTransactionData.getFee()));
// Update recipient's balance
this.recipient.setConfirmedBalance(this.assetId, this.recipient.getConfirmedBalance(this.assetId).add(this.amount));
Account recipient = new Account(this.repository, messageTransactionData.getRecipient());
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(messageTransactionData.getAmount()));
// Update sender's reference
this.sender.setLastReference(this.signature);
sender.setLastReference(messageTransactionData.getSignature());
// For QORA amounts only: if recipient has no reference yet, then this is their starting reference
if (this.assetId == Asset.QORA && this.recipient.getLastReference() == null)
this.recipient.setLastReference(this.signature);
if (assetId == Asset.QORA && recipient.getLastReference() == null)
recipient.setLastReference(messageTransactionData.getSignature());
}
public void orphan() throws SQLException {
this.delete();
public void orphan() throws DataException {
MessageTransactionData messageTransactionData = (MessageTransactionData) this.transactionData;
long assetId = messageTransactionData.getAssetId();
// Delete this transaction itself
this.repository.getTransactionRepository().delete(this.transactionData);
// Update sender's balance due to amount
this.sender.setConfirmedBalance(this.assetId, this.sender.getConfirmedBalance(this.assetId).add(this.amount));
Account sender = new PublicKeyAccount(this.repository, messageTransactionData.getSenderPublicKey());
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).add(messageTransactionData.getAmount()));
// Update sender's balance due to fee
this.sender.setConfirmedBalance(Asset.QORA, this.sender.getConfirmedBalance(Asset.QORA).add(this.fee));
sender.setConfirmedBalance(Asset.QORA, sender.getConfirmedBalance(Asset.QORA).add(messageTransactionData.getFee()));
// Update recipient's balance
this.recipient.setConfirmedBalance(this.assetId, this.recipient.getConfirmedBalance(this.assetId).subtract(this.amount));
Account recipient = new Account(this.repository, messageTransactionData.getRecipient());
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).subtract(messageTransactionData.getAmount()));
// Update sender's reference
this.sender.setLastReference(this.reference);
sender.setLastReference(messageTransactionData.getReference());
/*
* For QORA amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own (which
* would have changed their last reference) thus this is their first reference so remove it.
*/
if (this.assetId == Asset.QORA && Arrays.equals(this.recipient.getLastReference(), this.signature))
this.recipient.setLastReference(null);
if (assetId == Asset.QORA && Arrays.equals(recipient.getLastReference(), messageTransactionData.getSignature()))
recipient.setLastReference(null);
}
}

View File

@ -89,6 +89,9 @@ public abstract class Transaction {
case CREATE_ASSET_ORDER:
return new CreateOrderTransaction(repository, transactionData);
case MESSAGE:
return new MessageTransaction(repository, transactionData);
default:
return null;
}

View File

@ -0,0 +1,63 @@
package repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import data.transaction.MessageTransactionData;
import data.transaction.TransactionData;
import repository.DataException;
import repository.hsqldb.HSQLDBRepository;
import repository.hsqldb.HSQLDBSaver;
public class HSQLDBMessageTransactionRepository extends HSQLDBTransactionRepository {
public HSQLDBMessageTransactionRepository(HSQLDBRepository repository) {
super(repository);
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute(
"SELECT version, sender, recipient, is_text, is_encrypted, amount, asset_id, data FROM MessageTransactions WHERE signature = ?", signature);
if (rs == null)
return null;
int version = rs.getInt(1);
byte[] senderPublicKey = this.repository.getResultSetBytes(rs.getBinaryStream(2));
String recipient = rs.getString(3);
boolean isText = rs.getBoolean(4);
boolean isEncrypted = rs.getBoolean(5);
BigDecimal amount = rs.getBigDecimal(6);
Long assetId = rs.getLong(7);
byte[] data = this.repository.getResultSetBytes(rs.getBinaryStream(8));
return new MessageTransactionData(version, senderPublicKey, recipient, assetId, amount, fee, data, isText, isEncrypted, timestamp, reference,
signature);
} catch (SQLException e) {
throw new DataException("Unable to fetch message transaction from repository", e);
}
}
@Override
public void save(TransactionData transactionData) throws DataException {
super.save(transactionData);
MessageTransactionData messageTransactionData = (MessageTransactionData) transactionData;
HSQLDBSaver saveHelper = new HSQLDBSaver("MessageTransactions");
saveHelper.bind("signature", messageTransactionData.getSignature()).bind("version", messageTransactionData.getVersion())
.bind("sender", messageTransactionData.getSenderPublicKey()).bind("recipient", messageTransactionData.getRecipient())
.bind("is_text", messageTransactionData.getIsText()).bind("is_encrypted", messageTransactionData.getIsEncrypted())
.bind("amount", messageTransactionData.getAmount()).bind("asset_id", messageTransactionData.getAssetId())
.bind("data", messageTransactionData.getData());
try {
saveHelper.execute(this.repository);
} catch (SQLException e) {
throw new DataException("Unable to save message transaction into repository", e);
}
}
}

View File

@ -20,6 +20,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
private HSQLDBPaymentTransactionRepository paymentTransactionRepository;
private HSQLDBIssueAssetTransactionRepository issueAssetTransactionRepository;
private HSQLDBCreateOrderTransactionRepository createOrderTransactionRepository;
private HSQLDBMessageTransactionRepository messageTransactionRepository;
public HSQLDBTransactionRepository(HSQLDBRepository repository) {
this.repository = repository;
@ -27,6 +28,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
this.paymentTransactionRepository = new HSQLDBPaymentTransactionRepository(repository);
this.issueAssetTransactionRepository = new HSQLDBIssueAssetTransactionRepository(repository);
this.createOrderTransactionRepository = new HSQLDBCreateOrderTransactionRepository(repository);
this.messageTransactionRepository = new HSQLDBMessageTransactionRepository(repository);
}
public TransactionData fromSignature(byte[] signature) throws DataException {
@ -80,6 +82,9 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
case CREATE_ASSET_ORDER:
return this.createOrderTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
case MESSAGE:
return this.messageTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
default:
return null;
}

View File

@ -0,0 +1,162 @@
package transform.transaction;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import org.json.simple.JSONObject;
import com.google.common.hash.HashCode;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import data.transaction.TransactionData;
import qora.account.PublicKeyAccount;
import qora.assets.Asset;
import qora.transaction.MessageTransaction;
import data.transaction.MessageTransactionData;
import transform.TransformationException;
import utils.Base58;
import utils.Serialization;
public class MessageTransactionTransformer extends TransactionTransformer {
// Property lengths
private static final int SENDER_LENGTH = PUBLIC_KEY_LENGTH;
private static final int RECIPIENT_LENGTH = ADDRESS_LENGTH;
private static final int AMOUNT_LENGTH = 8;
private static final int ASSET_ID_LENGTH = LONG_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 TYPELESS_DATALESS_LENGTH_V1 = BASE_TYPELESS_LENGTH + SENDER_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH + DATA_SIZE_LENGTH
+ IS_TEXT_LENGTH + IS_ENCRYPTED_LENGTH;
private static final int TYPELESS_DATALESS_LENGTH_V3 = BASE_TYPELESS_LENGTH + SENDER_LENGTH + RECIPIENT_LENGTH + ASSET_ID_LENGTH + AMOUNT_LENGTH
+ DATA_SIZE_LENGTH + IS_TEXT_LENGTH + IS_ENCRYPTED_LENGTH;
// Other property lengths
private static final int MAX_DATA_SIZE = 4000;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH_V1)
throw new TransformationException("Byte data too short for MessageTransaction");
long timestamp = byteBuffer.getLong();
int version = MessageTransaction.getVersionByTimestamp(timestamp);
int minimumRemaining = version == 1 ? TYPELESS_DATALESS_LENGTH_V1 : TYPELESS_DATALESS_LENGTH_V3;
minimumRemaining -= TIMESTAMP_LENGTH; // Already read above
if (byteBuffer.remaining() < minimumRemaining)
throw new TransformationException("Byte data too short for MessageTransaction");
byte[] reference = new byte[REFERENCE_LENGTH];
byteBuffer.get(reference);
byte[] senderPublicKey = Serialization.deserializePublicKey(byteBuffer);
String recipient = Serialization.deserializeRecipient(byteBuffer);
long assetId;
if (version == 1)
assetId = Asset.QORA;
else
assetId = byteBuffer.getLong();
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
int dataSize = byteBuffer.getInt(0);
// Don't allow invalid dataSize here to avoid run-time issues
if (dataSize > MAX_DATA_SIZE)
throw new TransformationException("MessageTransaction data size too large");
byte[] data = new byte[dataSize];
byteBuffer.get(data);
boolean isEncrypted = byteBuffer.get() != 0;
boolean isText = byteBuffer.get() != 0;
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
byte[] signature = new byte[SIGNATURE_LENGTH];
byteBuffer.get(signature);
return new MessageTransactionData(version, senderPublicKey, recipient, assetId, amount, fee, data, isText, isEncrypted, timestamp, reference,
signature);
}
public static int getDataLength(TransactionData transactionData) throws TransformationException {
MessageTransactionData messageTransactionData = (MessageTransactionData) transactionData;
if (messageTransactionData.getVersion() == 1)
return TYPE_LENGTH + TYPELESS_DATALESS_LENGTH_V1 + messageTransactionData.getData().length;
else
return TYPE_LENGTH + TYPELESS_DATALESS_LENGTH_V3 + messageTransactionData.getData().length;
}
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
try {
MessageTransactionData messageTransactionData = (MessageTransactionData) transactionData;
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bytes.write(Ints.toByteArray(messageTransactionData.getType().value));
bytes.write(Longs.toByteArray(messageTransactionData.getTimestamp()));
bytes.write(messageTransactionData.getReference());
bytes.write(messageTransactionData.getSenderPublicKey());
bytes.write(Base58.decode(messageTransactionData.getRecipient()));
if (messageTransactionData.getVersion() != 1)
bytes.write(Longs.toByteArray(messageTransactionData.getAssetId()));
bytes.write(Serialization.serializeBigDecimal(messageTransactionData.getAmount()));
bytes.write(Ints.toByteArray(messageTransactionData.getData().length));
bytes.write(messageTransactionData.getData());
bytes.write((byte) (messageTransactionData.getIsEncrypted() ? 1 : 0));
bytes.write((byte) (messageTransactionData.getIsText() ? 1 : 0));
bytes.write(Serialization.serializeBigDecimal(messageTransactionData.getFee()));
bytes.write(messageTransactionData.getSignature());
return bytes.toByteArray();
} catch (IOException | ClassCastException e) {
throw new TransformationException(e);
}
}
@SuppressWarnings("unchecked")
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
JSONObject json = TransactionTransformer.getBaseJSON(transactionData);
try {
MessageTransactionData messageTransactionData = (MessageTransactionData) transactionData;
byte[] senderPublicKey = messageTransactionData.getSenderPublicKey();
json.put("version", messageTransactionData.getVersion());
json.put("sender", PublicKeyAccount.getAddress(senderPublicKey));
json.put("senderPublicKey", HashCode.fromBytes(senderPublicKey).toString());
json.put("recipient", messageTransactionData.getRecipient());
json.put("amount", messageTransactionData.getAmount().toPlainString());
json.put("assetId", messageTransactionData.getAssetId());
json.put("isText", messageTransactionData.getIsText());
json.put("isEncrypted", messageTransactionData.getIsEncrypted());
// We can only show plain text as unencoded
if (messageTransactionData.getIsText() && !messageTransactionData.getIsEncrypted())
json.put("data", new String(messageTransactionData.getData(), Charset.forName("UTF-8")));
else
json.put("data", HashCode.fromBytes(messageTransactionData.getData()).toString());
} catch (ClassCastException e) {
throw new TransformationException(e);
}
return json;
}
}

View File

@ -42,6 +42,9 @@ public class TransactionTransformer extends Transformer {
case CREATE_ASSET_ORDER:
return CreateOrderTransactionTransformer.fromByteBuffer(byteBuffer);
case MESSAGE:
return MessageTransactionTransformer.fromByteBuffer(byteBuffer);
default:
throw new TransformationException("Unsupported transaction type");
}
@ -61,6 +64,9 @@ public class TransactionTransformer extends Transformer {
case CREATE_ASSET_ORDER:
return CreateOrderTransactionTransformer.getDataLength(transactionData);
case MESSAGE:
return MessageTransactionTransformer.getDataLength(transactionData);
default:
throw new TransformationException("Unsupported transaction type");
}
@ -80,6 +86,9 @@ public class TransactionTransformer extends Transformer {
case CREATE_ASSET_ORDER:
return CreateOrderTransactionTransformer.toBytes(transactionData);
case MESSAGE:
return MessageTransactionTransformer.toBytes(transactionData);
default:
throw new TransformationException("Unsupported transaction type");
}
@ -99,6 +108,9 @@ public class TransactionTransformer extends Transformer {
case CREATE_ASSET_ORDER:
return CreateOrderTransactionTransformer.toJSON(transaction);
case MESSAGE:
return MessageTransactionTransformer.toJSON(transaction);
default:
throw new TransformationException("Unsupported transaction type");
}