* Checks if transaction can have {@link TransactionHandler#process()} called. *
- * Expected to be called within an ongoing SQL Transaction, typically by {@link Block#process()}. - *
* Transactions that have already been processed will return false. * * @return true if transaction can be processed, false otherwise */ - public abstract ValidationResult isValid(); + public abstract ValidationResult isValid() throws DataException; /** * Actually process a transaction, updating the blockchain. *
* Processes transaction, updating balances, references, assets, etc. as appropriate. - *
- * Expected to be called within an ongoing SQL Transaction, typically by {@link Block#process()}. + * + * @throws DataException */ - public abstract void process(); + public abstract void process() throws DataException; /** * Undo transaction, updating the blockchain. *
* Undoes transaction, updating balances, references, assets, etc. as appropriate. - *
- * Expected to be called within an ongoing SQL Transaction, typically by {@link Block#process()}. + * + * @throws DataException */ - public abstract void orphan(); + public abstract void orphan() throws DataException; } diff --git a/src/repository/AccountRepository.java b/src/repository/AccountRepository.java index 08f7b72b..d740fe80 100644 --- a/src/repository/AccountRepository.java +++ b/src/repository/AccountRepository.java @@ -6,15 +6,15 @@ import data.account.AccountData; public interface AccountRepository { // General account - + public AccountData getAccount(String address) throws DataException; - + public void save(AccountData accountData) throws DataException; // Account balances - + public AccountBalanceData getBalance(String address, long assetId) throws DataException; - + public void save(AccountBalanceData accountBalanceData) throws DataException; public void delete(String address, long assetId) throws DataException; diff --git a/src/repository/AssetRepository.java b/src/repository/AssetRepository.java new file mode 100644 index 00000000..7b2ff7e6 --- /dev/null +++ b/src/repository/AssetRepository.java @@ -0,0 +1,17 @@ +package repository; + +import data.assets.AssetData; + +public interface AssetRepository { + + public AssetData fromAssetId(long assetId) throws DataException; + + public boolean assetExists(long assetId) throws DataException; + + public boolean assetExists(String assetName) throws DataException; + + public void save(AssetData assetData) throws DataException; + + public void delete(long assetId) throws DataException; + +} diff --git a/src/repository/Repository.java b/src/repository/Repository.java index f607f6e0..9a33c9ca 100644 --- a/src/repository/Repository.java +++ b/src/repository/Repository.java @@ -4,6 +4,8 @@ public interface Repository { public AccountRepository getAccountRepository(); + public AssetRepository getAssetRepository(); + public BlockRepository getBlockRepository(); public TransactionRepository getTransactionRepository(); diff --git a/src/repository/TransactionRepository.java b/src/repository/TransactionRepository.java index b01b0ec6..334d107d 100644 --- a/src/repository/TransactionRepository.java +++ b/src/repository/TransactionRepository.java @@ -5,14 +5,14 @@ import data.block.BlockData; public interface TransactionRepository { - public TransactionData fromSignature(byte[] signature); + public TransactionData fromSignature(byte[] signature) throws DataException; - public TransactionData fromReference(byte[] reference); + public TransactionData fromReference(byte[] reference) throws DataException; public int getHeight(TransactionData transactionData); - + public BlockData toBlock(TransactionData transactionData); - + public void save(TransactionData transactionData) throws DataException; public void delete(TransactionData transactionData) throws DataException; diff --git a/src/repository/hsqldb/HSQLDBAccountRepository.java b/src/repository/hsqldb/HSQLDBAccountRepository.java index d27da759..80c543b5 100644 --- a/src/repository/hsqldb/HSQLDBAccountRepository.java +++ b/src/repository/hsqldb/HSQLDBAccountRepository.java @@ -34,7 +34,7 @@ public class HSQLDBAccountRepository implements AccountRepository { saveHelper.bind("account", accountData.getAddress()).bind("reference", accountData.getReference()); try { - saveHelper.execute(this.repository.connection); + saveHelper.execute(this.repository); } catch (SQLException e) { throw new DataException("Unable to save account info into repository", e); } @@ -60,7 +60,7 @@ public class HSQLDBAccountRepository implements AccountRepository { accountBalanceData.getBalance()); try { - saveHelper.execute(this.repository.connection); + saveHelper.execute(this.repository); } catch (SQLException e) { throw new DataException("Unable to save account balance into repository", e); } diff --git a/src/repository/hsqldb/HSQLDBAssetRepository.java b/src/repository/hsqldb/HSQLDBAssetRepository.java new file mode 100644 index 00000000..d2e69296 --- /dev/null +++ b/src/repository/hsqldb/HSQLDBAssetRepository.java @@ -0,0 +1,78 @@ +package repository.hsqldb; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import data.assets.AssetData; +import repository.AssetRepository; +import repository.DataException; + +public class HSQLDBAssetRepository implements AssetRepository { + + protected HSQLDBRepository repository; + + public HSQLDBAssetRepository(HSQLDBRepository repository) { + this.repository = repository; + } + + public AssetData fromAssetId(long assetId) throws DataException { + try { + ResultSet resultSet = this.repository + .checkedExecute("SELECT owner, asset_name, description, quantity, is_divisible, reference FROM Assets WHERE asset_id = ?", assetId); + if (resultSet == null) + return null; + + String owner = resultSet.getString(1); + String assetName = resultSet.getString(2); + String description = resultSet.getString(3); + long quantity = resultSet.getLong(4); + boolean isDivisible = resultSet.getBoolean(5); + byte[] reference = this.repository.getResultSetBytes(resultSet.getBinaryStream(6)); + + return new AssetData(assetId, owner, assetName, description, quantity, isDivisible, reference); + } catch (SQLException e) { + throw new DataException("Unable to fetch asset from repository", e); + } + } + + public boolean assetExists(long assetId) throws DataException { + try { + return this.repository.exists("Assets", "asset_id = ?", assetId); + } catch (SQLException e) { + throw new DataException("Unable to check for asset in repository", e); + } + } + + public boolean assetExists(String assetName) throws DataException { + try { + return this.repository.exists("Assets", "asset_name = ?", assetName); + } catch (SQLException e) { + throw new DataException("Unable to check for asset in repository", e); + } + } + + public void save(AssetData assetData) throws DataException { + HSQLDBSaver saveHelper = new HSQLDBSaver("Assets"); + saveHelper.bind("asset_id", assetData.getAssetId()).bind("owner", assetData.getOwner()).bind("asset_name", assetData.getName()) + .bind("description", assetData.getDescription()).bind("quantity", assetData.getQuantity()).bind("is_divisible", assetData.getIsDivisible()) + .bind("reference", assetData.getReference()); + + try { + saveHelper.execute(this.repository); + + if (assetData.getAssetId() == null) + assetData.setAssetId(this.repository.callIdentity()); + } catch (SQLException e) { + throw new DataException("Unable to save asset into repository", e); + } + } + + public void delete(long assetId) throws DataException { + try { + this.repository.checkedExecute("DELETE FROM Assets WHERE assetId = ?", assetId); + } catch (SQLException e) { + throw new DataException("Unable to delete asset from repository", e); + } + } + +} diff --git a/src/repository/hsqldb/HSQLDBBlockRepository.java b/src/repository/hsqldb/HSQLDBBlockRepository.java index f60c8408..8887ce8d 100644 --- a/src/repository/hsqldb/HSQLDBBlockRepository.java +++ b/src/repository/hsqldb/HSQLDBBlockRepository.java @@ -134,7 +134,7 @@ public class HSQLDBBlockRepository implements BlockRepository { .bind("AT_data", blockData.getAtBytes()).bind("AT_fees", blockData.getAtFees()); try { - saveHelper.execute(this.repository.connection); + saveHelper.execute(this.repository); } catch (SQLException e) { throw new DataException("Unable to save Block into repository", e); } @@ -146,7 +146,7 @@ public class HSQLDBBlockRepository implements BlockRepository { .bind("transaction_signature", blockTransactionData.getTransactionSignature()); try { - saveHelper.execute(this.repository.connection); + saveHelper.execute(this.repository); } catch (SQLException e) { throw new DataException("Unable to save BlockTransaction into repository", e); } diff --git a/src/repository/hsqldb/HSQLDBRepository.java b/src/repository/hsqldb/HSQLDBRepository.java index 7496d393..e2984203 100644 --- a/src/repository/hsqldb/HSQLDBRepository.java +++ b/src/repository/hsqldb/HSQLDBRepository.java @@ -9,10 +9,12 @@ import java.sql.ResultSet; import java.sql.SQLException; import repository.AccountRepository; +import repository.AssetRepository; import repository.BlockRepository; import repository.DataException; import repository.Repository; import repository.TransactionRepository; +import repository.hsqldb.transaction.HSQLDBTransactionRepository; public class HSQLDBRepository implements Repository { @@ -28,6 +30,11 @@ public class HSQLDBRepository implements Repository { return new HSQLDBAccountRepository(this); } + @Override + public AssetRepository getAssetRepository() { + return new HSQLDBAssetRepository(this); + } + @Override public BlockRepository getBlockRepository() { return new HSQLDBBlockRepository(this); @@ -79,7 +86,7 @@ public class HSQLDBRepository implements Repository { * @param inputStream * @return byte[] */ - byte[] getResultSetBytes(InputStream inputStream) { + public byte[] getResultSetBytes(InputStream inputStream) { // inputStream could be null if database's column's value is null if (inputStream == null) return null; @@ -107,7 +114,7 @@ public class HSQLDBRepository implements Repository { * @return ResultSet, or null if there are no found rows * @throws SQLException */ - ResultSet checkedExecute(String sql, Object... objects) throws SQLException { + public ResultSet checkedExecute(String sql, Object... objects) throws SQLException { PreparedStatement preparedStatement = this.connection.prepareStatement(sql); for (int i = 0; i < objects.length; ++i) @@ -130,7 +137,7 @@ public class HSQLDBRepository implements Repository { * @return ResultSet, or null if there are no found rows * @throws SQLException */ - ResultSet checkedExecute(PreparedStatement preparedStatement) throws SQLException { + public ResultSet checkedExecute(PreparedStatement preparedStatement) throws SQLException { if (!preparedStatement.execute()) throw new SQLException("Fetching from database produced no results"); @@ -154,7 +161,7 @@ public class HSQLDBRepository implements Repository { * @return Long * @throws SQLException */ - Long callIdentity() throws SQLException { + public Long callIdentity() throws SQLException { PreparedStatement preparedStatement = this.connection.prepareStatement("CALL IDENTITY()"); ResultSet resultSet = this.checkedExecute(preparedStatement); if (resultSet == null) @@ -180,7 +187,7 @@ public class HSQLDBRepository implements Repository { * @return true if matching row found in database, false otherwise * @throws SQLException */ - boolean exists(String tableName, String whereClause, Object... objects) throws SQLException { + public boolean exists(String tableName, String whereClause, Object... objects) throws SQLException { PreparedStatement preparedStatement = this.connection .prepareStatement("SELECT TRUE FROM " + tableName + " WHERE " + whereClause + " ORDER BY NULL LIMIT 1"); ResultSet resultSet = this.checkedExecute(preparedStatement); diff --git a/src/repository/hsqldb/HSQLDBSaver.java b/src/repository/hsqldb/HSQLDBSaver.java index 705ee81b..0d6172dd 100644 --- a/src/repository/hsqldb/HSQLDBSaver.java +++ b/src/repository/hsqldb/HSQLDBSaver.java @@ -1,7 +1,6 @@ package repository.hsqldb; import java.math.BigDecimal; -import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; @@ -15,7 +14,7 @@ import java.util.List; *
* {@code SaveHelper helper = new SaveHelper("TableName"); }
* {@code helper.bind("column_name", someColumnValue).bind("column2", columnValue2); }
- * {@code helper.execute(); }
+ * {@code helper.execute(repository); }
*
*/
public class HSQLDBSaver {
@@ -49,14 +48,17 @@ public class HSQLDBSaver {
/**
* Build PreparedStatement using bound column-value pairs then execute it.
+ *
+ * @param repository
+ * TODO
+ * @param repository
*
- * @param connection
* @return the result from {@link PreparedStatement#execute()}
* @throws SQLException
*/
- public boolean execute(Connection connection) throws SQLException {
+ public boolean execute(HSQLDBRepository repository) throws SQLException {
String sql = this.formatInsertWithPlaceholders();
- PreparedStatement preparedStatement = connection.prepareStatement(sql);
+ PreparedStatement preparedStatement = repository.connection.prepareStatement(sql);
this.bindValues(preparedStatement);
diff --git a/src/repository/hsqldb/HSQLDBGenesisTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBGenesisTransactionRepository.java
similarity index 55%
rename from src/repository/hsqldb/HSQLDBGenesisTransactionRepository.java
rename to src/repository/hsqldb/transaction/HSQLDBGenesisTransactionRepository.java
index 5a97639b..f640848a 100644
--- a/src/repository/hsqldb/HSQLDBGenesisTransactionRepository.java
+++ b/src/repository/hsqldb/transaction/HSQLDBGenesisTransactionRepository.java
@@ -1,4 +1,4 @@
-package repository.hsqldb;
+package repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
@@ -7,6 +7,8 @@ import java.sql.SQLException;
import data.transaction.GenesisTransactionData;
import data.transaction.TransactionData;
import repository.DataException;
+import repository.hsqldb.HSQLDBRepository;
+import repository.hsqldb.HSQLDBSaver;
public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionRepository {
@@ -14,7 +16,7 @@ public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionReposit
super(repository);
}
- TransactionData fromBase(byte[] signature, byte[] reference, byte[] creator, long timestamp, BigDecimal fee) {
+ TransactionData fromBase(byte[] signature, byte[] reference, byte[] creator, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature);
if (rs == null)
@@ -25,22 +27,24 @@ public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionReposit
return new GenesisTransactionData(recipient, amount, timestamp, signature);
} catch (SQLException e) {
- return null;
+ throw new DataException("Unable to fetch genesis transaction from repository", e);
}
}
@Override
- public void save(TransactionData transaction) throws DataException {
- super.save(transaction);
+ public void save(TransactionData transactionData) throws DataException {
+ super.save(transactionData);
+
+ GenesisTransactionData genesisTransactionData = (GenesisTransactionData) transactionData;
- GenesisTransactionData genesisTransaction = (GenesisTransactionData) transaction;
-
HSQLDBSaver saveHelper = new HSQLDBSaver("GenesisTransactions");
- saveHelper.bind("signature", genesisTransaction.getSignature()).bind("recipient", genesisTransaction.getRecipient()).bind("amount", genesisTransaction.getAmount());
+ saveHelper.bind("signature", genesisTransactionData.getSignature()).bind("recipient", genesisTransactionData.getRecipient()).bind("amount",
+ genesisTransactionData.getAmount());
+
try {
- saveHelper.execute(this.repository.connection);
+ saveHelper.execute(this.repository);
} catch (SQLException e) {
- throw new DataException(e);
+ throw new DataException("Unable to save genesis transaction into repository", e);
}
}
diff --git a/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java
new file mode 100644
index 00000000..4a32ca26
--- /dev/null
+++ b/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java
@@ -0,0 +1,62 @@
+package repository.hsqldb.transaction;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import data.transaction.IssueAssetTransactionData;
+import data.transaction.TransactionData;
+import repository.DataException;
+import repository.hsqldb.HSQLDBRepository;
+import repository.hsqldb.HSQLDBSaver;
+
+public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepository {
+
+ public HSQLDBIssueAssetTransactionRepository(HSQLDBRepository repository) {
+ super(repository);
+ }
+
+ TransactionData fromBase(byte[] signature, byte[] reference, byte[] creator, long timestamp, BigDecimal fee) throws DataException {
+ try {
+ ResultSet rs = this.repository.checkedExecute(
+ "SELECT issuer, owner, asset_name, description, quantity, is_divisible, asset_id FROM IssueAssetTransactions WHERE signature = ?",
+ signature);
+ if (rs == null)
+ return null;
+
+ byte[] issuerPublicKey = this.repository.getResultSetBytes(rs.getBinaryStream(1));
+ String owner = rs.getString(2);
+ String assetName = rs.getString(3);
+ String description = rs.getString(4);
+ long quantity = rs.getLong(5);
+ boolean isDivisible = rs.getBoolean(6);
+ Long assetId = rs.getLong(7);
+
+ return new IssueAssetTransactionData(assetId, issuerPublicKey, owner, assetName, description, quantity, isDivisible, fee, timestamp, reference,
+ signature);
+ } catch (SQLException e) {
+ throw new DataException("Unable to fetch issue asset transaction from repository", e);
+ }
+ }
+
+ @Override
+ public void save(TransactionData transactionData) throws DataException {
+ super.save(transactionData);
+
+ IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) transactionData;
+
+ HSQLDBSaver saveHelper = new HSQLDBSaver("IssueAssetTransactions");
+
+ saveHelper.bind("signature", issueAssetTransactionData.getSignature()).bind("issuer", issueAssetTransactionData.getIssuerPublicKey())
+ .bind("asset_name", issueAssetTransactionData.getAssetName()).bind("description", issueAssetTransactionData.getDescription())
+ .bind("quantity", issueAssetTransactionData.getQuantity()).bind("is_divisible", issueAssetTransactionData.getIsDivisible())
+ .bind("asset_id", issueAssetTransactionData.getAssetId());
+
+ try {
+ saveHelper.execute(this.repository);
+ } catch (SQLException e) {
+ throw new DataException("Unable to save issue asset transaction into repository", e);
+ }
+ }
+
+}
diff --git a/src/repository/hsqldb/HSQLDBTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java
similarity index 84%
rename from src/repository/hsqldb/HSQLDBTransactionRepository.java
rename to src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java
index c4e60ce2..ad4894d7 100644
--- a/src/repository/hsqldb/HSQLDBTransactionRepository.java
+++ b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java
@@ -1,4 +1,4 @@
-package repository.hsqldb;
+package repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
@@ -10,18 +10,22 @@ import data.transaction.TransactionData;
import qora.transaction.Transaction.TransactionType;
import repository.DataException;
import repository.TransactionRepository;
+import repository.hsqldb.HSQLDBRepository;
+import repository.hsqldb.HSQLDBSaver;
public class HSQLDBTransactionRepository implements TransactionRepository {
protected HSQLDBRepository repository;
private HSQLDBGenesisTransactionRepository genesisTransactionRepository;
+ private HSQLDBIssueAssetTransactionRepository issueAssetTransactionRepository;
public HSQLDBTransactionRepository(HSQLDBRepository repository) {
this.repository = repository;
genesisTransactionRepository = new HSQLDBGenesisTransactionRepository(repository);
+ issueAssetTransactionRepository = new HSQLDBIssueAssetTransactionRepository(repository);
}
- public TransactionData fromSignature(byte[] signature) {
+ public TransactionData fromSignature(byte[] signature) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT type, reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature);
if (rs == null)
@@ -35,11 +39,11 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
return this.fromBase(type, signature, reference, creator, timestamp, fee);
} catch (SQLException e) {
- return null;
+ throw new DataException("Unable to fetch transaction from repository", e);
}
}
- public TransactionData fromReference(byte[] reference) {
+ public TransactionData fromReference(byte[] reference) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT type, signature, creator, creation, fee FROM Transactions WHERE reference = ?", reference);
if (rs == null)
@@ -53,15 +57,19 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
return this.fromBase(type, signature, reference, creator, timestamp, fee);
} catch (SQLException e) {
- return null;
+ throw new DataException("Unable to fetch transaction from repository", e);
}
}
- private TransactionData fromBase(TransactionType type, byte[] signature, byte[] reference, byte[] creator, long timestamp, BigDecimal fee) {
+ private TransactionData fromBase(TransactionType type, byte[] signature, byte[] reference, byte[] creator, long timestamp, BigDecimal fee)
+ throws DataException {
switch (type) {
case GENESIS:
return this.genesisTransactionRepository.fromBase(signature, reference, creator, timestamp, fee);
+ case ISSUE_ASSET:
+ return this.issueAssetTransactionRepository.fromBase(signature, reference, creator, timestamp, fee);
+
default:
return null;
}
@@ -115,7 +123,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
.bind("creator", transactionData.getCreatorPublicKey()).bind("creation", new Timestamp(transactionData.getTimestamp()))
.bind("fee", transactionData.getFee()).bind("milestone_block", null);
try {
- saver.execute(this.repository.connection);
+ saver.execute(this.repository);
} catch (SQLException e) {
throw new DataException(e);
}
diff --git a/src/transform/Transformer.java b/src/transform/Transformer.java
index d35e8b7f..c7b2b550 100644
--- a/src/transform/Transformer.java
+++ b/src/transform/Transformer.java
@@ -2,6 +2,7 @@ package transform;
public abstract class Transformer {
+ public static final int BOOLEAN_LENGTH = 4;
public static final int INT_LENGTH = 4;
public static final int LONG_LENGTH = 8;
@@ -9,7 +10,7 @@ public abstract class Transformer {
public static final int ADDRESS_LENGTH = 25;
public static final int PUBLIC_KEY_LENGTH = 32;
- public static final int SIGNATURE_LENGTH = 64;
+ public static final int SIGNATURE_LENGTH = 64;
public static final int TIMESTAMP_LENGTH = LONG_LENGTH;
}
diff --git a/src/transform/transaction/GenesisTransactionTransformer.java b/src/transform/transaction/GenesisTransactionTransformer.java
index 0baf4ece..d53b589e 100644
--- a/src/transform/transaction/GenesisTransactionTransformer.java
+++ b/src/transform/transaction/GenesisTransactionTransformer.java
@@ -35,20 +35,20 @@ public class GenesisTransactionTransformer extends TransactionTransformer {
return new GenesisTransactionData(recipient, amount, timestamp);
}
- public static int getDataLength(TransactionData baseTransaction) throws TransformationException {
+ public static int getDataLength(TransactionData transactionData) throws TransformationException {
return TYPE_LENGTH + TYPELESS_LENGTH;
}
- public static byte[] toBytes(TransactionData baseTransaction) throws TransformationException {
+ public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
try {
- GenesisTransactionData transaction = (GenesisTransactionData) baseTransaction;
+ GenesisTransactionData genesisTransactionData = (GenesisTransactionData) transactionData;
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
- bytes.write(Ints.toByteArray(transaction.getType().value));
- bytes.write(Longs.toByteArray(transaction.getTimestamp()));
- bytes.write(Base58.decode(transaction.getRecipient()));
- bytes.write(Serialization.serializeBigDecimal(transaction.getAmount()));
+ bytes.write(Ints.toByteArray(genesisTransactionData.getType().value));
+ bytes.write(Longs.toByteArray(genesisTransactionData.getTimestamp()));
+ bytes.write(Base58.decode(genesisTransactionData.getRecipient()));
+ bytes.write(Serialization.serializeBigDecimal(genesisTransactionData.getAmount()));
return bytes.toByteArray();
} catch (IOException | ClassCastException e) {
@@ -57,14 +57,14 @@ public class GenesisTransactionTransformer extends TransactionTransformer {
}
@SuppressWarnings("unchecked")
- public static JSONObject toJSON(TransactionData baseTransaction) throws TransformationException {
- JSONObject json = TransactionTransformer.getBaseJSON(baseTransaction);
+ public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
+ JSONObject json = TransactionTransformer.getBaseJSON(transactionData);
try {
- GenesisTransactionData transaction = (GenesisTransactionData) baseTransaction;
+ GenesisTransactionData genesisTransactionData = (GenesisTransactionData) transactionData;
- json.put("recipient", transaction.getRecipient());
- json.put("amount", transaction.getAmount().toPlainString());
+ json.put("recipient", genesisTransactionData.getRecipient());
+ json.put("amount", genesisTransactionData.getAmount().toPlainString());
} catch (ClassCastException e) {
throw new TransformationException(e);
}
diff --git a/src/transform/transaction/IssueAssetTransactionTransformer.java b/src/transform/transaction/IssueAssetTransactionTransformer.java
new file mode 100644
index 00000000..bf206e9b
--- /dev/null
+++ b/src/transform/transaction/IssueAssetTransactionTransformer.java
@@ -0,0 +1,122 @@
+package transform.transaction;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+
+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 data.transaction.IssueAssetTransactionData;
+import transform.TransformationException;
+import utils.Base58;
+import utils.Serialization;
+
+public class IssueAssetTransactionTransformer extends TransactionTransformer {
+
+ // Property lengths
+ private static final int ISSUER_LENGTH = PUBLIC_KEY_LENGTH;
+ private static final int OWNER_LENGTH = ADDRESS_LENGTH;
+ private static final int NAME_SIZE_LENGTH = INT_LENGTH;
+ private static final int DESCRIPTION_SIZE_LENGTH = INT_LENGTH;
+ private static final int QUANTITY_LENGTH = LONG_LENGTH;
+ private static final int IS_DIVISIBLE_LENGTH = BOOLEAN_LENGTH;
+ private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + ISSUER_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + DESCRIPTION_SIZE_LENGTH
+ + QUANTITY_LENGTH + IS_DIVISIBLE_LENGTH;
+
+ // Other useful lengths
+ public static final int MAX_NAME_SIZE = 400;
+ public static final int MAX_DESCRIPTION_SIZE = 4000;
+
+ static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
+ if (byteBuffer.remaining() < TYPELESS_LENGTH)
+ throw new TransformationException("Byte data too short for GenesisTransaction");
+
+ long timestamp = byteBuffer.getLong();
+
+ byte[] reference = new byte[REFERENCE_LENGTH];
+ byteBuffer.get(reference);
+
+ byte[] issuer = Serialization.deserializePublicKey(byteBuffer);
+ String owner = Serialization.deserializeRecipient(byteBuffer);
+
+ String assetName = Serialization.deserializeSizedString(byteBuffer, MAX_NAME_SIZE);
+ String description = Serialization.deserializeSizedString(byteBuffer, MAX_DESCRIPTION_SIZE);
+
+ // Still need to make sure there are enough bytes left for remaining fields
+ if (byteBuffer.remaining() < QUANTITY_LENGTH + IS_DIVISIBLE_LENGTH + SIGNATURE_LENGTH)
+ throw new TransformationException("Byte data too short for IssueAssetTransaction");
+
+ long quantity = byteBuffer.getLong();
+ boolean isDivisible = byteBuffer.get() != 0;
+
+ BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
+
+ byte[] signature = new byte[SIGNATURE_LENGTH];
+ byteBuffer.get(signature);
+
+ return new IssueAssetTransactionData(issuer, owner, assetName, description, quantity, isDivisible, fee, timestamp, reference, signature);
+ }
+
+ public static int getDataLength(TransactionData transactionData) throws TransformationException {
+ IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) transactionData;
+
+ return TYPE_LENGTH + TYPELESS_LENGTH + issueAssetTransactionData.getAssetName().length() + issueAssetTransactionData.getDescription().length();
+ }
+
+ public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
+ try {
+ IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) transactionData;
+
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+
+ bytes.write(Ints.toByteArray(issueAssetTransactionData.getType().value));
+ bytes.write(Longs.toByteArray(issueAssetTransactionData.getTimestamp()));
+ bytes.write(issueAssetTransactionData.getReference());
+ bytes.write(issueAssetTransactionData.getIssuerPublicKey());
+ bytes.write(Base58.decode(issueAssetTransactionData.getOwner()));
+
+ Serialization.serializeSizedString(bytes, issueAssetTransactionData.getAssetName());
+ Serialization.serializeSizedString(bytes, issueAssetTransactionData.getDescription());
+
+ bytes.write(Longs.toByteArray(issueAssetTransactionData.getQuantity()));
+ bytes.write((byte) (issueAssetTransactionData.getIsDivisible() ? 1 : 0));
+
+ bytes.write(issueAssetTransactionData.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 {
+ IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) transactionData;
+
+ byte[] issuerPublicKey = issueAssetTransactionData.getIssuerPublicKey();
+
+ json.put("issuer", PublicKeyAccount.getAddress(issuerPublicKey));
+ json.put("issuerPublicKey", HashCode.fromBytes(issuerPublicKey).toString());
+ json.put("owner", issueAssetTransactionData.getOwner());
+ json.put("assetName", issueAssetTransactionData.getAssetName());
+ json.put("description", issueAssetTransactionData.getDescription());
+ json.put("quantity", issueAssetTransactionData.getQuantity());
+ json.put("isDivisible", issueAssetTransactionData.getIsDivisible());
+ } catch (ClassCastException e) {
+ throw new TransformationException(e);
+ }
+
+ return json;
+ }
+
+}
diff --git a/src/transform/transaction/TransactionTransformer.java b/src/transform/transaction/TransactionTransformer.java
index e118ab66..18dbf89b 100644
--- a/src/transform/transaction/TransactionTransformer.java
+++ b/src/transform/transaction/TransactionTransformer.java
@@ -13,6 +13,8 @@ import utils.Base58;
public class TransactionTransformer extends Transformer {
protected static final int TYPE_LENGTH = INT_LENGTH;
+ protected static final int REFERENCE_LENGTH = SIGNATURE_LENGTH;
+ protected static final int BASE_TYPELESS_LENGTH = TYPE_LENGTH + TIMESTAMP_LENGTH + REFERENCE_LENGTH + SIGNATURE_LENGTH;
public static TransactionData fromBytes(byte[] bytes) throws TransformationException {
if (bytes == null)
@@ -31,25 +33,34 @@ public class TransactionTransformer extends Transformer {
case GENESIS:
return GenesisTransactionTransformer.fromByteBuffer(byteBuffer);
+ case ISSUE_ASSET:
+ return IssueAssetTransactionTransformer.fromByteBuffer(byteBuffer);
+
default:
return null;
}
}
- public static int getDataLength(TransactionData transaction) throws TransformationException {
- switch (transaction.getType()) {
+ public static int getDataLength(TransactionData transactionData) throws TransformationException {
+ switch (transactionData.getType()) {
case GENESIS:
- return GenesisTransactionTransformer.getDataLength(transaction);
+ return GenesisTransactionTransformer.getDataLength(transactionData);
+
+ case ISSUE_ASSET:
+ return IssueAssetTransactionTransformer.getDataLength(transactionData);
default:
throw new TransformationException("Unsupported transaction type");
}
}
- public static byte[] toBytes(TransactionData transaction) throws TransformationException {
- switch (transaction.getType()) {
+ public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
+ switch (transactionData.getType()) {
case GENESIS:
- return GenesisTransactionTransformer.toBytes(transaction);
+ return GenesisTransactionTransformer.toBytes(transactionData);
+
+ case ISSUE_ASSET:
+ return IssueAssetTransactionTransformer.toBytes(transactionData);
default:
return null;
diff --git a/src/utils/Serialization.java b/src/utils/Serialization.java
index bb308926..980aff01 100644
--- a/src/utils/Serialization.java
+++ b/src/utils/Serialization.java
@@ -1,10 +1,14 @@
package utils;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
+import com.google.common.primitives.Ints;
+
import transform.TransformationException;
import transform.Transformer;
@@ -41,6 +45,11 @@ public class Serialization {
return bytes;
}
+ public static void serializeSizedString(ByteArrayOutputStream bytes, String string) throws UnsupportedEncodingException, IOException {
+ bytes.write(Ints.toByteArray(string.length()));
+ bytes.write(string.getBytes("UTF-8"));
+ }
+
public static String deserializeSizedString(ByteBuffer byteBuffer, int maxSize) throws TransformationException {
int size = byteBuffer.getInt();
if (size > maxSize || size > byteBuffer.remaining())