Merge branch 'asset-unicode' into launch

This commit is contained in:
catbref 2020-05-19 07:57:06 +01:00
commit ed178e744d
9 changed files with 152 additions and 25 deletions

View File

@ -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);
} }

View File

@ -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;
}
} }

View File

@ -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;
}
} }

View File

@ -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 {

View File

@ -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);

View File

@ -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, "

View File

@ -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());

View File

@ -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;

View File

@ -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;