forked from Qortal/qortal
Merge branch 'asset-unicode' into launch
This commit is contained in:
commit
ed178e744d
@ -8,6 +8,7 @@ import org.qortal.data.transaction.UpdateAssetTransactionData;
|
|||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.utils.Amounts;
|
import org.qortal.utils.Amounts;
|
||||||
|
import org.qortal.utils.Unicode;
|
||||||
|
|
||||||
public class Asset {
|
public class Asset {
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ public class Asset {
|
|||||||
|
|
||||||
// Other useful constants
|
// Other useful constants
|
||||||
|
|
||||||
|
public static final int MIN_NAME_SIZE = 3;
|
||||||
public static final int MAX_NAME_SIZE = 40;
|
public static final int MAX_NAME_SIZE = 40;
|
||||||
public static final int MAX_DESCRIPTION_SIZE = 4000;
|
public static final int MAX_DESCRIPTION_SIZE = 4000;
|
||||||
public static final int MAX_DATA_SIZE = 400000;
|
public static final int MAX_DATA_SIZE = 400000;
|
||||||
@ -49,8 +51,8 @@ public class Asset {
|
|||||||
this.assetData = new AssetData(ownerAddress, issueAssetTransactionData.getAssetName(),
|
this.assetData = new AssetData(ownerAddress, issueAssetTransactionData.getAssetName(),
|
||||||
issueAssetTransactionData.getDescription(), issueAssetTransactionData.getQuantity(),
|
issueAssetTransactionData.getDescription(), issueAssetTransactionData.getQuantity(),
|
||||||
issueAssetTransactionData.isDivisible(), issueAssetTransactionData.getData(),
|
issueAssetTransactionData.isDivisible(), issueAssetTransactionData.getData(),
|
||||||
issueAssetTransactionData.isUnspendable(),
|
issueAssetTransactionData.isUnspendable(), issueAssetTransactionData.getTxGroupId(),
|
||||||
issueAssetTransactionData.getTxGroupId(), issueAssetTransactionData.getSignature());
|
issueAssetTransactionData.getSignature(), issueAssetTransactionData.getReducedAssetName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Asset(Repository repository, long assetId) throws DataException {
|
public Asset(Repository repository, long assetId) throws DataException {
|
||||||
@ -66,6 +68,10 @@ public class Asset {
|
|||||||
|
|
||||||
// Processing
|
// Processing
|
||||||
|
|
||||||
|
public static String reduceName(String assetName) {
|
||||||
|
return Unicode.sanitize(assetName);
|
||||||
|
}
|
||||||
|
|
||||||
public void issue() throws DataException {
|
public void issue() throws DataException {
|
||||||
this.repository.getAssetRepository().save(this.assetData);
|
this.repository.getAssetRepository().save(this.assetData);
|
||||||
}
|
}
|
||||||
|
@ -20,11 +20,17 @@ public class AssetData {
|
|||||||
private String data;
|
private String data;
|
||||||
private boolean isUnspendable;
|
private boolean isUnspendable;
|
||||||
private int creationGroupId;
|
private int creationGroupId;
|
||||||
|
|
||||||
// No need to expose this via API
|
// No need to expose this via API
|
||||||
@XmlTransient
|
@XmlTransient
|
||||||
@Schema(hidden = true)
|
@Schema(hidden = true)
|
||||||
private byte[] reference;
|
private byte[] reference;
|
||||||
|
|
||||||
|
// For internal use only
|
||||||
|
@XmlTransient
|
||||||
|
@Schema(hidden = true)
|
||||||
|
private String reducedAssetName;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
// necessary for JAXB serialization
|
// necessary for JAXB serialization
|
||||||
@ -32,7 +38,8 @@ public class AssetData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: key is Long, not long, because it can be null if asset ID/key not yet assigned.
|
// 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, String data, boolean isUnspendable, int creationGroupId, byte[] reference) {
|
public AssetData(Long assetId, String owner, String name, String description, long quantity, boolean isDivisible,
|
||||||
|
String data, boolean isUnspendable, int creationGroupId, byte[] reference, String reducedAssetName) {
|
||||||
this.assetId = assetId;
|
this.assetId = assetId;
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -43,11 +50,13 @@ public class AssetData {
|
|||||||
this.isUnspendable = isUnspendable;
|
this.isUnspendable = isUnspendable;
|
||||||
this.creationGroupId = creationGroupId;
|
this.creationGroupId = creationGroupId;
|
||||||
this.reference = reference;
|
this.reference = reference;
|
||||||
|
this.reducedAssetName = reducedAssetName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// New asset with unassigned assetId
|
// New asset with unassigned assetId
|
||||||
public AssetData(String owner, String name, String description, long quantity, boolean isDivisible, String data, boolean isUnspendable, int creationGroupId, byte[] reference) {
|
public AssetData(String owner, String name, String description, long quantity, boolean isDivisible, String data,
|
||||||
this(null, owner, name, description, quantity, isDivisible, data, isUnspendable, creationGroupId, reference);
|
boolean isUnspendable, int creationGroupId, byte[] reference, String reducedAssetName) {
|
||||||
|
this(null, owner, name, description, quantity, isDivisible, data, isUnspendable, creationGroupId, reference, reducedAssetName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters/Setters
|
// Getters/Setters
|
||||||
@ -112,4 +121,12 @@ public class AssetData {
|
|||||||
this.reference = reference;
|
this.reference = reference;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getReducedAssetName() {
|
||||||
|
return this.reducedAssetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReducedAssetName(String reducedAssetName) {
|
||||||
|
this.reducedAssetName = reducedAssetName;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,12 @@ package org.qortal.data.transaction;
|
|||||||
import javax.xml.bind.Unmarshaller;
|
import javax.xml.bind.Unmarshaller;
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlTransient;
|
||||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||||
|
|
||||||
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
|
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
|
||||||
import org.qortal.account.NullAccount;
|
import org.qortal.account.NullAccount;
|
||||||
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.block.GenesisBlock;
|
import org.qortal.block.GenesisBlock;
|
||||||
import org.qortal.transaction.Transaction.TransactionType;
|
import org.qortal.transaction.Transaction.TransactionType;
|
||||||
|
|
||||||
@ -48,6 +50,11 @@ public class IssueAssetTransactionData extends TransactionData {
|
|||||||
@Schema(description = "whether non-owner holders of asset are barred from using asset", example = "false")
|
@Schema(description = "whether non-owner holders of asset are barred from using asset", example = "false")
|
||||||
private boolean isUnspendable;
|
private boolean isUnspendable;
|
||||||
|
|
||||||
|
// For internal use
|
||||||
|
@Schema(hidden = true)
|
||||||
|
@XmlTransient
|
||||||
|
private String reducedAssetName;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
// For JAXB
|
// For JAXB
|
||||||
@ -64,12 +71,20 @@ public class IssueAssetTransactionData extends TransactionData {
|
|||||||
if (parent instanceof GenesisBlock.GenesisInfo && this.issuerPublicKey == null)
|
if (parent instanceof GenesisBlock.GenesisInfo && this.issuerPublicKey == null)
|
||||||
this.issuerPublicKey = NullAccount.PUBLIC_KEY;
|
this.issuerPublicKey = NullAccount.PUBLIC_KEY;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we're being constructed as part of the genesis block info inside blockchain config
|
||||||
|
* then we need to construct 'reduced' form of asset name.
|
||||||
|
*/
|
||||||
|
if (parent instanceof GenesisBlock.GenesisInfo && this.reducedAssetName == null)
|
||||||
|
this.reducedAssetName = Asset.reduceName(this.assetName);
|
||||||
|
|
||||||
this.creatorPublicKey = this.issuerPublicKey;
|
this.creatorPublicKey = this.issuerPublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** From repository */
|
/** From repository */
|
||||||
public IssueAssetTransactionData(BaseTransactionData baseTransactionData,
|
public IssueAssetTransactionData(BaseTransactionData baseTransactionData, Long assetId, String assetName,
|
||||||
Long assetId, String assetName, String description, long quantity, boolean isDivisible, String data, boolean isUnspendable) {
|
String description, long quantity, boolean isDivisible, String data, boolean isUnspendable,
|
||||||
|
String reducedAssetName) {
|
||||||
super(TransactionType.ISSUE_ASSET, baseTransactionData);
|
super(TransactionType.ISSUE_ASSET, baseTransactionData);
|
||||||
|
|
||||||
this.assetId = assetId;
|
this.assetId = assetId;
|
||||||
@ -80,12 +95,13 @@ public class IssueAssetTransactionData extends TransactionData {
|
|||||||
this.isDivisible = isDivisible;
|
this.isDivisible = isDivisible;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.isUnspendable = isUnspendable;
|
this.isUnspendable = isUnspendable;
|
||||||
|
this.reducedAssetName = reducedAssetName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** From network/API */
|
/** From network/API */
|
||||||
public IssueAssetTransactionData(BaseTransactionData baseTransactionData, String assetName, String description,
|
public IssueAssetTransactionData(BaseTransactionData baseTransactionData, String assetName, String description,
|
||||||
long quantity, boolean isDivisible, String data, boolean isUnspendable) {
|
long quantity, boolean isDivisible, String data, boolean isUnspendable) {
|
||||||
this(baseTransactionData, null, assetName, description, quantity, isDivisible, data, isUnspendable);
|
this(baseTransactionData, null, assetName, description, quantity, isDivisible, data, isUnspendable, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters/Setters
|
// Getters/Setters
|
||||||
@ -126,4 +142,12 @@ public class IssueAssetTransactionData extends TransactionData {
|
|||||||
return this.isUnspendable;
|
return this.isUnspendable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getReducedAssetName() {
|
||||||
|
return this.reducedAssetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReducedAssetName(String reducedAssetName) {
|
||||||
|
this.reducedAssetName = reducedAssetName;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@ public interface AssetRepository {
|
|||||||
|
|
||||||
public boolean assetExists(String assetName) throws DataException;
|
public boolean assetExists(String assetName) throws DataException;
|
||||||
|
|
||||||
|
public boolean reducedAssetNameExists(String reducedAssetName) throws DataException;
|
||||||
|
|
||||||
public List<AssetData> getAllAssets(Integer limit, Integer offset, Boolean reverse) throws DataException;
|
public List<AssetData> getAllAssets(Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
public default List<AssetData> getAllAssets() throws DataException {
|
public default List<AssetData> getAllAssets() throws DataException {
|
||||||
|
@ -25,7 +25,9 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AssetData fromAssetId(long assetId) throws DataException {
|
public AssetData fromAssetId(long assetId) throws DataException {
|
||||||
String sql = "SELECT owner, asset_name, description, quantity, is_divisible, data, is_unspendable, creation_group_id, reference FROM Assets WHERE asset_id = ?";
|
String sql = "SELECT owner, asset_name, description, quantity, is_divisible, data, "
|
||||||
|
+ "is_unspendable, creation_group_id, reference, reduced_asset_name "
|
||||||
|
+ "FROM Assets WHERE asset_id = ?";
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, assetId)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, assetId)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
@ -40,9 +42,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
boolean isUnspendable = resultSet.getBoolean(7);
|
boolean isUnspendable = resultSet.getBoolean(7);
|
||||||
int creationGroupId = resultSet.getInt(8);
|
int creationGroupId = resultSet.getInt(8);
|
||||||
byte[] reference = resultSet.getBytes(9);
|
byte[] reference = resultSet.getBytes(9);
|
||||||
|
String reducedAssetName = resultSet.getString(10);
|
||||||
|
|
||||||
return new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data, isUnspendable,
|
return new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data, isUnspendable,
|
||||||
creationGroupId, reference);
|
creationGroupId, reference, reducedAssetName);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to fetch asset from repository", e);
|
throw new DataException("Unable to fetch asset from repository", e);
|
||||||
}
|
}
|
||||||
@ -50,7 +53,9 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AssetData fromAssetName(String assetName) throws DataException {
|
public AssetData fromAssetName(String assetName) throws DataException {
|
||||||
String sql = "SELECT owner, asset_id, description, quantity, is_divisible, data, is_unspendable, creation_group_id, reference FROM Assets WHERE asset_name = ?";
|
String sql = "SELECT owner, asset_id, description, quantity, is_divisible, data, "
|
||||||
|
+ "is_unspendable, creation_group_id, reference, reduced_asset_name "
|
||||||
|
+ "FROM Assets WHERE asset_name = ?";
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, assetName)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, assetName)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
@ -65,9 +70,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
boolean isUnspendable = resultSet.getBoolean(7);
|
boolean isUnspendable = resultSet.getBoolean(7);
|
||||||
int creationGroupId = resultSet.getInt(8);
|
int creationGroupId = resultSet.getInt(8);
|
||||||
byte[] reference = resultSet.getBytes(9);
|
byte[] reference = resultSet.getBytes(9);
|
||||||
|
String reducedAssetName = resultSet.getString(10);
|
||||||
|
|
||||||
return new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data, isUnspendable,
|
return new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data, isUnspendable,
|
||||||
creationGroupId, reference);
|
creationGroupId, reference, reducedAssetName);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to fetch asset from repository", e);
|
throw new DataException("Unable to fetch asset from repository", e);
|
||||||
}
|
}
|
||||||
@ -91,10 +97,20 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean reducedAssetNameExists(String reducedAssetName) throws DataException {
|
||||||
|
try {
|
||||||
|
return this.repository.exists("Assets", "reduced_asset_name = ?", reducedAssetName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to check for asset in repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AssetData> getAllAssets(Integer limit, Integer offset, Boolean reverse) throws DataException {
|
public List<AssetData> getAllAssets(Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
StringBuilder sql = new StringBuilder(256);
|
StringBuilder sql = new StringBuilder(256);
|
||||||
sql.append("SELECT asset_id, owner, asset_name, description, quantity, is_divisible, data, is_unspendable, creation_group_id, reference "
|
sql.append("SELECT asset_id, owner, asset_name, description, quantity, is_divisible, data, "
|
||||||
|
+ "is_unspendable, creation_group_id, reference, reduced_asset_name "
|
||||||
+ "FROM Assets ORDER BY asset_id");
|
+ "FROM Assets ORDER BY asset_id");
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
sql.append(" DESC");
|
sql.append(" DESC");
|
||||||
@ -118,9 +134,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
boolean isUnspendable = resultSet.getBoolean(8);
|
boolean isUnspendable = resultSet.getBoolean(8);
|
||||||
int creationGroupId = resultSet.getInt(9);
|
int creationGroupId = resultSet.getInt(9);
|
||||||
byte[] reference = resultSet.getBytes(10);
|
byte[] reference = resultSet.getBytes(10);
|
||||||
|
String reducedAssetName = resultSet.getString(11);
|
||||||
|
|
||||||
assets.add(new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data,
|
assets.add(new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data,
|
||||||
isUnspendable,creationGroupId, reference));
|
isUnspendable,creationGroupId, reference, reducedAssetName));
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
return assets;
|
return assets;
|
||||||
@ -161,7 +178,8 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
.bind("asset_name", assetData.getName()).bind("description", assetData.getDescription())
|
.bind("asset_name", assetData.getName()).bind("description", assetData.getDescription())
|
||||||
.bind("quantity", assetData.getQuantity()).bind("is_divisible", assetData.isDivisible())
|
.bind("quantity", assetData.getQuantity()).bind("is_divisible", assetData.isDivisible())
|
||||||
.bind("data", assetData.getData()).bind("is_unspendable", assetData.isUnspendable())
|
.bind("data", assetData.getData()).bind("is_unspendable", assetData.isUnspendable())
|
||||||
.bind("creation_group_id", assetData.getCreationGroupId()).bind("reference", assetData.getReference());
|
.bind("creation_group_id", assetData.getCreationGroupId()).bind("reference", assetData.getReference())
|
||||||
|
.bind("reduced_asset_name", assetData.getReducedAssetName());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saveHelper.execute(this.repository);
|
saveHelper.execute(this.repository);
|
||||||
|
@ -355,9 +355,11 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
+ "quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, "
|
+ "quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, "
|
||||||
+ "is_unspendable BOOLEAN NOT NULL DEFAULT FALSE, creation_group_id GroupID NOT NULL DEFAULT 0, "
|
+ "is_unspendable BOOLEAN NOT NULL DEFAULT FALSE, creation_group_id GroupID NOT NULL DEFAULT 0, "
|
||||||
+ "reference Signature NOT NULL, data AssetData NOT NULL DEFAULT '', "
|
+ "reference Signature NOT NULL, data AssetData NOT NULL DEFAULT '', "
|
||||||
+ "PRIMARY KEY (asset_id))");
|
+ "reduced_asset_name AssetName NOT NULL, PRIMARY KEY (asset_id))");
|
||||||
// For when a user wants to lookup an asset by name
|
// For when a user wants to lookup an asset by name
|
||||||
stmt.execute("CREATE INDEX AssetNameIndex on Assets (asset_name)");
|
stmt.execute("CREATE INDEX AssetNameIndex on Assets (asset_name)");
|
||||||
|
// For looking up assets by 'reduced' name
|
||||||
|
stmt.execute("CREATE INDEX AssetReducedNameIndex on Assets (reduced_asset_name)");
|
||||||
|
|
||||||
// We need a corresponding trigger to make sure new asset_id values are assigned sequentially start from 0
|
// We need a corresponding trigger to make sure new asset_id values are assigned sequentially start from 0
|
||||||
stmt.execute("CREATE TRIGGER Asset_ID_Trigger BEFORE INSERT ON Assets "
|
stmt.execute("CREATE TRIGGER Asset_ID_Trigger BEFORE INSERT ON Assets "
|
||||||
@ -386,7 +388,8 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
// Issue Asset Transactions
|
// Issue Asset Transactions
|
||||||
stmt.execute("CREATE TABLE IssueAssetTransactions (signature Signature, issuer QortalPublicKey NOT NULL, asset_name AssetName NOT NULL, "
|
stmt.execute("CREATE TABLE IssueAssetTransactions (signature Signature, issuer QortalPublicKey NOT NULL, asset_name AssetName NOT NULL, "
|
||||||
+ "description GenericDescription NOT NULL, quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, asset_id AssetID, "
|
+ "description GenericDescription NOT NULL, quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, asset_id AssetID, "
|
||||||
+ "is_unspendable BOOLEAN NOT NULL, data AssetData NOT NULL DEFAULT '', " + TRANSACTION_KEYS + ")");
|
+ "is_unspendable BOOLEAN NOT NULL, data AssetData NOT NULL DEFAULT '', reduced_asset_name AssetName NOT NULL, "
|
||||||
|
+ TRANSACTION_KEYS + ")");
|
||||||
|
|
||||||
// Transfer Asset Transactions
|
// Transfer Asset Transactions
|
||||||
stmt.execute("CREATE TABLE TransferAssetTransactions (signature Signature, sender QortalPublicKey NOT NULL, recipient QortalAddress NOT NULL, "
|
stmt.execute("CREATE TABLE TransferAssetTransactions (signature Signature, sender QortalPublicKey NOT NULL, recipient QortalAddress NOT NULL, "
|
||||||
|
@ -17,7 +17,8 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo
|
|||||||
}
|
}
|
||||||
|
|
||||||
TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException {
|
TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException {
|
||||||
String sql = "SELECT asset_name, description, quantity, is_divisible, data, is_unspendable, asset_id FROM IssueAssetTransactions WHERE signature = ?";
|
String sql = "SELECT asset_name, description, quantity, is_divisible, data, is_unspendable, asset_id, reduced_asset_name "
|
||||||
|
+ "FROM IssueAssetTransactions WHERE signature = ?";
|
||||||
|
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
@ -35,8 +36,10 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo
|
|||||||
if (assetId == 0 && resultSet.wasNull())
|
if (assetId == 0 && resultSet.wasNull())
|
||||||
assetId = null;
|
assetId = null;
|
||||||
|
|
||||||
|
String reducedAssetName = resultSet.getString(8);
|
||||||
|
|
||||||
return new IssueAssetTransactionData(baseTransactionData, assetId, assetName, description, quantity, isDivisible,
|
return new IssueAssetTransactionData(baseTransactionData, assetId, assetName, description, quantity, isDivisible,
|
||||||
data, isUnspendable);
|
data, isUnspendable, reducedAssetName);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to fetch issue asset transaction from repository", e);
|
throw new DataException("Unable to fetch issue asset transaction from repository", e);
|
||||||
}
|
}
|
||||||
@ -49,7 +52,7 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo
|
|||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("IssueAssetTransactions");
|
HSQLDBSaver saveHelper = new HSQLDBSaver("IssueAssetTransactions");
|
||||||
|
|
||||||
saveHelper.bind("signature", issueAssetTransactionData.getSignature()).bind("issuer", issueAssetTransactionData.getIssuerPublicKey())
|
saveHelper.bind("signature", issueAssetTransactionData.getSignature()).bind("issuer", issueAssetTransactionData.getIssuerPublicKey())
|
||||||
.bind("asset_name", issueAssetTransactionData.getAssetName())
|
.bind("asset_name", issueAssetTransactionData.getAssetName()).bind("reduced_asset_name", issueAssetTransactionData.getReducedAssetName())
|
||||||
.bind("description", issueAssetTransactionData.getDescription()).bind("quantity", issueAssetTransactionData.getQuantity())
|
.bind("description", issueAssetTransactionData.getDescription()).bind("quantity", issueAssetTransactionData.getQuantity())
|
||||||
.bind("is_divisible", issueAssetTransactionData.isDivisible()).bind("data", issueAssetTransactionData.getData())
|
.bind("is_divisible", issueAssetTransactionData.isDivisible()).bind("data", issueAssetTransactionData.getData())
|
||||||
.bind("is_unspendable", issueAssetTransactionData.isUnspendable()).bind("asset_id", issueAssetTransactionData.getAssetId());
|
.bind("is_unspendable", issueAssetTransactionData.isUnspendable()).bind("asset_id", issueAssetTransactionData.getAssetId());
|
||||||
|
@ -7,9 +7,11 @@ import org.qortal.account.Account;
|
|||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.data.transaction.IssueAssetTransactionData;
|
import org.qortal.data.transaction.IssueAssetTransactionData;
|
||||||
import org.qortal.data.transaction.TransactionData;
|
import org.qortal.data.transaction.TransactionData;
|
||||||
|
import org.qortal.naming.Name;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.utils.Amounts;
|
import org.qortal.utils.Amounts;
|
||||||
|
import org.qortal.utils.Unicode;
|
||||||
|
|
||||||
import com.google.common.base.Utf8;
|
import com.google.common.base.Utf8;
|
||||||
|
|
||||||
@ -40,15 +42,29 @@ public class IssueAssetTransaction extends Transaction {
|
|||||||
return this.getCreator();
|
return this.getCreator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getReducedAssetName() {
|
||||||
|
if (this.issueAssetTransactionData.getReducedAssetName() == null) {
|
||||||
|
String reducedAssetName = Name.reduceName(this.issueAssetTransactionData.getAssetName());
|
||||||
|
this.issueAssetTransactionData.setReducedAssetName(reducedAssetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.issueAssetTransactionData.getReducedAssetName();
|
||||||
|
}
|
||||||
|
|
||||||
// Processing
|
// Processing
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult isValid() throws DataException {
|
public ValidationResult isValid() throws DataException {
|
||||||
// Check name size bounds
|
// Check name size bounds
|
||||||
int assetNameLength = Utf8.encodedLength(this.issueAssetTransactionData.getAssetName());
|
String assetName = this.issueAssetTransactionData.getAssetName();
|
||||||
if (assetNameLength < 1 || assetNameLength > Asset.MAX_NAME_SIZE)
|
int assetNameLength = Utf8.encodedLength(assetName);
|
||||||
|
if (assetNameLength < Asset.MIN_NAME_SIZE || assetNameLength > Asset.MAX_NAME_SIZE)
|
||||||
return ValidationResult.INVALID_NAME_LENGTH;
|
return ValidationResult.INVALID_NAME_LENGTH;
|
||||||
|
|
||||||
|
// Check name is in normalized form (no leading/trailing whitespace, etc.)
|
||||||
|
if (!assetName.equals(Unicode.normalize(assetName)))
|
||||||
|
return ValidationResult.NAME_NOT_LOWER_CASE;
|
||||||
|
|
||||||
// Check description size bounds
|
// Check description size bounds
|
||||||
int assetDescriptionlength = Utf8.encodedLength(this.issueAssetTransactionData.getDescription());
|
int assetDescriptionlength = Utf8.encodedLength(this.issueAssetTransactionData.getDescription());
|
||||||
if (assetDescriptionlength < 1 || assetDescriptionlength > Asset.MAX_DESCRIPTION_SIZE)
|
if (assetDescriptionlength < 1 || assetDescriptionlength > Asset.MAX_DESCRIPTION_SIZE)
|
||||||
@ -74,13 +90,16 @@ public class IssueAssetTransaction extends Transaction {
|
|||||||
if (issuer.getConfirmedBalance(Asset.QORT) < this.issueAssetTransactionData.getFee())
|
if (issuer.getConfirmedBalance(Asset.QORT) < this.issueAssetTransactionData.getFee())
|
||||||
return ValidationResult.NO_BALANCE;
|
return ValidationResult.NO_BALANCE;
|
||||||
|
|
||||||
|
// Fill in missing reduced name. Caller is likely to save this as next step.
|
||||||
|
getReducedAssetName();
|
||||||
|
|
||||||
return ValidationResult.OK;
|
return ValidationResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult isProcessable() throws DataException {
|
public ValidationResult isProcessable() throws DataException {
|
||||||
// Check the asset name isn't already taken.
|
// Check the name isn't already taken
|
||||||
if (this.repository.getAssetRepository().assetExists(this.issueAssetTransactionData.getAssetName()))
|
if (this.repository.getAssetRepository().reducedAssetNameExists(getReducedAssetName()))
|
||||||
return ValidationResult.ASSET_ALREADY_EXISTS;
|
return ValidationResult.ASSET_ALREADY_EXISTS;
|
||||||
|
|
||||||
return ValidationResult.OK;
|
return ValidationResult.OK;
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
package org.qortal.test.assets;
|
package org.qortal.test.assets;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.qortal.data.transaction.IssueAssetTransactionData;
|
||||||
|
import org.qortal.data.transaction.TransactionData;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
|
import org.qortal.repository.Repository;
|
||||||
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.test.common.Common;
|
import org.qortal.test.common.Common;
|
||||||
|
import org.qortal.test.common.TestAccount;
|
||||||
|
import org.qortal.test.common.TransactionUtils;
|
||||||
|
import org.qortal.test.common.transaction.TestTransaction;
|
||||||
|
import org.qortal.transaction.Transaction;
|
||||||
|
import org.qortal.transaction.Transaction.ValidationResult;
|
||||||
import org.qortal.utils.Amounts;
|
import org.qortal.utils.Amounts;
|
||||||
|
|
||||||
public class MiscTests extends Common {
|
public class MiscTests extends Common {
|
||||||
@ -21,6 +30,32 @@ public class MiscTests extends Common {
|
|||||||
Common.orphanCheck();
|
Common.orphanCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateAssetWithExistingName() throws DataException {
|
||||||
|
try (Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||||
|
|
||||||
|
String assetName = "test-asset";
|
||||||
|
String description = "description";
|
||||||
|
long quantity = 12345678L;
|
||||||
|
boolean isDivisible = true;
|
||||||
|
String data = "{}";
|
||||||
|
boolean isUnspendable = false;
|
||||||
|
|
||||||
|
TransactionData transactionData = new IssueAssetTransactionData(TestTransaction.generateBase(alice), assetName, description, quantity, isDivisible, data, isUnspendable);
|
||||||
|
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||||
|
|
||||||
|
String duplicateAssetName = "TEST-Ásset";
|
||||||
|
transactionData = new IssueAssetTransactionData(TestTransaction.generateBase(alice), duplicateAssetName, description, quantity, isDivisible, data, isUnspendable);
|
||||||
|
|
||||||
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
transaction.sign(alice);
|
||||||
|
|
||||||
|
ValidationResult result = transaction.importAsUnconfirmed();
|
||||||
|
assertTrue("Transaction should be invalid", ValidationResult.OK != result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCalcCommitmentWithRoundUp() throws DataException {
|
public void testCalcCommitmentWithRoundUp() throws DataException {
|
||||||
long amount = 1234_87654321L;
|
long amount = 1234_87654321L;
|
||||||
|
Loading…
Reference in New Issue
Block a user