forked from Qortal/qortal
Work on Assets conversion
* Added AssetData transfer object * Added IssueAssetTransactionData transfer object * Reworked qora.assets.Asset into business layer object * Reworked qora.transaction.IssueAssetTransaction into business layer object * Added corresponding AssetRepository and support in TransactionRepository et al * Fixed BlockChain in line with asset changes * Some renaming inside GenesisTransaction to reflect use of transfer object, not business object * Business transaction objects now take Repository param * Moved HSQLDB transaction repositories into a sub-package * Changed HSQLDBSaver.execute(Connection connection) to .execute(Repository repository) to fix visibility issues and allow repository more control in the future if need be * Changed from "return null" statements in HSQLDB repositories to throw DataException when an error occurs. Better to throw than to silently return null? * Added static version of PublicKeyAccount.verify() for when a repository-backed PublicKeyAccount is not needed * Fixed getter/setter code template incorrectly producing "this.this.field = param"
This commit is contained in:
parent
698c4b6cc9
commit
519331f823
File diff suppressed because one or more lines are too long
64
src/data/assets/AssetData.java
Normal file
64
src/data/assets/AssetData.java
Normal file
@ -0,0 +1,64 @@
|
||||
package data.assets;
|
||||
|
||||
public class AssetData {
|
||||
|
||||
// Properties
|
||||
private Long assetId;
|
||||
private String owner;
|
||||
private String name;
|
||||
private String description;
|
||||
private long quantity;
|
||||
private boolean isDivisible;
|
||||
private byte[] reference;
|
||||
|
||||
// NOTE: key is Long, not long, because it can be null if asset ID/key not yet assigned.
|
||||
public AssetData(Long assetId, String owner, String name, String description, long quantity, boolean isDivisible, byte[] reference) {
|
||||
this.assetId = assetId;
|
||||
this.owner = owner;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.quantity = quantity;
|
||||
this.isDivisible = isDivisible;
|
||||
this.reference = reference;
|
||||
}
|
||||
|
||||
// New asset with unassigned assetId
|
||||
public AssetData(String owner, String name, String description, long quantity, boolean isDivisible, byte[] reference) {
|
||||
this(null, owner, name, description, quantity, isDivisible, reference);
|
||||
}
|
||||
|
||||
// Getters/Setters
|
||||
|
||||
public Long getAssetId() {
|
||||
return this.assetId;
|
||||
}
|
||||
|
||||
public void setAssetId(Long assetId) {
|
||||
this.assetId = assetId;
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return this.owner;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public long getQuantity() {
|
||||
return this.quantity;
|
||||
}
|
||||
|
||||
public boolean getIsDivisible() {
|
||||
return this.isDivisible;
|
||||
}
|
||||
|
||||
public byte[] getReference() {
|
||||
return this.reference;
|
||||
}
|
||||
|
||||
}
|
77
src/data/transaction/IssueAssetTransactionData.java
Normal file
77
src/data/transaction/IssueAssetTransactionData.java
Normal file
@ -0,0 +1,77 @@
|
||||
package data.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import qora.transaction.Transaction.TransactionType;
|
||||
|
||||
public class IssueAssetTransactionData extends TransactionData {
|
||||
|
||||
// Properties
|
||||
// assetId can be null but assigned during save() or during load from repository
|
||||
private Long assetId = null;
|
||||
private byte[] issuerPublicKey;
|
||||
private String owner;
|
||||
private String assetName;
|
||||
private String description;
|
||||
private long quantity;
|
||||
private boolean isDivisible;
|
||||
|
||||
// Constructors
|
||||
|
||||
public IssueAssetTransactionData(Long assetId, byte[] issuerPublicKey, String owner, String assetName, String description, long quantity,
|
||||
boolean isDivisible, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) {
|
||||
super(TransactionType.ISSUE_ASSET, fee, issuerPublicKey, timestamp, reference);
|
||||
|
||||
this.assetId = assetId;
|
||||
this.issuerPublicKey = issuerPublicKey;
|
||||
this.owner = owner;
|
||||
this.assetName = assetName;
|
||||
this.description = description;
|
||||
this.quantity = quantity;
|
||||
this.isDivisible = isDivisible;
|
||||
}
|
||||
|
||||
public IssueAssetTransactionData(byte[] issuerPublicKey, String owner, String assetName, String description, long quantity, boolean isDivisible,
|
||||
BigDecimal fee, long timestamp, byte[] reference, byte[] signature) {
|
||||
this(null, issuerPublicKey, owner, assetName, description, quantity, isDivisible, fee, timestamp, reference, signature);
|
||||
}
|
||||
|
||||
// Getters/Setters
|
||||
|
||||
public Long getAssetId() {
|
||||
return this.assetId;
|
||||
}
|
||||
|
||||
public void setAssetId(Long assetId) {
|
||||
this.assetId = assetId;
|
||||
}
|
||||
|
||||
public byte[] getIssuerPublicKey() {
|
||||
return this.issuerPublicKey;
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return this.owner;
|
||||
}
|
||||
|
||||
public void setOwner(String owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public String getAssetName() {
|
||||
return this.assetName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public long getQuantity() {
|
||||
return this.quantity;
|
||||
}
|
||||
|
||||
public boolean getIsDivisible() {
|
||||
return this.isDivisible;
|
||||
}
|
||||
|
||||
}
|
@ -23,8 +23,12 @@ public class PublicKeyAccount extends Account {
|
||||
}
|
||||
|
||||
public boolean verify(byte[] signature, byte[] message) {
|
||||
return PublicKeyAccount.verify(this.publicKey, signature, message);
|
||||
}
|
||||
|
||||
public static boolean verify(byte[] publicKey, byte[] signature, byte[] message) {
|
||||
try {
|
||||
return Ed25519.verify(signature, message, this.publicKey);
|
||||
return Ed25519.verify(signature, message, publicKey);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1,118 +1,10 @@
|
||||
package qora.assets;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
import qora.account.Account;
|
||||
import repository.hsqldb.HSQLDBSaver;
|
||||
|
||||
public class Asset {
|
||||
|
||||
/**
|
||||
* QORA coins are just another asset but with fixed assetId of zero.
|
||||
*/
|
||||
public static final long QORA = 0L;
|
||||
|
||||
// Properties
|
||||
private Long assetId;
|
||||
private Account owner;
|
||||
private String name;
|
||||
private String description;
|
||||
private long quantity;
|
||||
private boolean isDivisible;
|
||||
private byte[] reference;
|
||||
|
||||
// NOTE: key is Long because it can be null if asset ID/key not yet assigned (which is done by save() method).
|
||||
public Asset(Long assetId, String owner, String name, String description, long quantity, boolean isDivisible, byte[] reference) {
|
||||
this.assetId = assetId;
|
||||
this.owner = new Account(owner);
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.quantity = quantity;
|
||||
this.isDivisible = isDivisible;
|
||||
this.reference = reference;
|
||||
}
|
||||
|
||||
// New asset with unassigned assetId
|
||||
public Asset(String owner, String name, String description, long quantity, boolean isDivisible, byte[] reference) {
|
||||
this(null, owner, name, description, quantity, isDivisible, reference);
|
||||
}
|
||||
|
||||
// Getters/Setters
|
||||
|
||||
public Long getAssetId() {
|
||||
return this.assetId;
|
||||
}
|
||||
|
||||
public Account getOwner() {
|
||||
return this.owner;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public long getQuantity() {
|
||||
return this.quantity;
|
||||
}
|
||||
|
||||
public boolean isDivisible() {
|
||||
return this.isDivisible;
|
||||
}
|
||||
|
||||
public byte[] getReference() {
|
||||
return this.reference;
|
||||
}
|
||||
|
||||
// Load/Save/Delete/Exists
|
||||
|
||||
protected Asset(long assetId) throws SQLException {
|
||||
this(DB.checkedExecute("SELECT owner, asset_name, description, quantity, is_divisible, reference FROM Assets WHERE asset_id = ?", assetId));
|
||||
}
|
||||
|
||||
protected Asset(ResultSet rs) throws SQLException {
|
||||
if (rs == null)
|
||||
throw new NoDataFoundException();
|
||||
|
||||
this.owner = new Account(rs.getString(1));
|
||||
this.name = rs.getString(2);
|
||||
this.description = rs.getString(3);
|
||||
this.quantity = rs.getLong(4);
|
||||
this.isDivisible = rs.getBoolean(5);
|
||||
this.reference = DB.getResultSetBytes(rs.getBinaryStream(6));
|
||||
}
|
||||
|
||||
public static Asset fromAssetId(long assetId) throws SQLException {
|
||||
try {
|
||||
return new Asset(assetId);
|
||||
} catch (NoDataFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void save() throws SQLException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Assets");
|
||||
saveHelper.bind("asset_id", this.assetId).bind("owner", this.owner.getAddress()).bind("asset_name", this.name).bind("description", this.description)
|
||||
.bind("quantity", this.quantity).bind("is_divisible", this.isDivisible).bind("reference", this.reference);
|
||||
saveHelper.execute();
|
||||
|
||||
if (this.assetId == null)
|
||||
this.assetId = DB.callIdentity();
|
||||
}
|
||||
|
||||
public void delete() throws SQLException {
|
||||
DB.checkedExecute("DELETE FROM Assets WHERE asset_id = ?", this.assetId);
|
||||
}
|
||||
|
||||
public static boolean exists(long assetId) throws SQLException {
|
||||
return DB.exists("Assets", "asset_id = ?", assetId);
|
||||
}
|
||||
|
||||
public static boolean exists(String assetName) throws SQLException {
|
||||
return DB.exists("Assets", "asset_name = ?", assetName);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ public class Block {
|
||||
this.transactions = new ArrayList<Transaction>();
|
||||
|
||||
for (TransactionData transactionData : transactionsData)
|
||||
this.transactions.add(Transaction.fromData(transactionData));
|
||||
this.transactions.add(Transaction.fromData(this.repository, transactionData));
|
||||
|
||||
return this.transactions;
|
||||
}
|
||||
@ -242,7 +242,7 @@ public class Block {
|
||||
}
|
||||
|
||||
// Add to block
|
||||
this.transactions.add(Transaction.fromData(transactionData));
|
||||
this.transactions.add(Transaction.fromData(this.repository, transactionData));
|
||||
|
||||
// Update transaction count
|
||||
this.blockData.setTransactionCount(this.blockData.getTransactionCount() + 1);
|
||||
|
@ -3,6 +3,7 @@ package qora.block;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import data.assets.AssetData;
|
||||
import data.block.BlockData;
|
||||
import qora.assets.Asset;
|
||||
import repository.BlockRepository;
|
||||
@ -69,10 +70,9 @@ public class BlockChain {
|
||||
|
||||
// Add QORA asset.
|
||||
// NOTE: Asset's transaction reference is Genesis Block's generator signature which doesn't exist as a transaction!
|
||||
// TODO construct Asset(repository, AssetData) then .save()?
|
||||
Asset qoraAsset = new Asset(Asset.QORA, genesisBlock.getGenerator().getAddress(), "Qora", "This is the simulated Qora asset.", 10_000_000_000L, true,
|
||||
AssetData qoraAssetData = new AssetData(Asset.QORA, genesisBlock.getGenerator().getAddress(), "Qora", "This is the simulated Qora asset.", 10_000_000_000L, true,
|
||||
genesisBlock.getBlockData().getGeneratorSignature());
|
||||
qoraAsset.save();
|
||||
repository.getAssetRepository().save(qoraAssetData);
|
||||
|
||||
repository.saveChanges();
|
||||
}
|
||||
|
@ -195,8 +195,8 @@ public class GenesisBlock extends Block {
|
||||
}
|
||||
|
||||
private void addGenesisTransaction(String recipient, String amount) {
|
||||
this.transactions
|
||||
.add(Transaction.fromData(new GenesisTransactionData(recipient, new BigDecimal(amount).setScale(8), this.getBlockData().getTimestamp())));
|
||||
this.transactions.add(Transaction.fromData(this.repository,
|
||||
new GenesisTransactionData(recipient, new BigDecimal(amount).setScale(8), this.getBlockData().getTimestamp())));
|
||||
this.blockData.setTransactionCount(this.blockData.getTransactionCount() + 1);
|
||||
}
|
||||
|
||||
|
@ -9,13 +9,16 @@ import data.transaction.GenesisTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.PrivateKeyAccount;
|
||||
import qora.crypto.Crypto;
|
||||
import repository.Repository;
|
||||
import transform.TransformationException;
|
||||
import transform.transaction.TransactionTransformer;
|
||||
|
||||
public class GenesisTransaction extends Transaction {
|
||||
|
||||
public GenesisTransaction(TransactionData transactionData) {
|
||||
this.transactionData = transactionData;
|
||||
// Constructors
|
||||
|
||||
public GenesisTransaction(Repository repository, TransactionData transactionData) {
|
||||
super(repository, transactionData);
|
||||
}
|
||||
|
||||
// Processing
|
||||
@ -68,14 +71,14 @@ public class GenesisTransaction extends Transaction {
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() {
|
||||
GenesisTransactionData genesisTransaction = (GenesisTransactionData) this.transactionData;
|
||||
GenesisTransactionData genesisTransactionData = (GenesisTransactionData) this.transactionData;
|
||||
|
||||
// Check amount is zero or positive
|
||||
if (genesisTransaction.getAmount().compareTo(BigDecimal.ZERO) == -1)
|
||||
if (genesisTransactionData.getAmount().compareTo(BigDecimal.ZERO) == -1)
|
||||
return ValidationResult.NEGATIVE_AMOUNT;
|
||||
|
||||
// Check recipient address is valid
|
||||
if (!Crypto.isValidAddress(genesisTransaction.getRecipient()))
|
||||
if (!Crypto.isValidAddress(genesisTransactionData.getRecipient()))
|
||||
return ValidationResult.INVALID_ADDRESS;
|
||||
|
||||
return ValidationResult.OK;
|
||||
|
@ -1,350 +1,126 @@
|
||||
package qora.transaction;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
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.assets.AssetData;
|
||||
import data.transaction.IssueAssetTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.Account;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.assets.Asset;
|
||||
import qora.block.Block;
|
||||
import qora.crypto.Crypto;
|
||||
import repository.hsqldb.HSQLDBSaver;
|
||||
import transform.TransformationException;
|
||||
import utils.Base58;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
import transform.transaction.IssueAssetTransactionTransformer;
|
||||
import utils.NTP;
|
||||
import utils.Serialization;
|
||||
|
||||
public class IssueAssetTransaction extends TransactionHandler {
|
||||
|
||||
// Properties
|
||||
private PublicKeyAccount issuer;
|
||||
private Account owner;
|
||||
private String assetName;
|
||||
private String description;
|
||||
private long quantity;
|
||||
private boolean isDivisible;
|
||||
// assetId assigned during save() or during load from database
|
||||
private Long assetId = null;
|
||||
|
||||
// Property lengths
|
||||
private static final int ISSUER_LENGTH = CREATOR_LENGTH;
|
||||
private static final int OWNER_LENGTH = RECIPIENT_LENGTH;
|
||||
private static final int NAME_SIZE_LENGTH = 4;
|
||||
private static final int DESCRIPTION_SIZE_LENGTH = 4;
|
||||
private static final int QUANTITY_LENGTH = 8;
|
||||
private static final int IS_DIVISIBLE_LENGTH = 1;
|
||||
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
|
||||
private static final int MAX_NAME_SIZE = 400;
|
||||
private static final int MAX_DESCRIPTION_SIZE = 4000;
|
||||
public class IssueAssetTransaction extends Transaction {
|
||||
|
||||
// Constructors
|
||||
|
||||
/**
|
||||
* Reconstruct an IssueAssetTransaction, including signature.
|
||||
*
|
||||
* @param issuer
|
||||
* @param owner
|
||||
* @param assetName
|
||||
* @param description
|
||||
* @param quantity
|
||||
* @param isDivisible
|
||||
* @param fee
|
||||
* @param timestamp
|
||||
* @param reference
|
||||
* @param signature
|
||||
*/
|
||||
public IssueAssetTransaction(PublicKeyAccount issuer, String owner, String assetName, String description, long quantity, boolean isDivisible,
|
||||
BigDecimal fee, long timestamp, byte[] reference, byte[] signature) {
|
||||
super(TransactionType.ISSUE_ASSET, fee, issuer, timestamp, reference, signature);
|
||||
|
||||
this.issuer = issuer;
|
||||
this.owner = new Account(owner);
|
||||
this.assetName = assetName;
|
||||
this.description = description;
|
||||
this.quantity = quantity;
|
||||
this.isDivisible = isDivisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new IssueAssetTransaction.
|
||||
*
|
||||
* @param issuer
|
||||
* @param owner
|
||||
* @param assetName
|
||||
* @param description
|
||||
* @param quantity
|
||||
* @param isDivisible
|
||||
* @param fee
|
||||
* @param timestamp
|
||||
* @param reference
|
||||
*/
|
||||
public IssueAssetTransaction(PublicKeyAccount issuer, String owner, String assetName, String description, long quantity, boolean isDivisible,
|
||||
BigDecimal fee, long timestamp, byte[] reference) {
|
||||
this(issuer, owner, assetName, description, quantity, isDivisible, fee, timestamp, reference, null);
|
||||
}
|
||||
|
||||
// Getters/Setters
|
||||
|
||||
public PublicKeyAccount getIssuer() {
|
||||
return this.issuer;
|
||||
}
|
||||
|
||||
public Account getOwner() {
|
||||
return this.owner;
|
||||
}
|
||||
|
||||
public String getAssetName() {
|
||||
return this.assetName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public long getQuantity() {
|
||||
return this.quantity;
|
||||
}
|
||||
|
||||
public boolean isDivisible() {
|
||||
return this.isDivisible;
|
||||
}
|
||||
|
||||
// More information
|
||||
|
||||
/**
|
||||
* Return asset ID assigned if this transaction has been processed.
|
||||
*
|
||||
* @return asset ID if transaction has been processed and asset created, null otherwise
|
||||
*/
|
||||
public Long getAssetId() {
|
||||
return this.assetId;
|
||||
}
|
||||
|
||||
public int getDataLength() {
|
||||
return TYPE_LENGTH + TYPELESS_LENGTH + assetName.length() + description.length();
|
||||
}
|
||||
|
||||
// Load/Save
|
||||
|
||||
/**
|
||||
* Construct IssueAssetTransaction from DB using signature.
|
||||
*
|
||||
* @param signature
|
||||
* @throws NoDataFoundException
|
||||
* if no matching row found
|
||||
* @throws SQLException
|
||||
*/
|
||||
protected IssueAssetTransaction(byte[] signature) throws SQLException {
|
||||
super(TransactionType.ISSUE_ASSET, signature);
|
||||
|
||||
ResultSet rs = DB.checkedExecute(
|
||||
"SELECT issuer, owner, asset_name, description, quantity, is_divisible, asset_id FROM IssueAssetTransactions WHERE signature = ?", signature);
|
||||
if (rs == null)
|
||||
throw new NoDataFoundException();
|
||||
|
||||
this.issuer = new PublicKeyAccount(DB.getResultSetBytes(rs.getBinaryStream(2), ISSUER_LENGTH));
|
||||
this.owner = new Account(rs.getString(2));
|
||||
this.assetName = rs.getString(3);
|
||||
this.description = rs.getString(4);
|
||||
this.quantity = rs.getLong(5);
|
||||
this.isDivisible = rs.getBoolean(6);
|
||||
this.assetId = rs.getLong(7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load IssueAssetTransaction from DB using signature.
|
||||
*
|
||||
* @param signature
|
||||
* @return PaymentTransaction, or null if not found
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static IssueAssetTransaction fromSignature(byte[] signature) throws SQLException {
|
||||
try {
|
||||
return new IssueAssetTransaction(signature);
|
||||
} catch (NoDataFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() throws SQLException {
|
||||
super.save();
|
||||
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("IssueAssetTransactions");
|
||||
saveHelper.bind("signature", this.signature).bind("creator", this.creator.getPublicKey()).bind("asset_name", this.assetName)
|
||||
.bind("description", this.description).bind("quantity", this.quantity).bind("is_divisible", this.isDivisible).bind("asset_id", this.assetId);
|
||||
saveHelper.execute();
|
||||
}
|
||||
|
||||
// Converters
|
||||
|
||||
protected static TransactionHandler parse(ByteBuffer byteBuffer) throws TransformationException {
|
||||
if (byteBuffer.remaining() < TYPELESS_LENGTH)
|
||||
throw new TransformationException("Byte data too short for IssueAssetTransaction");
|
||||
|
||||
long timestamp = byteBuffer.getLong();
|
||||
|
||||
byte[] reference = new byte[REFERENCE_LENGTH];
|
||||
byteBuffer.get(reference);
|
||||
|
||||
PublicKeyAccount 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 IssueAssetTransaction(issuer, owner, assetName, description, quantity, isDivisible, fee, timestamp, reference, signature);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public JSONObject toJSON() throws SQLException {
|
||||
JSONObject json = getBaseJSON();
|
||||
|
||||
json.put("issuer", this.creator.getAddress());
|
||||
json.put("issuerPublicKey", HashCode.fromBytes(this.creator.getPublicKey()).toString());
|
||||
json.put("owner", this.owner.getAddress());
|
||||
json.put("assetName", this.assetName);
|
||||
json.put("description", this.description);
|
||||
json.put("quantity", this.quantity);
|
||||
json.put("isDivisible", this.isDivisible);
|
||||
|
||||
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.issuer.getPublicKey());
|
||||
bytes.write(Base58.decode(this.owner.getAddress()));
|
||||
|
||||
bytes.write(Ints.toByteArray(this.assetName.length()));
|
||||
bytes.write(this.assetName.getBytes("UTF-8"));
|
||||
|
||||
bytes.write(Ints.toByteArray(this.description.length()));
|
||||
bytes.write(this.description.getBytes("UTF-8"));
|
||||
|
||||
bytes.write(Longs.toByteArray(this.quantity));
|
||||
bytes.write((byte) (this.isDivisible ? 1 : 0));
|
||||
|
||||
bytes.write(this.signature);
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
public IssueAssetTransaction(Repository repository, TransactionData transactionData) {
|
||||
super(repository, transactionData);
|
||||
}
|
||||
|
||||
// Processing
|
||||
|
||||
public ValidationResult isValid() throws SQLException {
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Lowest cost checks first
|
||||
|
||||
IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) this.transactionData;
|
||||
|
||||
// Are IssueAssetTransactions even allowed at this point?
|
||||
if (NTP.getTime() < Block.ASSETS_RELEASE_TIMESTAMP)
|
||||
return ValidationResult.NOT_YET_RELEASED;
|
||||
|
||||
// Check owner address is valid
|
||||
if (!Crypto.isValidAddress(this.owner.getAddress()))
|
||||
if (!Crypto.isValidAddress(issueAssetTransactionData.getOwner()))
|
||||
return ValidationResult.INVALID_ADDRESS;
|
||||
|
||||
// Check name size bounds
|
||||
if (this.assetName.length() < 1 || this.assetName.length() > MAX_NAME_SIZE)
|
||||
if (issueAssetTransactionData.getAssetName().length() < 1
|
||||
|| issueAssetTransactionData.getAssetName().length() > IssueAssetTransactionTransformer.MAX_NAME_SIZE)
|
||||
return ValidationResult.INVALID_NAME_LENGTH;
|
||||
|
||||
// Check description size bounds
|
||||
if (this.description.length() < 1 || this.description.length() > MAX_NAME_SIZE)
|
||||
if (issueAssetTransactionData.getDescription().length() < 1
|
||||
|| issueAssetTransactionData.getDescription().length() > IssueAssetTransactionTransformer.MAX_DESCRIPTION_SIZE)
|
||||
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
|
||||
|
||||
// Check quantity - either 10 billion or if that's not enough: a billion billion!
|
||||
long maxQuantity = this.isDivisible ? 10_000_000_000L : 1_000_000_000_000_000_000L;
|
||||
if (this.quantity < 1 || this.quantity > maxQuantity)
|
||||
long maxQuantity = issueAssetTransactionData.getIsDivisible() ? 10_000_000_000L : 1_000_000_000_000_000_000L;
|
||||
if (issueAssetTransactionData.getQuantity() < 1 || issueAssetTransactionData.getQuantity() > maxQuantity)
|
||||
return ValidationResult.INVALID_QUANTITY;
|
||||
|
||||
// Check fee is positive
|
||||
if (this.fee.compareTo(BigDecimal.ZERO) <= 0)
|
||||
if (issueAssetTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
|
||||
return ValidationResult.NEGATIVE_FEE;
|
||||
|
||||
// Check reference is correct
|
||||
if (!Arrays.equals(this.issuer.getLastReference(), this.reference))
|
||||
PublicKeyAccount issuer = new PublicKeyAccount(this.repository, issueAssetTransactionData.getIssuerPublicKey());
|
||||
|
||||
if (!Arrays.equals(issuer.getLastReference(), issueAssetTransactionData.getReference()))
|
||||
return ValidationResult.INVALID_REFERENCE;
|
||||
|
||||
// Check issuer has enough funds
|
||||
if (this.issuer.getConfirmedBalance(Asset.QORA).compareTo(this.fee) == -1)
|
||||
if (issuer.getConfirmedBalance(Asset.QORA).compareTo(issueAssetTransactionData.getFee()) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
// XXX: Surely we want to check the asset name isn't already taken?
|
||||
if (Asset.exists(this.assetName))
|
||||
// XXX: Surely we want to check the asset name isn't already taken? This check is not present in gen1.
|
||||
if (this.repository.getAssetRepository().assetExists(issueAssetTransactionData.getAssetName()))
|
||||
return ValidationResult.ASSET_ALREADY_EXISTS;
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
public void process() throws SQLException {
|
||||
public void process() throws DataException {
|
||||
IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) this.transactionData;
|
||||
|
||||
// Issue asset
|
||||
Asset asset = new Asset(owner.getAddress(), this.assetName, this.description, this.quantity, this.isDivisible, this.reference);
|
||||
asset.save();
|
||||
AssetData assetData = new AssetData(issueAssetTransactionData.getOwner(), issueAssetTransactionData.getAssetName(),
|
||||
issueAssetTransactionData.getDescription(), issueAssetTransactionData.getQuantity(), issueAssetTransactionData.getIsDivisible(),
|
||||
issueAssetTransactionData.getReference());
|
||||
this.repository.getAssetRepository().save(assetData);
|
||||
|
||||
// Note newly assigned asset ID in our transaction record
|
||||
this.assetId = asset.getAssetId();
|
||||
issueAssetTransactionData.setAssetId(assetData.getAssetId());
|
||||
|
||||
this.save();
|
||||
// Save this transaction, now with corresponding assetId
|
||||
this.repository.getTransactionRepository().save(issueAssetTransactionData);
|
||||
|
||||
// Update issuer's balance
|
||||
this.issuer.setConfirmedBalance(Asset.QORA, this.issuer.getConfirmedBalance(Asset.QORA).subtract(this.fee));
|
||||
Account issuer = new PublicKeyAccount(this.repository, issueAssetTransactionData.getIssuerPublicKey());
|
||||
issuer.setConfirmedBalance(Asset.QORA, issuer.getConfirmedBalance(Asset.QORA).subtract(issueAssetTransactionData.getFee()));
|
||||
|
||||
// Update issuer's reference
|
||||
this.issuer.setLastReference(this.signature);
|
||||
issuer.setLastReference(issueAssetTransactionData.getSignature());
|
||||
|
||||
// Add asset to owner
|
||||
this.owner.setConfirmedBalance(this.assetId, BigDecimal.valueOf(this.quantity).setScale(8));
|
||||
Account owner = new Account(this.repository, issueAssetTransactionData.getOwner());
|
||||
owner.setConfirmedBalance(issueAssetTransactionData.getAssetId(), BigDecimal.valueOf(issueAssetTransactionData.getQuantity()).setScale(8));
|
||||
}
|
||||
|
||||
public void orphan() throws SQLException {
|
||||
public void orphan() throws DataException {
|
||||
IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) this.transactionData;
|
||||
|
||||
// Remove asset from owner
|
||||
this.owner.deleteBalance(this.assetId);
|
||||
Account owner = new Account(this.repository, issueAssetTransactionData.getOwner());
|
||||
owner.deleteBalance(issueAssetTransactionData.getAssetId());
|
||||
|
||||
// Unissue asset
|
||||
Asset asset = Asset.fromAssetId(this.assetId);
|
||||
asset.delete();
|
||||
this.repository.getAssetRepository().delete(issueAssetTransactionData.getAssetId());
|
||||
|
||||
this.delete();
|
||||
// Delete this transaction itself
|
||||
this.repository.getTransactionRepository().delete(issueAssetTransactionData);
|
||||
|
||||
// Update issuer's balance
|
||||
this.issuer.setConfirmedBalance(Asset.QORA, this.issuer.getConfirmedBalance(Asset.QORA).add(this.fee));
|
||||
Account issuer = new PublicKeyAccount(this.repository, issueAssetTransactionData.getIssuerPublicKey());
|
||||
issuer.setConfirmedBalance(Asset.QORA, issuer.getConfirmedBalance(Asset.QORA).add(issueAssetTransactionData.getFee()));
|
||||
|
||||
// Update issuer's reference
|
||||
this.issuer.setLastReference(this.reference);
|
||||
issuer.setLastReference(issueAssetTransactionData.getReference());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,8 +10,10 @@ import static java.util.stream.Collectors.toMap;
|
||||
import data.block.BlockData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.PrivateKeyAccount;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.block.Block;
|
||||
import qora.block.BlockChain;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
import repository.RepositoryManager;
|
||||
import settings.Settings;
|
||||
@ -65,14 +67,23 @@ public abstract class Transaction {
|
||||
protected static final BigDecimal minFeePerByte = BigDecimal.ONE.divide(maxBytePerFee, MathContext.DECIMAL32);
|
||||
|
||||
// Properties
|
||||
protected Repository repository;
|
||||
protected TransactionData transactionData;
|
||||
|
||||
// Constructors
|
||||
|
||||
public static Transaction fromData(TransactionData transactionData) {
|
||||
protected Transaction(Repository repository, TransactionData transactionData) {
|
||||
this.repository = repository;
|
||||
this.transactionData = transactionData;
|
||||
}
|
||||
|
||||
public static Transaction fromData(Repository repository, TransactionData transactionData) {
|
||||
switch (transactionData.getType()) {
|
||||
case GENESIS:
|
||||
return new GenesisTransaction(transactionData);
|
||||
return new GenesisTransaction(repository, transactionData);
|
||||
|
||||
case ISSUE_ASSET:
|
||||
return new IssueAssetTransaction(repository, transactionData);
|
||||
|
||||
default:
|
||||
return null;
|
||||
@ -142,20 +153,21 @@ public abstract class Transaction {
|
||||
* @return height, or 0 if not in blockchain (i.e. unconfirmed)
|
||||
*/
|
||||
public int getHeight() {
|
||||
return RepositoryManager.getRepository().getTransactionRepository().getHeight(this.transactionData);
|
||||
return this.repository.getTransactionRepository().getHeight(this.transactionData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of confirmations for this transaction.
|
||||
*
|
||||
* @return confirmation count, or 0 if not in blockchain (i.e. unconfirmed)
|
||||
* @throws DataException
|
||||
*/
|
||||
public int getConfirmations() {
|
||||
public int getConfirmations() throws DataException {
|
||||
int ourHeight = getHeight();
|
||||
if (ourHeight == 0)
|
||||
return 0;
|
||||
|
||||
int blockChainHeight = BlockChain.getHeight();
|
||||
int blockChainHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||
if (blockChainHeight == 0)
|
||||
return 0;
|
||||
|
||||
@ -170,33 +182,35 @@ public abstract class Transaction {
|
||||
* @return Block, or null if transaction is not in a Block
|
||||
*/
|
||||
public BlockData getBlock() {
|
||||
return RepositoryManager.getTransactionRepository().toBlock(this.transactionData);
|
||||
return this.repository.getTransactionRepository().toBlock(this.transactionData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load parent Transaction from DB via this transaction's reference.
|
||||
*
|
||||
* @return Transaction, or null if no parent found (which should not happen)
|
||||
* @throws DataException
|
||||
*/
|
||||
public TransactionData getParent() {
|
||||
public TransactionData getParent() throws DataException {
|
||||
byte[] reference = this.transactionData.getReference();
|
||||
if (reference == null)
|
||||
return null;
|
||||
|
||||
return RepositoryManager.getTransactionRepository().fromSignature(reference);
|
||||
return this.repository.getTransactionRepository().fromSignature(reference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load child Transaction from DB, if any.
|
||||
*
|
||||
* @return Transaction, or null if no child found
|
||||
* @throws DataException
|
||||
*/
|
||||
public TransactionData getChild() {
|
||||
public TransactionData getChild() throws DataException {
|
||||
byte[] signature = this.transactionData.getSignature();
|
||||
if (signature == null)
|
||||
return null;
|
||||
|
||||
return RepositoryManager.getTransactionRepository().fromSignature(signature);
|
||||
return this.repository.getTransactionRepository().fromSignature(signature);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -227,8 +241,7 @@ public abstract class Transaction {
|
||||
if (signature == null)
|
||||
return false;
|
||||
|
||||
// XXX: return this.transaction.getCreator().verify(signature, this.toBytesLessSignature());
|
||||
return false;
|
||||
return PublicKeyAccount.verify(this.transactionData.getCreatorPublicKey(), signature, this.toBytesLessSignature());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -236,30 +249,28 @@ public abstract class Transaction {
|
||||
* <p>
|
||||
* Checks if transaction can have {@link TransactionHandler#process()} called.
|
||||
* <p>
|
||||
* Expected to be called within an ongoing SQL Transaction, typically by {@link Block#process()}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Processes transaction, updating balances, references, assets, etc. as appropriate.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Undoes transaction, updating balances, references, assets, etc. as appropriate.
|
||||
* <p>
|
||||
* 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;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
17
src/repository/AssetRepository.java
Normal file
17
src/repository/AssetRepository.java
Normal file
@ -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;
|
||||
|
||||
}
|
@ -4,6 +4,8 @@ public interface Repository {
|
||||
|
||||
public AccountRepository getAccountRepository();
|
||||
|
||||
public AssetRepository getAssetRepository();
|
||||
|
||||
public BlockRepository getBlockRepository();
|
||||
|
||||
public TransactionRepository getTransactionRepository();
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
78
src/repository/hsqldb/HSQLDBAssetRepository.java
Normal file
78
src/repository/hsqldb/HSQLDBAssetRepository.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
* <p>
|
||||
* {@code SaveHelper helper = new SaveHelper("TableName"); }<br>
|
||||
* {@code helper.bind("column_name", someColumnValue).bind("column2", columnValue2); }<br>
|
||||
* {@code helper.execute(); }<br>
|
||||
* {@code helper.execute(repository); }<br>
|
||||
*
|
||||
*/
|
||||
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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
122
src/transform/transaction/IssueAssetTransactionTransformer.java
Normal file
122
src/transform/transaction/IssueAssetTransactionTransformer.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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())
|
||||
|
Loading…
Reference in New Issue
Block a user