forked from Qortal/qortal
Added "data" field to assets, added UPDATE_ASSET tx + fixes
This commit is contained in:
parent
8f72d9d423
commit
d53777f461
@ -43,6 +43,7 @@ import org.qora.data.transaction.CancelAssetOrderTransactionData;
|
|||||||
import org.qora.data.transaction.CreateAssetOrderTransactionData;
|
import org.qora.data.transaction.CreateAssetOrderTransactionData;
|
||||||
import org.qora.data.transaction.IssueAssetTransactionData;
|
import org.qora.data.transaction.IssueAssetTransactionData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
|
import org.qora.data.transaction.UpdateAssetTransactionData;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
@ -53,6 +54,7 @@ import org.qora.transform.TransformationException;
|
|||||||
import org.qora.transform.transaction.CancelAssetOrderTransactionTransformer;
|
import org.qora.transform.transaction.CancelAssetOrderTransactionTransformer;
|
||||||
import org.qora.transform.transaction.CreateAssetOrderTransactionTransformer;
|
import org.qora.transform.transaction.CreateAssetOrderTransactionTransformer;
|
||||||
import org.qora.transform.transaction.IssueAssetTransactionTransformer;
|
import org.qora.transform.transaction.IssueAssetTransactionTransformer;
|
||||||
|
import org.qora.transform.transaction.UpdateAssetTransactionTransformer;
|
||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
|
|
||||||
@Path("/assets")
|
@Path("/assets")
|
||||||
@ -763,4 +765,52 @@ public class AssetsResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/update")
|
||||||
|
@Operation(
|
||||||
|
summary = "Update asset",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = UpdateAssetTransactionData.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "raw, unsigned, UPDATE_ASSET transaction encoded in Base58",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.TEXT_PLAIN,
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({
|
||||||
|
ApiError.NON_PRODUCTION, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID
|
||||||
|
})
|
||||||
|
public String updateAsset(UpdateAssetTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isApiRestricted())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
|
ValidationResult result = transaction.isValidUnconfirmed();
|
||||||
|
if (result != ValidationResult.OK)
|
||||||
|
throw TransactionsResource.createTransactionInvalidException(request, result);
|
||||||
|
|
||||||
|
byte[] bytes = UpdateAssetTransactionTransformer.toBytes(transactionData);
|
||||||
|
return Base58.encode(bytes);
|
||||||
|
} catch (TransformationException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package org.qora.asset;
|
|||||||
|
|
||||||
import org.qora.data.asset.AssetData;
|
import org.qora.data.asset.AssetData;
|
||||||
import org.qora.data.transaction.IssueAssetTransactionData;
|
import org.qora.data.transaction.IssueAssetTransactionData;
|
||||||
|
import org.qora.data.transaction.TransactionData;
|
||||||
|
import org.qora.data.transaction.UpdateAssetTransactionData;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
|
|
||||||
@ -12,6 +14,15 @@ public class Asset {
|
|||||||
*/
|
*/
|
||||||
public static final long QORA = 0L;
|
public static final long QORA = 0L;
|
||||||
|
|
||||||
|
// Other useful constants
|
||||||
|
|
||||||
|
public static final int MAX_NAME_SIZE = 400;
|
||||||
|
public static final int MAX_DESCRIPTION_SIZE = 4000;
|
||||||
|
public static final int MAX_DATA_SIZE = 4000;
|
||||||
|
|
||||||
|
public static final long MAX_DIVISIBLE_QUANTITY = 10_000_000_000L;
|
||||||
|
public static final long MAX_INDIVISIBLE_QUANTITY = 1_000_000_000_000_000_000L;
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
private Repository repository;
|
private Repository repository;
|
||||||
private AssetData assetData;
|
private AssetData assetData;
|
||||||
@ -25,9 +36,12 @@ public class Asset {
|
|||||||
|
|
||||||
public Asset(Repository repository, IssueAssetTransactionData issueAssetTransactionData) {
|
public Asset(Repository repository, IssueAssetTransactionData issueAssetTransactionData) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
|
||||||
|
// NOTE: transaction's reference is used to look up newly assigned assetID on creation!
|
||||||
this.assetData = new AssetData(issueAssetTransactionData.getOwner(), issueAssetTransactionData.getAssetName(),
|
this.assetData = new AssetData(issueAssetTransactionData.getOwner(), issueAssetTransactionData.getAssetName(),
|
||||||
issueAssetTransactionData.getDescription(), issueAssetTransactionData.getQuantity(), issueAssetTransactionData.getIsDivisible(),
|
issueAssetTransactionData.getDescription(), issueAssetTransactionData.getQuantity(),
|
||||||
issueAssetTransactionData.getReference());
|
issueAssetTransactionData.getIsDivisible(), issueAssetTransactionData.getData(),
|
||||||
|
issueAssetTransactionData.getTxGroupId(), issueAssetTransactionData.getSignature());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Asset(Repository repository, long assetId) throws DataException {
|
public Asset(Repository repository, long assetId) throws DataException {
|
||||||
@ -51,4 +65,56 @@ public class Asset {
|
|||||||
this.repository.getAssetRepository().delete(this.assetData.getAssetId());
|
this.repository.getAssetRepository().delete(this.assetData.getAssetId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void update(UpdateAssetTransactionData updateAssetTransactionData) throws DataException {
|
||||||
|
// Update reference in transaction data
|
||||||
|
updateAssetTransactionData.setOrphanReference(this.assetData.getReference());
|
||||||
|
|
||||||
|
// New reference is this transaction's signature
|
||||||
|
this.assetData.setReference(updateAssetTransactionData.getSignature());
|
||||||
|
|
||||||
|
// Update asset's owner, description and data
|
||||||
|
this.assetData.setOwner(updateAssetTransactionData.getNewOwner());
|
||||||
|
this.assetData.setDescription(updateAssetTransactionData.getNewDescription());
|
||||||
|
this.assetData.setData(updateAssetTransactionData.getNewData());
|
||||||
|
|
||||||
|
// Save updated asset
|
||||||
|
this.repository.getAssetRepository().save(this.assetData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void revert(UpdateAssetTransactionData updateAssetTransactionData) throws DataException {
|
||||||
|
// Previous asset reference is taken from this transaction's cached copy
|
||||||
|
this.assetData.setReference(updateAssetTransactionData.getOrphanReference());
|
||||||
|
|
||||||
|
// Previous owner, description and/or data taken from referenced transaction
|
||||||
|
TransactionData previousTransactionData = this.repository.getTransactionRepository()
|
||||||
|
.fromSignature(this.assetData.getReference());
|
||||||
|
|
||||||
|
if (previousTransactionData == null)
|
||||||
|
throw new IllegalStateException("Missing referenced transaction when orphaning UPDATE_ASSET");
|
||||||
|
|
||||||
|
switch (previousTransactionData.getType()) {
|
||||||
|
case ISSUE_ASSET:
|
||||||
|
IssueAssetTransactionData previousIssueAssetTransactionData = (IssueAssetTransactionData) previousTransactionData;
|
||||||
|
|
||||||
|
this.assetData.setOwner(previousIssueAssetTransactionData.getOwner());
|
||||||
|
this.assetData.setDescription(previousIssueAssetTransactionData.getDescription());
|
||||||
|
this.assetData.setData(previousIssueAssetTransactionData.getData());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case UPDATE_ASSET:
|
||||||
|
UpdateAssetTransactionData previousUpdateAssetTransactionData = (UpdateAssetTransactionData) previousTransactionData;
|
||||||
|
|
||||||
|
this.assetData.setOwner(previousUpdateAssetTransactionData.getNewOwner());
|
||||||
|
this.assetData.setDescription(previousUpdateAssetTransactionData.getNewDescription());
|
||||||
|
this.assetData.setData(previousUpdateAssetTransactionData.getNewData());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Invalid referenced transaction when orphaning UPDATE_ASSET");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save reverted asset
|
||||||
|
this.repository.getAssetRepository().save(this.assetData);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,14 +31,14 @@ public class Trade {
|
|||||||
|
|
||||||
// Update corresponding Orders on both sides of trade
|
// Update corresponding Orders on both sides of trade
|
||||||
OrderData initiatingOrder = assetRepository.fromOrderId(this.tradeData.getInitiator());
|
OrderData initiatingOrder = assetRepository.fromOrderId(this.tradeData.getInitiator());
|
||||||
initiatingOrder.setFulfilled(initiatingOrder.getFulfilled().add(tradeData.getPrice()));
|
initiatingOrder.setFulfilled(initiatingOrder.getFulfilled().add(tradeData.getInitiatorAmount()));
|
||||||
initiatingOrder.setIsFulfilled(Order.isFulfilled(initiatingOrder));
|
initiatingOrder.setIsFulfilled(Order.isFulfilled(initiatingOrder));
|
||||||
// Set isClosed to true if isFulfilled now true
|
// Set isClosed to true if isFulfilled now true
|
||||||
initiatingOrder.setIsClosed(initiatingOrder.getIsFulfilled());
|
initiatingOrder.setIsClosed(initiatingOrder.getIsFulfilled());
|
||||||
assetRepository.save(initiatingOrder);
|
assetRepository.save(initiatingOrder);
|
||||||
|
|
||||||
OrderData targetOrder = assetRepository.fromOrderId(this.tradeData.getTarget());
|
OrderData targetOrder = assetRepository.fromOrderId(this.tradeData.getTarget());
|
||||||
targetOrder.setFulfilled(targetOrder.getFulfilled().add(tradeData.getAmount()));
|
targetOrder.setFulfilled(targetOrder.getFulfilled().add(tradeData.getTargetAmount()));
|
||||||
targetOrder.setIsFulfilled(Order.isFulfilled(targetOrder));
|
targetOrder.setIsFulfilled(Order.isFulfilled(targetOrder));
|
||||||
// Set isClosed to true if isFulfilled now true
|
// Set isClosed to true if isFulfilled now true
|
||||||
targetOrder.setIsClosed(targetOrder.getIsFulfilled());
|
targetOrder.setIsClosed(targetOrder.getIsFulfilled());
|
||||||
@ -47,11 +47,11 @@ public class Trade {
|
|||||||
// Actually transfer asset balances
|
// Actually transfer asset balances
|
||||||
Account initiatingCreator = new PublicKeyAccount(this.repository, initiatingOrder.getCreatorPublicKey());
|
Account initiatingCreator = new PublicKeyAccount(this.repository, initiatingOrder.getCreatorPublicKey());
|
||||||
initiatingCreator.setConfirmedBalance(initiatingOrder.getWantAssetId(),
|
initiatingCreator.setConfirmedBalance(initiatingOrder.getWantAssetId(),
|
||||||
initiatingCreator.getConfirmedBalance(initiatingOrder.getWantAssetId()).add(tradeData.getAmount()));
|
initiatingCreator.getConfirmedBalance(initiatingOrder.getWantAssetId()).add(tradeData.getTargetAmount()));
|
||||||
|
|
||||||
Account targetCreator = new PublicKeyAccount(this.repository, targetOrder.getCreatorPublicKey());
|
Account targetCreator = new PublicKeyAccount(this.repository, targetOrder.getCreatorPublicKey());
|
||||||
targetCreator.setConfirmedBalance(targetOrder.getWantAssetId(),
|
targetCreator.setConfirmedBalance(targetOrder.getWantAssetId(),
|
||||||
targetCreator.getConfirmedBalance(targetOrder.getWantAssetId()).add(tradeData.getPrice()));
|
targetCreator.getConfirmedBalance(targetOrder.getWantAssetId()).add(tradeData.getInitiatorAmount()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void orphan() throws DataException {
|
public void orphan() throws DataException {
|
||||||
@ -59,14 +59,14 @@ public class Trade {
|
|||||||
|
|
||||||
// Revert corresponding Orders on both sides of trade
|
// Revert corresponding Orders on both sides of trade
|
||||||
OrderData initiatingOrder = assetRepository.fromOrderId(this.tradeData.getInitiator());
|
OrderData initiatingOrder = assetRepository.fromOrderId(this.tradeData.getInitiator());
|
||||||
initiatingOrder.setFulfilled(initiatingOrder.getFulfilled().subtract(tradeData.getPrice()));
|
initiatingOrder.setFulfilled(initiatingOrder.getFulfilled().subtract(tradeData.getInitiatorAmount()));
|
||||||
initiatingOrder.setIsFulfilled(Order.isFulfilled(initiatingOrder));
|
initiatingOrder.setIsFulfilled(Order.isFulfilled(initiatingOrder));
|
||||||
// Set isClosed to false if isFulfilled now false
|
// Set isClosed to false if isFulfilled now false
|
||||||
initiatingOrder.setIsClosed(initiatingOrder.getIsFulfilled());
|
initiatingOrder.setIsClosed(initiatingOrder.getIsFulfilled());
|
||||||
assetRepository.save(initiatingOrder);
|
assetRepository.save(initiatingOrder);
|
||||||
|
|
||||||
OrderData targetOrder = assetRepository.fromOrderId(this.tradeData.getTarget());
|
OrderData targetOrder = assetRepository.fromOrderId(this.tradeData.getTarget());
|
||||||
targetOrder.setFulfilled(targetOrder.getFulfilled().subtract(tradeData.getAmount()));
|
targetOrder.setFulfilled(targetOrder.getFulfilled().subtract(tradeData.getTargetAmount()));
|
||||||
targetOrder.setIsFulfilled(Order.isFulfilled(targetOrder));
|
targetOrder.setIsFulfilled(Order.isFulfilled(targetOrder));
|
||||||
// Set isClosed to false if isFulfilled now false
|
// Set isClosed to false if isFulfilled now false
|
||||||
targetOrder.setIsClosed(targetOrder.getIsFulfilled());
|
targetOrder.setIsClosed(targetOrder.getIsFulfilled());
|
||||||
@ -75,11 +75,11 @@ public class Trade {
|
|||||||
// Reverse asset transfers
|
// Reverse asset transfers
|
||||||
Account initiatingCreator = new PublicKeyAccount(this.repository, initiatingOrder.getCreatorPublicKey());
|
Account initiatingCreator = new PublicKeyAccount(this.repository, initiatingOrder.getCreatorPublicKey());
|
||||||
initiatingCreator.setConfirmedBalance(initiatingOrder.getWantAssetId(),
|
initiatingCreator.setConfirmedBalance(initiatingOrder.getWantAssetId(),
|
||||||
initiatingCreator.getConfirmedBalance(initiatingOrder.getWantAssetId()).subtract(tradeData.getAmount()));
|
initiatingCreator.getConfirmedBalance(initiatingOrder.getWantAssetId()).subtract(tradeData.getTargetAmount()));
|
||||||
|
|
||||||
Account targetCreator = new PublicKeyAccount(this.repository, targetOrder.getCreatorPublicKey());
|
Account targetCreator = new PublicKeyAccount(this.repository, targetOrder.getCreatorPublicKey());
|
||||||
targetCreator.setConfirmedBalance(targetOrder.getWantAssetId(),
|
targetCreator.setConfirmedBalance(targetOrder.getWantAssetId(),
|
||||||
targetCreator.getConfirmedBalance(targetOrder.getWantAssetId()).subtract(tradeData.getPrice()));
|
targetCreator.getConfirmedBalance(targetOrder.getWantAssetId()).subtract(tradeData.getInitiatorAmount()));
|
||||||
|
|
||||||
// Remove trade from repository
|
// Remove trade from repository
|
||||||
assetRepository.delete(tradeData);
|
assetRepository.delete(tradeData);
|
||||||
|
@ -22,6 +22,7 @@ import org.qora.data.asset.AssetData;
|
|||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
import org.qora.data.transaction.IssueAssetTransactionData;
|
import org.qora.data.transaction.IssueAssetTransactionData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
|
import org.qora.group.Group;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.transaction.Transaction;
|
import org.qora.transaction.Transaction;
|
||||||
@ -117,7 +118,7 @@ public class GenesisBlock extends Block {
|
|||||||
IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) transactionData;
|
IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) transactionData;
|
||||||
|
|
||||||
return new AssetData(issueAssetTransactionData.getOwner(), issueAssetTransactionData.getAssetName(), issueAssetTransactionData.getDescription(),
|
return new AssetData(issueAssetTransactionData.getOwner(), issueAssetTransactionData.getAssetName(), issueAssetTransactionData.getDescription(),
|
||||||
issueAssetTransactionData.getQuantity(), issueAssetTransactionData.getIsDivisible(), issueAssetTransactionData.getReference());
|
issueAssetTransactionData.getQuantity(), issueAssetTransactionData.getIsDivisible(), null, Group.NO_GROUP, issueAssetTransactionData.getReference());
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@ public class AssetData {
|
|||||||
private String description;
|
private String description;
|
||||||
private long quantity;
|
private long quantity;
|
||||||
private boolean isDivisible;
|
private boolean isDivisible;
|
||||||
|
private String data;
|
||||||
|
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)
|
||||||
@ -29,19 +31,21 @@ 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, byte[] reference) {
|
public AssetData(Long assetId, String owner, String name, String description, long quantity, boolean isDivisible, String data, int creationGroupId, byte[] reference) {
|
||||||
this.assetId = assetId;
|
this.assetId = assetId;
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.quantity = quantity;
|
this.quantity = quantity;
|
||||||
this.isDivisible = isDivisible;
|
this.isDivisible = isDivisible;
|
||||||
|
this.data = data;
|
||||||
|
this.creationGroupId = creationGroupId;
|
||||||
this.reference = reference;
|
this.reference = reference;
|
||||||
}
|
}
|
||||||
|
|
||||||
// New asset with unassigned assetId
|
// New asset with unassigned assetId
|
||||||
public AssetData(String owner, String name, String description, long quantity, boolean isDivisible, byte[] reference) {
|
public AssetData(String owner, String name, String description, long quantity, boolean isDivisible, String data, int creationGroupId, byte[] reference) {
|
||||||
this(null, owner, name, description, quantity, isDivisible, reference);
|
this(null, owner, name, description, quantity, isDivisible, data, creationGroupId, reference);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters/Setters
|
// Getters/Setters
|
||||||
@ -58,6 +62,10 @@ public class AssetData {
|
|||||||
return this.owner;
|
return this.owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOwner(String owner) {
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
@ -66,6 +74,10 @@ public class AssetData {
|
|||||||
return this.description;
|
return this.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
public long getQuantity() {
|
public long getQuantity() {
|
||||||
return this.quantity;
|
return this.quantity;
|
||||||
}
|
}
|
||||||
@ -74,8 +86,24 @@ public class AssetData {
|
|||||||
return this.isDivisible;
|
return this.isDivisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getData() {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(String data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCreationGroupId() {
|
||||||
|
return this.creationGroupId;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getReference() {
|
public byte[] getReference() {
|
||||||
return this.reference;
|
return this.reference;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setReference(byte[] reference) {
|
||||||
|
this.reference = reference;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,9 @@ public class RecentTradeData {
|
|||||||
|
|
||||||
private long otherAssetId;
|
private long otherAssetId;
|
||||||
|
|
||||||
private BigDecimal amount;
|
private BigDecimal otherAmount;
|
||||||
|
|
||||||
private BigDecimal price;
|
private BigDecimal amount;
|
||||||
|
|
||||||
@Schema(
|
@Schema(
|
||||||
description = "when trade happened"
|
description = "when trade happened"
|
||||||
@ -31,11 +31,11 @@ public class RecentTradeData {
|
|||||||
protected RecentTradeData() {
|
protected RecentTradeData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecentTradeData(long assetId, long otherAssetId, BigDecimal amount, BigDecimal price, long timestamp) {
|
public RecentTradeData(long assetId, long otherAssetId, BigDecimal otherAmount, BigDecimal amount, long timestamp) {
|
||||||
this.assetId = assetId;
|
this.assetId = assetId;
|
||||||
this.otherAssetId = otherAssetId;
|
this.otherAssetId = otherAssetId;
|
||||||
|
this.otherAmount = otherAmount;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.price = price;
|
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,12 +49,12 @@ public class RecentTradeData {
|
|||||||
return this.otherAssetId;
|
return this.otherAssetId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getAmount() {
|
public BigDecimal getOtherAmount() {
|
||||||
return this.amount;
|
return this.otherAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getPrice() {
|
public BigDecimal getAmount() {
|
||||||
return this.price;
|
return this.amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTimestamp() {
|
public long getTimestamp() {
|
||||||
|
@ -23,11 +23,11 @@ public class TradeData {
|
|||||||
|
|
||||||
@Schema(name = "targetAmount", description = "amount traded from target order")
|
@Schema(name = "targetAmount", description = "amount traded from target order")
|
||||||
@XmlElement(name = "targetAmount")
|
@XmlElement(name = "targetAmount")
|
||||||
private BigDecimal amount;
|
private BigDecimal targetAmount;
|
||||||
|
|
||||||
@Schema(name = "initiatorAmount", description = "amount traded from initiating order")
|
@Schema(name = "initiatorAmount", description = "amount traded from initiating order")
|
||||||
@XmlElement(name = "initiatorAmount")
|
@XmlElement(name = "initiatorAmount")
|
||||||
private BigDecimal price;
|
private BigDecimal initiatorAmount;
|
||||||
|
|
||||||
@Schema(description = "when trade happened")
|
@Schema(description = "when trade happened")
|
||||||
private long timestamp;
|
private long timestamp;
|
||||||
@ -38,11 +38,11 @@ public class TradeData {
|
|||||||
protected TradeData() {
|
protected TradeData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradeData(byte[] initiator, byte[] target, BigDecimal amount, BigDecimal price, long timestamp) {
|
public TradeData(byte[] initiator, byte[] target, BigDecimal targetAmount, BigDecimal initiatorAmount, long timestamp) {
|
||||||
this.initiator = initiator;
|
this.initiator = initiator;
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.amount = amount;
|
this.targetAmount = targetAmount;
|
||||||
this.price = price;
|
this.initiatorAmount = initiatorAmount;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,12 +56,12 @@ public class TradeData {
|
|||||||
return this.target;
|
return this.target;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getAmount() {
|
public BigDecimal getTargetAmount() {
|
||||||
return this.amount;
|
return this.targetAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getPrice() {
|
public BigDecimal getInitiatorAmount() {
|
||||||
return this.price;
|
return this.initiatorAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTimestamp() {
|
public long getTimestamp() {
|
||||||
|
@ -37,6 +37,8 @@ public class IssueAssetTransactionData extends TransactionData {
|
|||||||
private long quantity;
|
private long quantity;
|
||||||
@Schema(description = "whether asset quantities can be fractional", example = "true")
|
@Schema(description = "whether asset quantities can be fractional", example = "true")
|
||||||
private boolean isDivisible;
|
private boolean isDivisible;
|
||||||
|
@Schema(description = "non-human-readable asset-related data, typically JSON", example = "{\"logo\": \"data:image/jpeg;base64,/9j/4AAQSkZJRgA==\"}")
|
||||||
|
private String data;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
@ -58,7 +60,7 @@ public class IssueAssetTransactionData extends TransactionData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public IssueAssetTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] issuerPublicKey, Long assetId, String owner,
|
public IssueAssetTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] issuerPublicKey, Long assetId, String owner,
|
||||||
String assetName, String description, long quantity, boolean isDivisible, BigDecimal fee, byte[] signature) {
|
String assetName, String description, long quantity, boolean isDivisible, String data, BigDecimal fee, byte[] signature) {
|
||||||
super(TransactionType.ISSUE_ASSET, timestamp, txGroupId, reference, issuerPublicKey, fee, signature);
|
super(TransactionType.ISSUE_ASSET, timestamp, txGroupId, reference, issuerPublicKey, fee, signature);
|
||||||
|
|
||||||
this.assetId = assetId;
|
this.assetId = assetId;
|
||||||
@ -68,16 +70,17 @@ public class IssueAssetTransactionData extends TransactionData {
|
|||||||
this.description = description;
|
this.description = description;
|
||||||
this.quantity = quantity;
|
this.quantity = quantity;
|
||||||
this.isDivisible = isDivisible;
|
this.isDivisible = isDivisible;
|
||||||
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IssueAssetTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] issuerPublicKey, String owner, String assetName,
|
public IssueAssetTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] issuerPublicKey, String owner, String assetName,
|
||||||
String description, long quantity, boolean isDivisible, BigDecimal fee, byte[] signature) {
|
String description, long quantity, boolean isDivisible, String data, BigDecimal fee, byte[] signature) {
|
||||||
this(timestamp, txGroupId, reference, issuerPublicKey, null, owner, assetName, description, quantity, isDivisible, fee, signature);
|
this(timestamp, txGroupId, reference, issuerPublicKey, null, owner, assetName, description, quantity, isDivisible, data, fee, signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IssueAssetTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] issuerPublicKey, String owner, String assetName,
|
public IssueAssetTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] issuerPublicKey, String owner, String assetName,
|
||||||
String description, long quantity, boolean isDivisible, BigDecimal fee) {
|
String description, long quantity, boolean isDivisible, String data, BigDecimal fee) {
|
||||||
this(timestamp, txGroupId, reference, issuerPublicKey, null, owner, assetName, description, quantity, isDivisible, fee, null);
|
this(timestamp, txGroupId, reference, issuerPublicKey, null, owner, assetName, description, quantity, isDivisible, data, fee, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters/Setters
|
// Getters/Setters
|
||||||
@ -118,4 +121,8 @@ public class IssueAssetTransactionData extends TransactionData {
|
|||||||
return this.isDivisible;
|
return this.isDivisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getData() {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,8 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
|
|||||||
GroupBanTransactionData.class, CancelGroupBanTransactionData.class,
|
GroupBanTransactionData.class, CancelGroupBanTransactionData.class,
|
||||||
GroupKickTransactionData.class, GroupInviteTransactionData.class,
|
GroupKickTransactionData.class, GroupInviteTransactionData.class,
|
||||||
JoinGroupTransactionData.class, LeaveGroupTransactionData.class,
|
JoinGroupTransactionData.class, LeaveGroupTransactionData.class,
|
||||||
GroupApprovalTransactionData.class, SetGroupTransactionData.class
|
GroupApprovalTransactionData.class, SetGroupTransactionData.class,
|
||||||
|
UpdateAssetTransactionData.class
|
||||||
})
|
})
|
||||||
//All properties to be converted to JSON via JAXB
|
//All properties to be converted to JSON via JAXB
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
package org.qora.data.transaction;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import javax.xml.bind.Unmarshaller;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlTransient;
|
||||||
|
|
||||||
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
// All properties to be converted to JSON via JAXB
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
@Schema(allOf = { TransactionData.class })
|
||||||
|
public class UpdateAssetTransactionData extends TransactionData {
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
private long assetId;
|
||||||
|
@Schema(description = "asset owner's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||||
|
private byte[] ownerPublicKey;
|
||||||
|
@Schema(description = "asset new owner's address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v")
|
||||||
|
private String newOwner;
|
||||||
|
@Schema(description = "asset new description", example = "Gold asset - 1 unit represents one 1kg of gold")
|
||||||
|
private String newDescription;
|
||||||
|
@Schema(description = "new asset-related data, typically JSON", example = "{\"logo\": \"data:image/jpeg;base64,/9j/4AAQSkZJRgA==\"}")
|
||||||
|
private String newData;
|
||||||
|
// No need to expose this via API
|
||||||
|
@XmlTransient
|
||||||
|
@Schema(hidden = true)
|
||||||
|
private byte[] orphanReference;
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
// For JAXB
|
||||||
|
protected UpdateAssetTransactionData() {
|
||||||
|
super(TransactionType.UPDATE_ASSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void afterUnmarshal(Unmarshaller u, Object parent) {
|
||||||
|
this.creatorPublicKey = this.ownerPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateAssetTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] ownerPublicKey, long assetId, String newOwner,
|
||||||
|
String newDescription, String newData, BigDecimal fee, byte[] orphanReference, byte[] signature) {
|
||||||
|
super(TransactionType.UPDATE_ASSET, timestamp, txGroupId, reference, ownerPublicKey, fee, signature);
|
||||||
|
|
||||||
|
this.assetId = assetId;
|
||||||
|
this.ownerPublicKey = ownerPublicKey;
|
||||||
|
this.newOwner = newOwner;
|
||||||
|
this.newDescription = newDescription;
|
||||||
|
this.newData = newData;
|
||||||
|
this.orphanReference = orphanReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateAssetTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] ownerPublicKey, long assetId, String newOwner,
|
||||||
|
String newDescription, String newData, BigDecimal fee, byte[] orphanReference) {
|
||||||
|
this(timestamp, txGroupId, reference, ownerPublicKey, assetId, newOwner, newDescription, newData, fee, orphanReference, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters/Setters
|
||||||
|
|
||||||
|
public long getAssetId() {
|
||||||
|
return this.assetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getOwnerPublicKey() {
|
||||||
|
return this.ownerPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewOwner() {
|
||||||
|
return this.newOwner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewDescription() {
|
||||||
|
return this.newDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewData() {
|
||||||
|
return this.newData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getOrphanReference() {
|
||||||
|
return this.orphanReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrphanReference(byte[] orphanReference) {
|
||||||
|
this.orphanReference = orphanReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -28,8 +28,9 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AssetData fromAssetId(long assetId) throws DataException {
|
public AssetData fromAssetId(long assetId) throws DataException {
|
||||||
try (ResultSet resultSet = this.repository
|
try (ResultSet resultSet = this.repository.checkedExecute(
|
||||||
.checkedExecute("SELECT owner, asset_name, description, quantity, is_divisible, reference FROM Assets WHERE asset_id = ?", assetId)) {
|
"SELECT owner, asset_name, description, quantity, is_divisible, data, creation_group_id, reference FROM Assets WHERE asset_id = ?",
|
||||||
|
assetId)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -38,9 +39,12 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
String description = resultSet.getString(3);
|
String description = resultSet.getString(3);
|
||||||
long quantity = resultSet.getLong(4);
|
long quantity = resultSet.getLong(4);
|
||||||
boolean isDivisible = resultSet.getBoolean(5);
|
boolean isDivisible = resultSet.getBoolean(5);
|
||||||
byte[] reference = resultSet.getBytes(6);
|
String data = resultSet.getString(6);
|
||||||
|
int creationGroupId = resultSet.getInt(7);
|
||||||
|
byte[] reference = resultSet.getBytes(8);
|
||||||
|
|
||||||
return new AssetData(assetId, owner, assetName, description, quantity, isDivisible, reference);
|
return new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data, creationGroupId,
|
||||||
|
reference);
|
||||||
} 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);
|
||||||
}
|
}
|
||||||
@ -48,8 +52,9 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AssetData fromAssetName(String assetName) throws DataException {
|
public AssetData fromAssetName(String assetName) throws DataException {
|
||||||
try (ResultSet resultSet = this.repository
|
try (ResultSet resultSet = this.repository.checkedExecute(
|
||||||
.checkedExecute("SELECT owner, asset_id, description, quantity, is_divisible, reference FROM Assets WHERE asset_name = ?", assetName)) {
|
"SELECT owner, asset_id, description, quantity, is_divisible, data, creation_group_id, reference FROM Assets WHERE asset_name = ?",
|
||||||
|
assetName)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -58,9 +63,12 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
String description = resultSet.getString(3);
|
String description = resultSet.getString(3);
|
||||||
long quantity = resultSet.getLong(4);
|
long quantity = resultSet.getLong(4);
|
||||||
boolean isDivisible = resultSet.getBoolean(5);
|
boolean isDivisible = resultSet.getBoolean(5);
|
||||||
byte[] reference = resultSet.getBytes(6);
|
String data = resultSet.getString(6);
|
||||||
|
int creationGroupId = resultSet.getInt(7);
|
||||||
|
byte[] reference = resultSet.getBytes(8);
|
||||||
|
|
||||||
return new AssetData(assetId, owner, assetName, description, quantity, isDivisible, reference);
|
return new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data, creationGroupId,
|
||||||
|
reference);
|
||||||
} 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);
|
||||||
}
|
}
|
||||||
@ -86,7 +94,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
|
|
||||||
@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 {
|
||||||
String sql = "SELECT owner, asset_id, description, quantity, is_divisible, reference, asset_name FROM Assets ORDER BY asset_id";
|
String sql = "SELECT asset_id, owner, asset_name, description, quantity, is_divisible, data, creation_group_id, reference FROM Assets ORDER BY asset_id";
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
sql += " DESC";
|
sql += " DESC";
|
||||||
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
@ -98,15 +106,18 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
return assets;
|
return assets;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
String owner = resultSet.getString(1);
|
long assetId = resultSet.getLong(1);
|
||||||
long assetId = resultSet.getLong(2);
|
String owner = resultSet.getString(2);
|
||||||
String description = resultSet.getString(3);
|
String assetName = resultSet.getString(3);
|
||||||
long quantity = resultSet.getLong(4);
|
String description = resultSet.getString(4);
|
||||||
boolean isDivisible = resultSet.getBoolean(5);
|
long quantity = resultSet.getLong(5);
|
||||||
byte[] reference = resultSet.getBytes(6);
|
boolean isDivisible = resultSet.getBoolean(6);
|
||||||
String assetName = resultSet.getString(7);
|
String data = resultSet.getString(7);
|
||||||
|
int creationGroupId = resultSet.getInt(8);
|
||||||
|
byte[] reference = resultSet.getBytes(9);
|
||||||
|
|
||||||
assets.add(new AssetData(assetId, owner, assetName, description, quantity, isDivisible, reference));
|
assets.add(new AssetData(assetId, owner, assetName, description, quantity, isDivisible, data,
|
||||||
|
creationGroupId, reference));
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
return assets;
|
return assets;
|
||||||
@ -119,8 +130,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
public void save(AssetData assetData) throws DataException {
|
public void save(AssetData assetData) throws DataException {
|
||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Assets");
|
HSQLDBSaver saveHelper = new HSQLDBSaver("Assets");
|
||||||
|
|
||||||
saveHelper.bind("asset_id", assetData.getAssetId()).bind("owner", assetData.getOwner()).bind("asset_name", assetData.getName())
|
saveHelper.bind("asset_id", assetData.getAssetId()).bind("owner", assetData.getOwner())
|
||||||
.bind("description", assetData.getDescription()).bind("quantity", assetData.getQuantity()).bind("is_divisible", assetData.getIsDivisible())
|
.bind("asset_name", assetData.getName()).bind("description", assetData.getDescription())
|
||||||
|
.bind("quantity", assetData.getQuantity()).bind("is_divisible", assetData.getIsDivisible())
|
||||||
|
.bind("data", assetData.getData()).bind("creation_group_id", assetData.getCreationGroupId())
|
||||||
.bind("reference", assetData.getReference());
|
.bind("reference", assetData.getReference());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -128,7 +141,8 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
|
|
||||||
if (assetData.getAssetId() == null) {
|
if (assetData.getAssetId() == null) {
|
||||||
// Fetch new assetId
|
// Fetch new assetId
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute("SELECT asset_id FROM Assets WHERE reference = ?", assetData.getReference())) {
|
try (ResultSet resultSet = this.repository
|
||||||
|
.checkedExecute("SELECT asset_id FROM Assets WHERE reference = ?", assetData.getReference())) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
throw new DataException("Unable to fetch new asset ID from repository");
|
throw new DataException("Unable to fetch new asset ID from repository");
|
||||||
|
|
||||||
@ -169,14 +183,16 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
boolean isClosed = resultSet.getBoolean(8);
|
boolean isClosed = resultSet.getBoolean(8);
|
||||||
boolean isFulfilled = resultSet.getBoolean(9);
|
boolean isFulfilled = resultSet.getBoolean(9);
|
||||||
|
|
||||||
return new OrderData(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, fulfilled, price, timestamp, isClosed, isFulfilled);
|
return new OrderData(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, fulfilled, price,
|
||||||
|
timestamp, isClosed, isFulfilled);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to fetch asset order from repository", e);
|
throw new DataException("Unable to fetch asset order from repository", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<OrderData> getOpenOrders(long haveAssetId, long wantAssetId, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
public List<OrderData> getOpenOrders(long haveAssetId, long wantAssetId, Integer limit, Integer offset,
|
||||||
|
Boolean reverse) throws DataException {
|
||||||
String sql = "SELECT creator, asset_order_id, amount, fulfilled, price, ordered FROM AssetOrders "
|
String sql = "SELECT creator, asset_order_id, amount, fulfilled, price, ordered FROM AssetOrders "
|
||||||
+ "WHERE have_asset_id = ? AND want_asset_id = ? AND is_closed = FALSE AND is_fulfilled = FALSE ORDER BY price";
|
+ "WHERE have_asset_id = ? AND want_asset_id = ? AND is_closed = FALSE AND is_fulfilled = FALSE ORDER BY price";
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
@ -202,8 +218,8 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
boolean isClosed = false;
|
boolean isClosed = false;
|
||||||
boolean isFulfilled = false;
|
boolean isFulfilled = false;
|
||||||
|
|
||||||
OrderData order = new OrderData(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, fulfilled, price, timestamp, isClosed,
|
OrderData order = new OrderData(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, fulfilled,
|
||||||
isFulfilled);
|
price, timestamp, isClosed, isFulfilled);
|
||||||
orders.add(order);
|
orders.add(order);
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
@ -214,7 +230,8 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<OrderData> getAggregatedOpenOrders(long haveAssetId, long wantAssetId, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
public List<OrderData> getAggregatedOpenOrders(long haveAssetId, long wantAssetId, Integer limit, Integer offset,
|
||||||
|
Boolean reverse) throws DataException {
|
||||||
String sql = "SELECT price, SUM(amount - fulfilled), MAX(ordered) FROM AssetOrders "
|
String sql = "SELECT price, SUM(amount - fulfilled), MAX(ordered) FROM AssetOrders "
|
||||||
+ "WHERE have_asset_id = ? AND want_asset_id = ? AND is_closed = FALSE AND is_fulfilled = FALSE GROUP BY price ORDER BY price";
|
+ "WHERE have_asset_id = ? AND want_asset_id = ? AND is_closed = FALSE AND is_fulfilled = FALSE GROUP BY price ORDER BY price";
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
@ -232,7 +249,8 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
BigDecimal totalUnfulfilled = resultSet.getBigDecimal(2);
|
BigDecimal totalUnfulfilled = resultSet.getBigDecimal(2);
|
||||||
long timestamp = resultSet.getTimestamp(3).getTime();
|
long timestamp = resultSet.getTimestamp(3).getTime();
|
||||||
|
|
||||||
OrderData order = new OrderData(null, null, haveAssetId, wantAssetId, totalUnfulfilled, BigDecimal.ZERO, price, timestamp, false, false);
|
OrderData order = new OrderData(null, null, haveAssetId, wantAssetId, totalUnfulfilled, BigDecimal.ZERO,
|
||||||
|
price, timestamp, false, false);
|
||||||
orders.add(order);
|
orders.add(order);
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
@ -243,8 +261,8 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<OrderData> getAccountsOrders(byte[] publicKey, Boolean optIsClosed, Boolean optIsFulfilled, Integer limit, Integer offset, Boolean reverse)
|
public List<OrderData> getAccountsOrders(byte[] publicKey, Boolean optIsClosed, Boolean optIsFulfilled,
|
||||||
throws DataException {
|
Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
String sql = "SELECT asset_order_id, have_asset_id, want_asset_id, amount, fulfilled, price, ordered, is_closed, is_fulfilled FROM AssetOrders WHERE creator = ?";
|
String sql = "SELECT asset_order_id, have_asset_id, want_asset_id, amount, fulfilled, price, ordered, is_closed, is_fulfilled FROM AssetOrders WHERE creator = ?";
|
||||||
if (optIsClosed != null)
|
if (optIsClosed != null)
|
||||||
sql += " AND is_closed = " + (optIsClosed ? "TRUE" : "FALSE");
|
sql += " AND is_closed = " + (optIsClosed ? "TRUE" : "FALSE");
|
||||||
@ -272,7 +290,8 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
boolean isClosed = resultSet.getBoolean(8);
|
boolean isClosed = resultSet.getBoolean(8);
|
||||||
boolean isFulfilled = resultSet.getBoolean(9);
|
boolean isFulfilled = resultSet.getBoolean(9);
|
||||||
|
|
||||||
OrderData order = new OrderData(orderId, publicKey, haveAssetId, wantAssetId, amount, fulfilled, price, timestamp, isClosed, isFulfilled);
|
OrderData order = new OrderData(orderId, publicKey, haveAssetId, wantAssetId, amount, fulfilled, price,
|
||||||
|
timestamp, isClosed, isFulfilled);
|
||||||
orders.add(order);
|
orders.add(order);
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
@ -283,8 +302,8 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<OrderData> getAccountsOrders(byte[] publicKey, long haveAssetId, long wantAssetId, Boolean optIsClosed, Boolean optIsFulfilled, Integer limit,
|
public List<OrderData> getAccountsOrders(byte[] publicKey, long haveAssetId, long wantAssetId, Boolean optIsClosed,
|
||||||
Integer offset, Boolean reverse) throws DataException {
|
Boolean optIsFulfilled, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
String sql = "SELECT asset_order_id, amount, fulfilled, price, ordered, is_closed, is_fulfilled FROM AssetOrders WHERE creator = ? AND have_asset_id = ? AND want_asset_id = ?";
|
String sql = "SELECT asset_order_id, amount, fulfilled, price, ordered, is_closed, is_fulfilled FROM AssetOrders WHERE creator = ? AND have_asset_id = ? AND want_asset_id = ?";
|
||||||
if (optIsClosed != null)
|
if (optIsClosed != null)
|
||||||
sql += " AND is_closed = " + (optIsClosed ? "TRUE" : "FALSE");
|
sql += " AND is_closed = " + (optIsClosed ? "TRUE" : "FALSE");
|
||||||
@ -310,7 +329,8 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
boolean isClosed = resultSet.getBoolean(6);
|
boolean isClosed = resultSet.getBoolean(6);
|
||||||
boolean isFulfilled = resultSet.getBoolean(7);
|
boolean isFulfilled = resultSet.getBoolean(7);
|
||||||
|
|
||||||
OrderData order = new OrderData(orderId, publicKey, haveAssetId, wantAssetId, amount, fulfilled, price, timestamp, isClosed, isFulfilled);
|
OrderData order = new OrderData(orderId, publicKey, haveAssetId, wantAssetId, amount, fulfilled, price,
|
||||||
|
timestamp, isClosed, isFulfilled);
|
||||||
orders.add(order);
|
orders.add(order);
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
@ -325,8 +345,9 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("AssetOrders");
|
HSQLDBSaver saveHelper = new HSQLDBSaver("AssetOrders");
|
||||||
|
|
||||||
saveHelper.bind("asset_order_id", orderData.getOrderId()).bind("creator", orderData.getCreatorPublicKey())
|
saveHelper.bind("asset_order_id", orderData.getOrderId()).bind("creator", orderData.getCreatorPublicKey())
|
||||||
.bind("have_asset_id", orderData.getHaveAssetId()).bind("want_asset_id", orderData.getWantAssetId()).bind("amount", orderData.getAmount())
|
.bind("have_asset_id", orderData.getHaveAssetId()).bind("want_asset_id", orderData.getWantAssetId())
|
||||||
.bind("fulfilled", orderData.getFulfilled()).bind("price", orderData.getPrice()).bind("ordered", new Timestamp(orderData.getTimestamp()))
|
.bind("amount", orderData.getAmount()).bind("fulfilled", orderData.getFulfilled())
|
||||||
|
.bind("price", orderData.getPrice()).bind("ordered", new Timestamp(orderData.getTimestamp()))
|
||||||
.bind("is_closed", orderData.getIsClosed()).bind("is_fulfilled", orderData.getIsFulfilled());
|
.bind("is_closed", orderData.getIsClosed()).bind("is_fulfilled", orderData.getIsFulfilled());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -348,8 +369,9 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
// Trades
|
// Trades
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TradeData> getTrades(long haveAssetId, long wantAssetId, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
public List<TradeData> getTrades(long haveAssetId, long wantAssetId, Integer limit, Integer offset, Boolean reverse)
|
||||||
String sql = "SELECT initiating_order_id, target_order_id, AssetTrades.amount, AssetTrades.price, traded FROM AssetOrders JOIN AssetTrades ON initiating_order_id = asset_order_id "
|
throws DataException {
|
||||||
|
String sql = "SELECT initiating_order_id, target_order_id, AssetTrades.target_amount, AssetTrades.initiator_amount, traded FROM AssetOrders JOIN AssetTrades ON initiating_order_id = asset_order_id "
|
||||||
+ "WHERE have_asset_id = ? AND want_asset_id = ? ORDER BY traded";
|
+ "WHERE have_asset_id = ? AND want_asset_id = ? ORDER BY traded";
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
sql += " DESC";
|
sql += " DESC";
|
||||||
@ -364,11 +386,12 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
do {
|
do {
|
||||||
byte[] initiatingOrderId = resultSet.getBytes(1);
|
byte[] initiatingOrderId = resultSet.getBytes(1);
|
||||||
byte[] targetOrderId = resultSet.getBytes(2);
|
byte[] targetOrderId = resultSet.getBytes(2);
|
||||||
BigDecimal amount = resultSet.getBigDecimal(3);
|
BigDecimal targetAmount = resultSet.getBigDecimal(3);
|
||||||
BigDecimal price = resultSet.getBigDecimal(4);
|
BigDecimal initiatorAmount = resultSet.getBigDecimal(4);
|
||||||
long timestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
|
long timestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
|
||||||
|
|
||||||
TradeData trade = new TradeData(initiatingOrderId, targetOrderId, amount, price, timestamp);
|
TradeData trade = new TradeData(initiatingOrderId, targetOrderId, targetAmount, initiatorAmount,
|
||||||
|
timestamp);
|
||||||
trades.add(trade);
|
trades.add(trade);
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
@ -379,19 +402,25 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<RecentTradeData> getRecentTrades(List<Long> assetIds, List<Long> otherAssetIds, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
public List<RecentTradeData> getRecentTrades(List<Long> assetIds, List<Long> otherAssetIds, Integer limit,
|
||||||
|
Integer offset, Boolean reverse) throws DataException {
|
||||||
// Find assetID pairs that have actually been traded
|
// Find assetID pairs that have actually been traded
|
||||||
String tradedAssetsSubquery = "SELECT have_asset_id, want_asset_id " + "FROM AssetTrades JOIN AssetOrders ON asset_order_id = initiating_order_id ";
|
String tradedAssetsSubquery = "SELECT have_asset_id, want_asset_id "
|
||||||
|
+ "FROM AssetTrades JOIN AssetOrders ON asset_order_id = initiating_order_id ";
|
||||||
|
|
||||||
// Optionally limit traded assetID pairs
|
// Optionally limit traded assetID pairs
|
||||||
if (!assetIds.isEmpty())
|
if (!assetIds.isEmpty())
|
||||||
// longs are safe enough to use literally
|
// longs are safe enough to use literally
|
||||||
tradedAssetsSubquery += "WHERE have_asset_id IN (" + String.join(", ", assetIds.stream().map(assetId -> assetId.toString()).collect(Collectors.toList())) + ")";
|
tradedAssetsSubquery += "WHERE have_asset_id IN (" + String.join(", ",
|
||||||
|
assetIds.stream().map(assetId -> assetId.toString()).collect(Collectors.toList())) + ")";
|
||||||
|
|
||||||
if (!otherAssetIds.isEmpty()) {
|
if (!otherAssetIds.isEmpty()) {
|
||||||
tradedAssetsSubquery += assetIds.isEmpty() ? " WHERE " : " AND ";
|
tradedAssetsSubquery += assetIds.isEmpty() ? " WHERE " : " AND ";
|
||||||
// longs are safe enough to use literally
|
// longs are safe enough to use literally
|
||||||
tradedAssetsSubquery += "want_asset_id IN (" + String.join(", ", otherAssetIds.stream().map(assetId -> assetId.toString()).collect(Collectors.toList())) + ")";
|
tradedAssetsSubquery += "want_asset_id IN ("
|
||||||
|
+ String.join(", ",
|
||||||
|
otherAssetIds.stream().map(assetId -> assetId.toString()).collect(Collectors.toList()))
|
||||||
|
+ ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
tradedAssetsSubquery += " GROUP BY have_asset_id, want_asset_id";
|
tradedAssetsSubquery += " GROUP BY have_asset_id, want_asset_id";
|
||||||
@ -403,8 +432,9 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
+ "ORDER BY traded DESC LIMIT 2";
|
+ "ORDER BY traded DESC LIMIT 2";
|
||||||
|
|
||||||
// Put it all together
|
// Put it all together
|
||||||
String sql = "SELECT have_asset_id, want_asset_id, RecentTrades.amount, RecentTrades.price, RecentTrades.traded " + "FROM (" + tradedAssetsSubquery
|
String sql = "SELECT have_asset_id, want_asset_id, RecentTrades.target_amount, RecentTrades.initiator_amount, RecentTrades.traded "
|
||||||
+ ") AS TradedAssets " + ", LATERAL (" + recentTradesSubquery + ") AS RecentTrades (amount, price, traded) " + "ORDER BY have_asset_id";
|
+ "FROM (" + tradedAssetsSubquery + ") AS TradedAssets " + ", LATERAL (" + recentTradesSubquery
|
||||||
|
+ ") AS RecentTrades (target_amount, initiator_amount, traded) " + "ORDER BY have_asset_id";
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
sql += " DESC";
|
sql += " DESC";
|
||||||
|
|
||||||
@ -425,11 +455,12 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
do {
|
do {
|
||||||
long haveAssetId = resultSet.getLong(1);
|
long haveAssetId = resultSet.getLong(1);
|
||||||
long wantAssetId = resultSet.getLong(2);
|
long wantAssetId = resultSet.getLong(2);
|
||||||
BigDecimal amount = resultSet.getBigDecimal(3);
|
BigDecimal otherAmount = resultSet.getBigDecimal(3);
|
||||||
BigDecimal price = resultSet.getBigDecimal(4);
|
BigDecimal amount = resultSet.getBigDecimal(4);
|
||||||
long timestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
|
long timestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
|
||||||
|
|
||||||
RecentTradeData recentTrade = new RecentTradeData(haveAssetId, wantAssetId, amount, price, timestamp);
|
RecentTradeData recentTrade = new RecentTradeData(haveAssetId, wantAssetId, otherAmount, amount,
|
||||||
|
timestamp);
|
||||||
recentTrades.add(recentTrade);
|
recentTrades.add(recentTrade);
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
@ -440,8 +471,9 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TradeData> getOrdersTrades(byte[] orderId, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
public List<TradeData> getOrdersTrades(byte[] orderId, Integer limit, Integer offset, Boolean reverse)
|
||||||
String sql = "SELECT initiating_order_id, target_order_id, amount, price, traded FROM AssetTrades WHERE initiating_order_id = ? OR target_order_id = ? ORDER BY traded";
|
throws DataException {
|
||||||
|
String sql = "SELECT initiating_order_id, target_order_id, target_amount, initiator_amount, traded FROM AssetTrades WHERE initiating_order_id = ? OR target_order_id = ? ORDER BY traded";
|
||||||
if (reverse != null && reverse)
|
if (reverse != null && reverse)
|
||||||
sql += " DESC";
|
sql += " DESC";
|
||||||
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
@ -455,11 +487,12 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
do {
|
do {
|
||||||
byte[] initiatingOrderId = resultSet.getBytes(1);
|
byte[] initiatingOrderId = resultSet.getBytes(1);
|
||||||
byte[] targetOrderId = resultSet.getBytes(2);
|
byte[] targetOrderId = resultSet.getBytes(2);
|
||||||
BigDecimal amount = resultSet.getBigDecimal(3);
|
BigDecimal targetAmount = resultSet.getBigDecimal(3);
|
||||||
BigDecimal price = resultSet.getBigDecimal(4);
|
BigDecimal initiatorAmount = resultSet.getBigDecimal(4);
|
||||||
long timestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
|
long timestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
|
||||||
|
|
||||||
TradeData trade = new TradeData(initiatingOrderId, targetOrderId, amount, price, timestamp);
|
TradeData trade = new TradeData(initiatingOrderId, targetOrderId, targetAmount, initiatorAmount,
|
||||||
|
timestamp);
|
||||||
trades.add(trade);
|
trades.add(trade);
|
||||||
} while (resultSet.next());
|
} while (resultSet.next());
|
||||||
|
|
||||||
@ -473,8 +506,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
public void save(TradeData tradeData) throws DataException {
|
public void save(TradeData tradeData) throws DataException {
|
||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("AssetTrades");
|
HSQLDBSaver saveHelper = new HSQLDBSaver("AssetTrades");
|
||||||
|
|
||||||
saveHelper.bind("initiating_order_id", tradeData.getInitiator()).bind("target_order_id", tradeData.getTarget()).bind("amount", tradeData.getAmount())
|
saveHelper.bind("initiating_order_id", tradeData.getInitiator()).bind("target_order_id", tradeData.getTarget())
|
||||||
.bind("price", tradeData.getPrice()).bind("traded", new Timestamp(tradeData.getTimestamp()));
|
.bind("target_amount", tradeData.getTargetAmount())
|
||||||
|
.bind("initiator_amount", tradeData.getInitiatorAmount())
|
||||||
|
.bind("traded", new Timestamp(tradeData.getTimestamp()));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saveHelper.execute(this.repository);
|
saveHelper.execute(this.repository);
|
||||||
@ -486,8 +521,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
@Override
|
@Override
|
||||||
public void delete(TradeData tradeData) throws DataException {
|
public void delete(TradeData tradeData) throws DataException {
|
||||||
try {
|
try {
|
||||||
this.repository.delete("AssetTrades", "initiating_order_id = ? AND target_order_id = ? AND amount = ? AND price = ?", tradeData.getInitiator(),
|
this.repository.delete("AssetTrades",
|
||||||
tradeData.getTarget(), tradeData.getAmount(), tradeData.getPrice());
|
"initiating_order_id = ? AND target_order_id = ? AND target_amount = ? AND initiator_amount = ?",
|
||||||
|
tradeData.getInitiator(), tradeData.getTarget(), tradeData.getTargetAmount(),
|
||||||
|
tradeData.getInitiatorAmount());
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to delete asset trade from repository", e);
|
throw new DataException("Unable to delete asset trade from repository", e);
|
||||||
}
|
}
|
||||||
|
@ -592,6 +592,26 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
stmt.execute("UPDATE AssetOrders SET is_closed = TRUE WHERE is_fulfilled = TRUE");
|
stmt.execute("UPDATE AssetOrders SET is_closed = TRUE WHERE is_fulfilled = TRUE");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 38:
|
||||||
|
// Rename asset trade columns for clarity
|
||||||
|
stmt.execute("ALTER TABLE AssetTrades ALTER COLUMN amount RENAME TO target_amount");
|
||||||
|
stmt.execute("ALTER TABLE AssetTrades ALTER COLUMN price RENAME TO initiator_amount");
|
||||||
|
// Add support for asset "data" - typically JSON map like registered name data
|
||||||
|
stmt.execute("CREATE TYPE AssetData AS VARCHAR(4000)");
|
||||||
|
stmt.execute("ALTER TABLE Assets ADD data AssetData NOT NULL DEFAULT '' BEFORE reference");
|
||||||
|
stmt.execute("ALTER TABLE Assets ADD creation_group_id GroupID NOT NULL DEFAULT 0 BEFORE reference");
|
||||||
|
// Add support for asset "data" to ISSUE_ASSET transaction
|
||||||
|
stmt.execute("ALTER TABLE IssueAssetTransactions ADD data AssetData NOT NULL DEFAULT '' BEFORE asset_id");
|
||||||
|
// Add support for UPDATE_ASSET transactions
|
||||||
|
stmt.execute("CREATE TABLE UpdateAssetTransactions (signature Signature, owner QoraPublicKey NOT NULL, asset_id AssetID NOT NULL, "
|
||||||
|
+ "new_owner QoraAddress NOT NULL, new_description GenericDescription NOT NULL, new_data AssetData NOT NULL, "
|
||||||
|
+ "orphan_reference Signature, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
|
||||||
|
// Correct Assets.reference to use ISSUE_ASSET transaction's signature instead of reference.
|
||||||
|
// This is to help UPDATE_ASSET orphaning.
|
||||||
|
stmt.execute("MERGE INTO Assets USING (SELECT asset_id, signature FROM Assets JOIN Transactions USING (reference) JOIN IssueAssetTransactions USING (signature)) AS Updates "
|
||||||
|
+ "ON Assets.asset_id = Updates.asset_id WHEN MATCHED THEN UPDATE SET Assets.reference = Updates.signature");
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return false;
|
return false;
|
||||||
|
@ -18,7 +18,7 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo
|
|||||||
|
|
||||||
TransactionData fromBase(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, BigDecimal fee, byte[] signature) throws DataException {
|
TransactionData fromBase(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, BigDecimal fee, byte[] signature) throws DataException {
|
||||||
try (ResultSet resultSet = this.repository.checkedExecute(
|
try (ResultSet resultSet = this.repository.checkedExecute(
|
||||||
"SELECT owner, asset_name, description, quantity, is_divisible, asset_id FROM IssueAssetTransactions WHERE signature = ?", signature)) {
|
"SELECT owner, asset_name, description, quantity, is_divisible, data, asset_id FROM IssueAssetTransactions WHERE signature = ?", signature)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -27,14 +27,15 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo
|
|||||||
String description = resultSet.getString(3);
|
String description = resultSet.getString(3);
|
||||||
long quantity = resultSet.getLong(4);
|
long quantity = resultSet.getLong(4);
|
||||||
boolean isDivisible = resultSet.getBoolean(5);
|
boolean isDivisible = resultSet.getBoolean(5);
|
||||||
|
String data = resultSet.getString(6);
|
||||||
|
|
||||||
// Special null-checking for asset ID
|
// Special null-checking for asset ID
|
||||||
Long assetId = resultSet.getLong(6);
|
Long assetId = resultSet.getLong(7);
|
||||||
if (resultSet.wasNull())
|
if (resultSet.wasNull())
|
||||||
assetId = null;
|
assetId = null;
|
||||||
|
|
||||||
return new IssueAssetTransactionData(timestamp, txGroupId, reference, creatorPublicKey, assetId, owner, assetName, description, quantity, isDivisible,
|
return new IssueAssetTransactionData(timestamp, txGroupId, reference, creatorPublicKey, assetId, owner, assetName, description, quantity, isDivisible,
|
||||||
fee, signature);
|
data, fee, signature);
|
||||||
} 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 +50,8 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo
|
|||||||
saveHelper.bind("signature", issueAssetTransactionData.getSignature()).bind("issuer", issueAssetTransactionData.getIssuerPublicKey())
|
saveHelper.bind("signature", issueAssetTransactionData.getSignature()).bind("issuer", issueAssetTransactionData.getIssuerPublicKey())
|
||||||
.bind("owner", issueAssetTransactionData.getOwner()).bind("asset_name", issueAssetTransactionData.getAssetName())
|
.bind("owner", issueAssetTransactionData.getOwner()).bind("asset_name", issueAssetTransactionData.getAssetName())
|
||||||
.bind("description", issueAssetTransactionData.getDescription()).bind("quantity", issueAssetTransactionData.getQuantity())
|
.bind("description", issueAssetTransactionData.getDescription()).bind("quantity", issueAssetTransactionData.getQuantity())
|
||||||
.bind("is_divisible", issueAssetTransactionData.getIsDivisible()).bind("asset_id", issueAssetTransactionData.getAssetId());
|
.bind("is_divisible", issueAssetTransactionData.getIsDivisible())
|
||||||
|
.bind("data", issueAssetTransactionData.getData()).bind("asset_id", issueAssetTransactionData.getAssetId());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saveHelper.execute(this.repository);
|
saveHelper.execute(this.repository);
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
package org.qora.repository.hsqldb.transaction;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import org.qora.data.transaction.TransactionData;
|
||||||
|
import org.qora.data.transaction.UpdateAssetTransactionData;
|
||||||
|
import org.qora.repository.DataException;
|
||||||
|
import org.qora.repository.hsqldb.HSQLDBRepository;
|
||||||
|
import org.qora.repository.hsqldb.HSQLDBSaver;
|
||||||
|
|
||||||
|
public class HSQLDBUpdateAssetTransactionRepository extends HSQLDBTransactionRepository {
|
||||||
|
|
||||||
|
public HSQLDBUpdateAssetTransactionRepository(HSQLDBRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionData fromBase(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, BigDecimal fee,
|
||||||
|
byte[] signature) throws DataException {
|
||||||
|
try (ResultSet resultSet = this.repository.checkedExecute(
|
||||||
|
"SELECT asset_id, new_owner, new_description, new_data, orphan_reference FROM UpdateAssetTransactions WHERE signature = ?",
|
||||||
|
signature)) {
|
||||||
|
if (resultSet == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
long assetId = resultSet.getLong(1);
|
||||||
|
String newOwner = resultSet.getString(2);
|
||||||
|
String newDescription = resultSet.getString(3);
|
||||||
|
String newData = resultSet.getString(4);
|
||||||
|
byte[] orphanReference = resultSet.getBytes(5);
|
||||||
|
|
||||||
|
return new UpdateAssetTransactionData(timestamp, txGroupId, reference, creatorPublicKey, assetId, newOwner,
|
||||||
|
newDescription, newData, fee, orphanReference, signature);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to fetch update asset transaction from repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(TransactionData transactionData) throws DataException {
|
||||||
|
UpdateAssetTransactionData updateAssetTransactionData = (UpdateAssetTransactionData) transactionData;
|
||||||
|
|
||||||
|
HSQLDBSaver saveHelper = new HSQLDBSaver("UpdateAssetTransactions");
|
||||||
|
|
||||||
|
saveHelper.bind("signature", updateAssetTransactionData.getSignature())
|
||||||
|
.bind("owner", updateAssetTransactionData.getOwnerPublicKey())
|
||||||
|
.bind("asset_id", updateAssetTransactionData.getAssetId())
|
||||||
|
.bind("new_owner", updateAssetTransactionData.getNewOwner())
|
||||||
|
.bind("new_description", updateAssetTransactionData.getNewDescription())
|
||||||
|
.bind("new_data", updateAssetTransactionData.getNewData())
|
||||||
|
.bind("orphan_reference", updateAssetTransactionData.getOrphanReference());
|
||||||
|
|
||||||
|
try {
|
||||||
|
saveHelper.execute(this.repository);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to save update asset transaction into repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,10 +22,6 @@ public class IssueAssetTransaction extends Transaction {
|
|||||||
// Properties
|
// Properties
|
||||||
private IssueAssetTransactionData issueAssetTransactionData;
|
private IssueAssetTransactionData issueAssetTransactionData;
|
||||||
|
|
||||||
// Other useful constants
|
|
||||||
public static final int MAX_NAME_SIZE = 400;
|
|
||||||
public static final int MAX_DESCRIPTION_SIZE = 4000;
|
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
public IssueAssetTransaction(Repository repository, TransactionData transactionData) {
|
public IssueAssetTransaction(Repository repository, TransactionData transactionData) {
|
||||||
@ -86,22 +82,35 @@ public class IssueAssetTransaction extends Transaction {
|
|||||||
if (this.issueAssetTransactionData.getTimestamp() < BlockChain.getInstance().getAssetsReleaseTimestamp())
|
if (this.issueAssetTransactionData.getTimestamp() < BlockChain.getInstance().getAssetsReleaseTimestamp())
|
||||||
return ValidationResult.NOT_YET_RELEASED;
|
return ValidationResult.NOT_YET_RELEASED;
|
||||||
|
|
||||||
|
// "data" field is only allowed in v2
|
||||||
|
String data = this.issueAssetTransactionData.getData();
|
||||||
|
if (this.issueAssetTransactionData.getTimestamp() >= BlockChain.getInstance().getQoraV2Timestamp()) {
|
||||||
|
// v2 so check data field properly
|
||||||
|
int dataLength = Utf8.encodedLength(data);
|
||||||
|
if (data == null || dataLength < 1 || dataLength > Asset.MAX_DATA_SIZE)
|
||||||
|
return ValidationResult.INVALID_DATA_LENGTH;
|
||||||
|
} else {
|
||||||
|
// pre-v2 so disallow data field
|
||||||
|
if (data != null)
|
||||||
|
return ValidationResult.NOT_YET_RELEASED;
|
||||||
|
}
|
||||||
|
|
||||||
// Check owner address is valid
|
// Check owner address is valid
|
||||||
if (!Crypto.isValidAddress(issueAssetTransactionData.getOwner()))
|
if (!Crypto.isValidAddress(issueAssetTransactionData.getOwner()))
|
||||||
return ValidationResult.INVALID_ADDRESS;
|
return ValidationResult.INVALID_ADDRESS;
|
||||||
|
|
||||||
// Check name size bounds
|
// Check name size bounds
|
||||||
int assetNameLength = Utf8.encodedLength(issueAssetTransactionData.getAssetName());
|
int assetNameLength = Utf8.encodedLength(issueAssetTransactionData.getAssetName());
|
||||||
if (assetNameLength < 1 || assetNameLength > IssueAssetTransaction.MAX_NAME_SIZE)
|
if (assetNameLength < 1 || assetNameLength > Asset.MAX_NAME_SIZE)
|
||||||
return ValidationResult.INVALID_NAME_LENGTH;
|
return ValidationResult.INVALID_NAME_LENGTH;
|
||||||
|
|
||||||
// Check description size bounds
|
// Check description size bounds
|
||||||
int assetDescriptionlength = Utf8.encodedLength(issueAssetTransactionData.getDescription());
|
int assetDescriptionlength = Utf8.encodedLength(issueAssetTransactionData.getDescription());
|
||||||
if (assetDescriptionlength < 1 || assetDescriptionlength > IssueAssetTransaction.MAX_DESCRIPTION_SIZE)
|
if (assetDescriptionlength < 1 || assetDescriptionlength > Asset.MAX_DESCRIPTION_SIZE)
|
||||||
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
|
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
|
||||||
|
|
||||||
// Check quantity - either 10 billion or if that's not enough: a billion billion!
|
// Check quantity - either 10 billion or if that's not enough: a billion billion!
|
||||||
long maxQuantity = issueAssetTransactionData.getIsDivisible() ? 10_000_000_000L : 1_000_000_000_000_000_000L;
|
long maxQuantity = issueAssetTransactionData.getIsDivisible() ? Asset.MAX_DIVISIBLE_QUANTITY : Asset.MAX_INDIVISIBLE_QUANTITY;
|
||||||
if (issueAssetTransactionData.getQuantity() < 1 || issueAssetTransactionData.getQuantity() > maxQuantity)
|
if (issueAssetTransactionData.getQuantity() < 1 || issueAssetTransactionData.getQuantity() > maxQuantity)
|
||||||
return ValidationResult.INVALID_QUANTITY;
|
return ValidationResult.INVALID_QUANTITY;
|
||||||
|
|
||||||
|
@ -71,7 +71,8 @@ public abstract class Transaction {
|
|||||||
JOIN_GROUP(31, false),
|
JOIN_GROUP(31, false),
|
||||||
LEAVE_GROUP(32, false),
|
LEAVE_GROUP(32, false),
|
||||||
GROUP_APPROVAL(33, false),
|
GROUP_APPROVAL(33, false),
|
||||||
SET_GROUP(34, false);
|
SET_GROUP(34, false),
|
||||||
|
UPDATE_ASSET(35, true);
|
||||||
|
|
||||||
public final int value;
|
public final int value;
|
||||||
public final boolean needsApproval;
|
public final boolean needsApproval;
|
||||||
@ -187,6 +188,7 @@ public abstract class Transaction {
|
|||||||
INVALID_TX_GROUP_ID(67),
|
INVALID_TX_GROUP_ID(67),
|
||||||
TX_GROUP_ID_MISMATCH(68),
|
TX_GROUP_ID_MISMATCH(68),
|
||||||
MULTIPLE_NAMES_FORBIDDEN(69),
|
MULTIPLE_NAMES_FORBIDDEN(69),
|
||||||
|
INVALID_ASSET_OWNER(70),
|
||||||
NOT_YET_RELEASED(1000);
|
NOT_YET_RELEASED(1000);
|
||||||
|
|
||||||
public final int value;
|
public final int value;
|
||||||
|
164
src/main/java/org/qora/transaction/UpdateAssetTransaction.java
Normal file
164
src/main/java/org/qora/transaction/UpdateAssetTransaction.java
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
package org.qora.transaction;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.qora.account.Account;
|
||||||
|
import org.qora.account.PublicKeyAccount;
|
||||||
|
import org.qora.asset.Asset;
|
||||||
|
import org.qora.block.BlockChain;
|
||||||
|
import org.qora.crypto.Crypto;
|
||||||
|
import org.qora.data.asset.AssetData;
|
||||||
|
import org.qora.data.transaction.TransactionData;
|
||||||
|
import org.qora.data.transaction.UpdateAssetTransactionData;
|
||||||
|
import org.qora.repository.DataException;
|
||||||
|
import org.qora.repository.Repository;
|
||||||
|
|
||||||
|
import com.google.common.base.Utf8;
|
||||||
|
|
||||||
|
public class UpdateAssetTransaction extends Transaction {
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
private UpdateAssetTransactionData updateAssetTransactionData;
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
public UpdateAssetTransaction(Repository repository, TransactionData transactionData) {
|
||||||
|
super(repository, transactionData);
|
||||||
|
|
||||||
|
this.updateAssetTransactionData = (UpdateAssetTransactionData) this.transactionData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// More information
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Account> getRecipientAccounts() throws DataException {
|
||||||
|
return Collections.singletonList(getNewOwner());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInvolved(Account account) throws DataException {
|
||||||
|
String address = account.getAddress();
|
||||||
|
|
||||||
|
if (address.equals(this.getOwner().getAddress()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (address.equals(this.getNewOwner().getAddress()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getAmount(Account account) throws DataException {
|
||||||
|
String address = account.getAddress();
|
||||||
|
BigDecimal amount = BigDecimal.ZERO.setScale(8);
|
||||||
|
|
||||||
|
if (address.equals(this.getOwner().getAddress()))
|
||||||
|
amount = amount.subtract(this.transactionData.getFee());
|
||||||
|
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
|
||||||
|
public PublicKeyAccount getOwner() throws DataException {
|
||||||
|
return new PublicKeyAccount(this.repository, this.updateAssetTransactionData.getOwnerPublicKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account getNewOwner() throws DataException {
|
||||||
|
return new Account(this.repository, this.updateAssetTransactionData.getNewOwner());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processing
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValidationResult isValid() throws DataException {
|
||||||
|
// V2-only transaction
|
||||||
|
if (this.updateAssetTransactionData.getTimestamp() < BlockChain.getInstance().getQoraV2Timestamp())
|
||||||
|
return ValidationResult.NOT_YET_RELEASED;
|
||||||
|
|
||||||
|
// Check asset actually exists
|
||||||
|
AssetData assetData = this.repository.getAssetRepository().fromAssetId(updateAssetTransactionData.getAssetId());
|
||||||
|
if (assetData == null)
|
||||||
|
return ValidationResult.ASSET_DOES_NOT_EXIST;
|
||||||
|
|
||||||
|
// Check transaction's public key matches asset's current owner
|
||||||
|
PublicKeyAccount currentOwner = getOwner();
|
||||||
|
if (!assetData.getOwner().equals(currentOwner.getAddress()))
|
||||||
|
return ValidationResult.INVALID_ASSET_OWNER;
|
||||||
|
|
||||||
|
// Check new owner address is valid
|
||||||
|
if (!Crypto.isValidAddress(updateAssetTransactionData.getNewOwner()))
|
||||||
|
return ValidationResult.INVALID_ADDRESS;
|
||||||
|
|
||||||
|
// Check new description size bounds
|
||||||
|
int newDescriptionLength = Utf8.encodedLength(updateAssetTransactionData.getNewDescription());
|
||||||
|
if (newDescriptionLength < 1 || newDescriptionLength > Asset.MAX_DESCRIPTION_SIZE)
|
||||||
|
return ValidationResult.INVALID_DATA_LENGTH;
|
||||||
|
|
||||||
|
// Check new data size bounds
|
||||||
|
int newDataLength = Utf8.encodedLength(updateAssetTransactionData.getNewData());
|
||||||
|
if (newDataLength < 1 || newDataLength > Asset.MAX_DATA_SIZE)
|
||||||
|
return ValidationResult.INVALID_DATA_LENGTH;
|
||||||
|
|
||||||
|
// As this transaction type could require approval, check txGroupId
|
||||||
|
// matches groupID at creation
|
||||||
|
if (assetData.getCreationGroupId() != updateAssetTransactionData.getTxGroupId())
|
||||||
|
return ValidationResult.TX_GROUP_ID_MISMATCH;
|
||||||
|
|
||||||
|
// Check fee is positive
|
||||||
|
if (updateAssetTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
|
||||||
|
return ValidationResult.NEGATIVE_FEE;
|
||||||
|
|
||||||
|
// Check reference is correct
|
||||||
|
if (!Arrays.equals(currentOwner.getLastReference(), updateAssetTransactionData.getReference()))
|
||||||
|
return ValidationResult.INVALID_REFERENCE;
|
||||||
|
|
||||||
|
// Check current owner has enough funds
|
||||||
|
if (currentOwner.getConfirmedBalance(Asset.QORA).compareTo(updateAssetTransactionData.getFee()) < 0)
|
||||||
|
return ValidationResult.NO_BALANCE;
|
||||||
|
|
||||||
|
return ValidationResult.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() throws DataException {
|
||||||
|
// Update Asset
|
||||||
|
Asset asset = new Asset(this.repository, updateAssetTransactionData.getAssetId());
|
||||||
|
asset.update(updateAssetTransactionData);
|
||||||
|
|
||||||
|
// Save this transaction, now with updated "name reference" to previous
|
||||||
|
// transaction that updated name
|
||||||
|
this.repository.getTransactionRepository().save(updateAssetTransactionData);
|
||||||
|
|
||||||
|
// Update old owner's balance
|
||||||
|
Account owner = getOwner();
|
||||||
|
owner.setConfirmedBalance(Asset.QORA,
|
||||||
|
owner.getConfirmedBalance(Asset.QORA).subtract(updateAssetTransactionData.getFee()));
|
||||||
|
|
||||||
|
// Update owner's reference
|
||||||
|
owner.setLastReference(updateAssetTransactionData.getSignature());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void orphan() throws DataException {
|
||||||
|
// Revert asset
|
||||||
|
Asset asset = new Asset(this.repository, updateAssetTransactionData.getAssetId());
|
||||||
|
asset.revert(updateAssetTransactionData);
|
||||||
|
|
||||||
|
// Delete this transaction itself
|
||||||
|
this.repository.getTransactionRepository().delete(updateAssetTransactionData);
|
||||||
|
|
||||||
|
// Update owner's balance
|
||||||
|
Account owner = getOwner();
|
||||||
|
owner.setConfirmedBalance(Asset.QORA,
|
||||||
|
owner.getConfirmedBalance(Asset.QORA).add(updateAssetTransactionData.getFee()));
|
||||||
|
|
||||||
|
// Update owner's reference
|
||||||
|
owner.setLastReference(updateAssetTransactionData.getReference());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,10 +6,10 @@ import java.math.BigDecimal;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.qora.asset.Asset;
|
||||||
import org.qora.block.BlockChain;
|
import org.qora.block.BlockChain;
|
||||||
import org.qora.data.transaction.IssueAssetTransactionData;
|
import org.qora.data.transaction.IssueAssetTransactionData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.transaction.IssueAssetTransaction;
|
|
||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
import org.qora.transform.TransformationException;
|
import org.qora.transform.TransformationException;
|
||||||
import org.qora.utils.Serialization;
|
import org.qora.utils.Serialization;
|
||||||
@ -26,8 +26,10 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
|
|||||||
private static final int QUANTITY_LENGTH = LONG_LENGTH;
|
private static final int QUANTITY_LENGTH = LONG_LENGTH;
|
||||||
private static final int IS_DIVISIBLE_LENGTH = BOOLEAN_LENGTH;
|
private static final int IS_DIVISIBLE_LENGTH = BOOLEAN_LENGTH;
|
||||||
private static final int ASSET_REFERENCE_LENGTH = REFERENCE_LENGTH;
|
private static final int ASSET_REFERENCE_LENGTH = REFERENCE_LENGTH;
|
||||||
|
private static final int DATA_SIZE_LENGTH = INT_LENGTH;
|
||||||
|
|
||||||
private static final int EXTRAS_LENGTH = OWNER_LENGTH + NAME_SIZE_LENGTH + DESCRIPTION_SIZE_LENGTH + QUANTITY_LENGTH + IS_DIVISIBLE_LENGTH;
|
private static final int EXTRAS_LENGTH = OWNER_LENGTH + NAME_SIZE_LENGTH + DESCRIPTION_SIZE_LENGTH + QUANTITY_LENGTH
|
||||||
|
+ IS_DIVISIBLE_LENGTH;
|
||||||
|
|
||||||
protected static final TransactionLayout layout;
|
protected static final TransactionLayout layout;
|
||||||
|
|
||||||
@ -45,6 +47,8 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
|
|||||||
layout.add("asset description", TransformationType.STRING);
|
layout.add("asset description", TransformationType.STRING);
|
||||||
layout.add("asset quantity", TransformationType.LONG);
|
layout.add("asset quantity", TransformationType.LONG);
|
||||||
layout.add("can asset quantities be fractional?", TransformationType.BOOLEAN);
|
layout.add("can asset quantities be fractional?", TransformationType.BOOLEAN);
|
||||||
|
layout.add("asset data length", TransformationType.INT);
|
||||||
|
layout.add("asset data", TransformationType.STRING);
|
||||||
layout.add("fee", TransformationType.AMOUNT);
|
layout.add("fee", TransformationType.AMOUNT);
|
||||||
layout.add("signature", TransformationType.SIGNATURE);
|
layout.add("signature", TransformationType.SIGNATURE);
|
||||||
}
|
}
|
||||||
@ -63,16 +67,22 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
|
|||||||
|
|
||||||
String owner = Serialization.deserializeAddress(byteBuffer);
|
String owner = Serialization.deserializeAddress(byteBuffer);
|
||||||
|
|
||||||
String assetName = Serialization.deserializeSizedString(byteBuffer, IssueAssetTransaction.MAX_NAME_SIZE);
|
String assetName = Serialization.deserializeSizedString(byteBuffer, Asset.MAX_NAME_SIZE);
|
||||||
|
|
||||||
String description = Serialization.deserializeSizedString(byteBuffer, IssueAssetTransaction.MAX_DESCRIPTION_SIZE);
|
String description = Serialization.deserializeSizedString(byteBuffer, Asset.MAX_DESCRIPTION_SIZE);
|
||||||
|
|
||||||
long quantity = byteBuffer.getLong();
|
long quantity = byteBuffer.getLong();
|
||||||
|
|
||||||
boolean isDivisible = byteBuffer.get() != 0;
|
boolean isDivisible = byteBuffer.get() != 0;
|
||||||
|
|
||||||
|
// in v2, assets have "data" field
|
||||||
|
String data = null;
|
||||||
|
if (timestamp >= BlockChain.getInstance().getQoraV2Timestamp())
|
||||||
|
data = Serialization.deserializeSizedString(byteBuffer, Asset.MAX_DATA_SIZE);
|
||||||
|
|
||||||
byte[] assetReference = new byte[ASSET_REFERENCE_LENGTH];
|
byte[] assetReference = new byte[ASSET_REFERENCE_LENGTH];
|
||||||
// In v1, IssueAssetTransaction uses Asset.parse which also deserializes reference.
|
// In v1, IssueAssetTransaction uses Asset.parse which also deserializes
|
||||||
|
// reference.
|
||||||
if (timestamp < BlockChain.getInstance().getQoraV2Timestamp())
|
if (timestamp < BlockChain.getInstance().getQoraV2Timestamp())
|
||||||
byteBuffer.get(assetReference);
|
byteBuffer.get(assetReference);
|
||||||
|
|
||||||
@ -81,17 +91,23 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
|
|||||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||||
byteBuffer.get(signature);
|
byteBuffer.get(signature);
|
||||||
|
|
||||||
return new IssueAssetTransactionData(timestamp, txGroupId, reference, issuerPublicKey, owner, assetName, description, quantity, isDivisible, fee,
|
return new IssueAssetTransactionData(timestamp, txGroupId, reference, issuerPublicKey, owner, assetName,
|
||||||
signature);
|
description, quantity, isDivisible, data, fee, signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
||||||
IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) transactionData;
|
IssueAssetTransactionData issueAssetTransactionData = (IssueAssetTransactionData) transactionData;
|
||||||
|
|
||||||
int dataLength = getBaseLength(transactionData) + EXTRAS_LENGTH + Utf8.encodedLength(issueAssetTransactionData.getAssetName())
|
int dataLength = getBaseLength(transactionData) + EXTRAS_LENGTH
|
||||||
|
+ Utf8.encodedLength(issueAssetTransactionData.getAssetName())
|
||||||
+ Utf8.encodedLength(issueAssetTransactionData.getDescription());
|
+ Utf8.encodedLength(issueAssetTransactionData.getDescription());
|
||||||
|
|
||||||
// In v1, IssueAssetTransaction uses Asset.toBytes which also serializes reference.
|
// In v2, assets have "data" field
|
||||||
|
if (transactionData.getTimestamp() < BlockChain.getInstance().getQoraV2Timestamp())
|
||||||
|
dataLength += DATA_SIZE_LENGTH + Utf8.encodedLength(issueAssetTransactionData.getData());
|
||||||
|
|
||||||
|
// In v1, IssueAssetTransaction uses Asset.toBytes which also serializes
|
||||||
|
// reference.
|
||||||
if (transactionData.getTimestamp() < BlockChain.getInstance().getQoraV2Timestamp())
|
if (transactionData.getTimestamp() < BlockChain.getInstance().getQoraV2Timestamp())
|
||||||
dataLength += ASSET_REFERENCE_LENGTH;
|
dataLength += ASSET_REFERENCE_LENGTH;
|
||||||
|
|
||||||
@ -115,7 +131,13 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
|
|||||||
bytes.write(Longs.toByteArray(issueAssetTransactionData.getQuantity()));
|
bytes.write(Longs.toByteArray(issueAssetTransactionData.getQuantity()));
|
||||||
bytes.write((byte) (issueAssetTransactionData.getIsDivisible() ? 1 : 0));
|
bytes.write((byte) (issueAssetTransactionData.getIsDivisible() ? 1 : 0));
|
||||||
|
|
||||||
// In v1, IssueAssetTransaction uses Asset.toBytes which also serializes Asset's reference which is the IssueAssetTransaction's signature
|
// In v2, assets have "data"
|
||||||
|
if (transactionData.getTimestamp() >= BlockChain.getInstance().getQoraV2Timestamp())
|
||||||
|
Serialization.serializeSizedString(bytes, issueAssetTransactionData.getData());
|
||||||
|
|
||||||
|
// In v1, IssueAssetTransaction uses Asset.toBytes which also
|
||||||
|
// serializes Asset's reference which is the IssueAssetTransaction's
|
||||||
|
// signature
|
||||||
if (transactionData.getTimestamp() < BlockChain.getInstance().getQoraV2Timestamp()) {
|
if (transactionData.getTimestamp() < BlockChain.getInstance().getQoraV2Timestamp()) {
|
||||||
byte[] assetReference = issueAssetTransactionData.getSignature();
|
byte[] assetReference = issueAssetTransactionData.getSignature();
|
||||||
if (assetReference != null)
|
if (assetReference != null)
|
||||||
@ -136,7 +158,8 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In Qora v1, the bytes used for verification have asset's reference zeroed so we need to test for v1-ness and adjust the bytes accordingly.
|
* In Qora v1, the bytes used for verification have asset's reference zeroed
|
||||||
|
* so we need to test for v1-ness and adjust the bytes accordingly.
|
||||||
*
|
*
|
||||||
* @param transactionData
|
* @param transactionData
|
||||||
* @return byte[]
|
* @return byte[]
|
||||||
@ -151,7 +174,11 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
|
|||||||
// Special v1 version
|
// Special v1 version
|
||||||
|
|
||||||
// Zero duplicate signature/reference
|
// Zero duplicate signature/reference
|
||||||
int start = bytes.length - ASSET_REFERENCE_LENGTH - FEE_LENGTH; // before asset reference (and fee)
|
int start = bytes.length - ASSET_REFERENCE_LENGTH - FEE_LENGTH; // before
|
||||||
|
// asset
|
||||||
|
// reference
|
||||||
|
// (and
|
||||||
|
// fee)
|
||||||
int end = start + ASSET_REFERENCE_LENGTH;
|
int end = start + ASSET_REFERENCE_LENGTH;
|
||||||
Arrays.fill(bytes, start, end, (byte) 0);
|
Arrays.fill(bytes, start, end, (byte) 0);
|
||||||
|
|
||||||
|
@ -208,11 +208,9 @@ public abstract class TransactionTransformer extends Transformer {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
return (TransactionData) method.invoke(null, byteBuffer);
|
return (TransactionData) method.invoke(null, byteBuffer);
|
||||||
} catch (BufferUnderflowException e) {
|
|
||||||
throw new TransformationException("Byte data too short for transaction type [" + type.value + "]");
|
|
||||||
} catch (InvocationTargetException e) {
|
} catch (InvocationTargetException e) {
|
||||||
if (e.getCause() instanceof BufferUnderflowException)
|
if (e.getCause() instanceof BufferUnderflowException)
|
||||||
throw (BufferUnderflowException) e.getCause();
|
throw new TransformationException("Byte data too short for transaction type [" + type.value + "]");
|
||||||
|
|
||||||
if (e.getCause() instanceof TransformationException)
|
if (e.getCause() instanceof TransformationException)
|
||||||
throw (TransformationException) e.getCause();
|
throw (TransformationException) e.getCause();
|
||||||
|
@ -0,0 +1,115 @@
|
|||||||
|
package org.qora.transform.transaction;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.qora.asset.Asset;
|
||||||
|
import org.qora.block.BlockChain;
|
||||||
|
import org.qora.data.transaction.UpdateAssetTransactionData;
|
||||||
|
import org.qora.data.transaction.TransactionData;
|
||||||
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
|
import org.qora.transform.TransformationException;
|
||||||
|
import org.qora.utils.Serialization;
|
||||||
|
|
||||||
|
import com.google.common.base.Utf8;
|
||||||
|
import com.google.common.primitives.Longs;
|
||||||
|
|
||||||
|
public class UpdateAssetTransactionTransformer extends TransactionTransformer {
|
||||||
|
|
||||||
|
// Property lengths
|
||||||
|
private static final int ASSET_ID_LENGTH = LONG_LENGTH;
|
||||||
|
private static final int NEW_OWNER_LENGTH = ADDRESS_LENGTH;
|
||||||
|
private static final int NEW_DESCRIPTION_SIZE_LENGTH = INT_LENGTH;
|
||||||
|
private static final int NEW_DATA_SIZE_LENGTH = INT_LENGTH;
|
||||||
|
|
||||||
|
private static final int EXTRAS_LENGTH = ASSET_ID_LENGTH + NEW_OWNER_LENGTH + NEW_DESCRIPTION_SIZE_LENGTH
|
||||||
|
+ NEW_DATA_SIZE_LENGTH;
|
||||||
|
|
||||||
|
protected static final TransactionLayout layout;
|
||||||
|
|
||||||
|
static {
|
||||||
|
layout = new TransactionLayout();
|
||||||
|
layout.add("txType: " + TransactionType.ISSUE_ASSET.valueString, TransformationType.INT);
|
||||||
|
layout.add("timestamp", TransformationType.TIMESTAMP);
|
||||||
|
layout.add("transaction's groupID", TransformationType.INT);
|
||||||
|
layout.add("reference", TransformationType.SIGNATURE);
|
||||||
|
layout.add("asset owner's public key", TransformationType.PUBLIC_KEY);
|
||||||
|
layout.add("asset ID", TransformationType.LONG);
|
||||||
|
layout.add("asset new owner", TransformationType.ADDRESS);
|
||||||
|
layout.add("asset new description length", TransformationType.INT);
|
||||||
|
layout.add("asset new description", TransformationType.STRING);
|
||||||
|
layout.add("asset new data length", TransformationType.INT);
|
||||||
|
layout.add("asset new data", TransformationType.STRING);
|
||||||
|
layout.add("fee", TransformationType.AMOUNT);
|
||||||
|
layout.add("signature", TransformationType.SIGNATURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
||||||
|
long timestamp = byteBuffer.getLong();
|
||||||
|
|
||||||
|
int txGroupId = 0;
|
||||||
|
if (timestamp >= BlockChain.getInstance().getQoraV2Timestamp())
|
||||||
|
txGroupId = byteBuffer.getInt();
|
||||||
|
|
||||||
|
byte[] reference = new byte[REFERENCE_LENGTH];
|
||||||
|
byteBuffer.get(reference);
|
||||||
|
|
||||||
|
byte[] ownerPublicKey = Serialization.deserializePublicKey(byteBuffer);
|
||||||
|
|
||||||
|
long assetId = byteBuffer.getLong();
|
||||||
|
|
||||||
|
String newOwner = Serialization.deserializeAddress(byteBuffer);
|
||||||
|
|
||||||
|
String newDescription = Serialization.deserializeSizedString(byteBuffer, Asset.MAX_DESCRIPTION_SIZE);
|
||||||
|
|
||||||
|
String newData = Serialization.deserializeSizedString(byteBuffer, Asset.MAX_DATA_SIZE);
|
||||||
|
|
||||||
|
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
|
||||||
|
|
||||||
|
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||||
|
byteBuffer.get(signature);
|
||||||
|
|
||||||
|
return new UpdateAssetTransactionData(timestamp, txGroupId, reference, ownerPublicKey, assetId, newOwner,
|
||||||
|
newDescription, newData, fee, null, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
||||||
|
UpdateAssetTransactionData updateAssetTransactionData = (UpdateAssetTransactionData) transactionData;
|
||||||
|
|
||||||
|
int dataLength = getBaseLength(transactionData) + EXTRAS_LENGTH
|
||||||
|
+ Utf8.encodedLength(updateAssetTransactionData.getNewDescription())
|
||||||
|
+ Utf8.encodedLength(updateAssetTransactionData.getNewData());
|
||||||
|
|
||||||
|
return dataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
|
||||||
|
try {
|
||||||
|
UpdateAssetTransactionData updateAssetTransactionData = (UpdateAssetTransactionData) transactionData;
|
||||||
|
|
||||||
|
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
transformCommonBytes(transactionData, bytes);
|
||||||
|
|
||||||
|
bytes.write(Longs.toByteArray(updateAssetTransactionData.getAssetId()));
|
||||||
|
|
||||||
|
Serialization.serializeAddress(bytes, updateAssetTransactionData.getNewOwner());
|
||||||
|
|
||||||
|
Serialization.serializeSizedString(bytes, updateAssetTransactionData.getNewDescription());
|
||||||
|
|
||||||
|
Serialization.serializeSizedString(bytes, updateAssetTransactionData.getNewData());
|
||||||
|
|
||||||
|
Serialization.serializeBigDecimal(bytes, updateAssetTransactionData.getFee());
|
||||||
|
|
||||||
|
if (updateAssetTransactionData.getSignature() != null)
|
||||||
|
bytes.write(updateAssetTransactionData.getSignature());
|
||||||
|
|
||||||
|
return bytes.toByteArray();
|
||||||
|
} catch (IOException | ClassCastException e) {
|
||||||
|
throw new TransformationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -605,9 +605,10 @@ public class TransactionTests extends Common {
|
|||||||
boolean isDivisible = true;
|
boolean isDivisible = true;
|
||||||
BigDecimal fee = BigDecimal.ONE;
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
||||||
|
String data = (timestamp >= BlockChain.getInstance().getQoraV2Timestamp()) ? "{}" : null;
|
||||||
|
|
||||||
IssueAssetTransactionData issueAssetTransactionData = new IssueAssetTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(),
|
IssueAssetTransactionData issueAssetTransactionData = new IssueAssetTransactionData(timestamp, Group.NO_GROUP, reference, sender.getPublicKey(),
|
||||||
sender.getAddress(), assetName, description, quantity, isDivisible, fee);
|
sender.getAddress(), assetName, description, quantity, isDivisible, data, fee);
|
||||||
|
|
||||||
Transaction issueAssetTransaction = new IssueAssetTransaction(repository, issueAssetTransactionData);
|
Transaction issueAssetTransaction = new IssueAssetTransaction(repository, issueAssetTransactionData);
|
||||||
issueAssetTransaction.sign(sender);
|
issueAssetTransaction.sign(sender);
|
||||||
@ -989,11 +990,11 @@ public class TransactionTests extends Common {
|
|||||||
|
|
||||||
// Check trade has correct values
|
// Check trade has correct values
|
||||||
BigDecimal expectedAmount = amount.divide(originalOrderData.getPrice()).setScale(8);
|
BigDecimal expectedAmount = amount.divide(originalOrderData.getPrice()).setScale(8);
|
||||||
BigDecimal actualAmount = tradeData.getAmount();
|
BigDecimal actualAmount = tradeData.getTargetAmount();
|
||||||
assertTrue(expectedAmount.compareTo(actualAmount) == 0);
|
assertTrue(expectedAmount.compareTo(actualAmount) == 0);
|
||||||
|
|
||||||
BigDecimal expectedPrice = amount;
|
BigDecimal expectedPrice = amount;
|
||||||
BigDecimal actualPrice = tradeData.getPrice();
|
BigDecimal actualPrice = tradeData.getInitiatorAmount();
|
||||||
assertTrue(expectedPrice.compareTo(actualPrice) == 0);
|
assertTrue(expectedPrice.compareTo(actualPrice) == 0);
|
||||||
|
|
||||||
// Check seller's "test asset" balance
|
// Check seller's "test asset" balance
|
||||||
|
Loading…
Reference in New Issue
Block a user