Supply extra information fields to various asset-related API calls.

e.g. supply "assetName" in JSON for TRANSFER_ASSET transactions

Also supply have/want asset names, amount asset ID/name, price-pair
and creator address in asset orders.

Show CREATE_ASSET_ORDER amount ID/name & price-pair in correct
format depending on whether transaction was placed before/after
'new' asset pricing took effect. (Orders are always in 'new' form).

Change API call /assets/transfers/{assetid}/{address} to
/assets/transfers/{assetid} with optional "address" query param.
This commit is contained in:
catbref 2019-04-10 13:09:30 +01:00
parent d5a2e5be19
commit cfbf5c12bf
10 changed files with 310 additions and 52 deletions

View File

@ -2,6 +2,7 @@ package org.qora.api.model;
import java.math.BigDecimal; import java.math.BigDecimal;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
@ -11,11 +12,18 @@ import org.qora.data.asset.OrderData;
@XmlAccessorType(XmlAccessType.NONE) @XmlAccessorType(XmlAccessType.NONE)
public class AggregatedOrder { public class AggregatedOrder {
// Not exposed to API
private OrderData orderData; private OrderData orderData;
// Needed by JAXB for [un]marshalling
protected AggregatedOrder() { protected AggregatedOrder() {
} }
public void beforeMarshal(Marshaller m) {
// OrderData needs to calculate values for us
this.orderData.beforeMarshal(m);
}
public AggregatedOrder(OrderData orderData) { public AggregatedOrder(OrderData orderData) {
this.orderData = orderData; this.orderData = orderData;
} }
@ -30,4 +38,19 @@ public class AggregatedOrder {
return this.orderData.getAmount(); return this.orderData.getAmount();
} }
@XmlElement(name = "unfulfilledAssetId")
public long getUnfulfilledAssetId() {
return this.orderData.getAmountAssetId();
}
@XmlElement(name = "unfulfilledAssetName")
public String getUnfulfilledAssetName() {
return this.orderData.getAmountAssetName();
}
@XmlElement(name = "pricePair")
public String getPricePair() {
return this.orderData.getPricePair();
}
} }

View File

@ -633,9 +633,9 @@ public class AssetsResource {
} }
@GET @GET
@Path("/transfers/{assetid}/{address}") @Path("/transfers/{assetid}")
@Operation( @Operation(
summary = "Asset transfers for specific asset and address combination", summary = "Asset transfers for specific asset, with optional address filter",
responses = { responses = {
@ApiResponse( @ApiResponse(
description = "Asset transactions", description = "Asset transactions",
@ -654,7 +654,7 @@ public class AssetsResource {
}) })
public List<TransferAssetTransactionData> getAssetTransfers(@Parameter( public List<TransferAssetTransactionData> getAssetTransfers(@Parameter(
ref = "assetid" ref = "assetid"
) @PathParam("assetid") int assetId, @PathParam("address") String address, @Parameter( ) @PathParam("assetid") int assetId, @QueryParam("address") String address, @Parameter(
ref = "limit" ref = "limit"
) @QueryParam("limit") Integer limit, @Parameter( ) @QueryParam("limit") Integer limit, @Parameter(
ref = "offset" ref = "offset"
@ -665,7 +665,7 @@ public class AssetsResource {
if (!repository.getAssetRepository().assetExists(assetId)) if (!repository.getAssetRepository().assetExists(assetId))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID); throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
if (!Crypto.isValidAddress(address)) if (address != null && !Crypto.isValidAddress(address))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
return repository.getTransactionRepository().getAssetTransfers(assetId, address, limit, offset, reverse); return repository.getTransactionRepository().getAssetTransfers(assetId, address, limit, offset, reverse);

View File

@ -2,12 +2,16 @@ package org.qora.data.asset;
import java.math.BigDecimal; import java.math.BigDecimal;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import io.swagger.v3.oas.annotations.media.Schema; import org.qora.crypto.Crypto;
// All properties to be converted to JSON via JAX-RS import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
// All properties to be converted to JSON via JAXB
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
public class OrderData implements Comparable<OrderData> { public class OrderData implements Comparable<OrderData> {
@ -38,13 +42,59 @@ public class OrderData implements Comparable<OrderData> {
@Schema(description = "has this order been fully traded?") @Schema(description = "has this order been fully traded?")
private boolean isFulfilled; private boolean isFulfilled;
// Used by API - not always present
@Schema(accessMode = AccessMode.READ_ONLY)
private String creator;
@Schema(accessMode = AccessMode.READ_ONLY)
private String haveAssetName;
@Schema(accessMode = AccessMode.READ_ONLY)
private String wantAssetName;
@Schema(accessMode = AccessMode.READ_ONLY)
private long amountAssetId;
@Schema(accessMode = AccessMode.READ_ONLY)
private String amountAssetName;
@Schema(accessMode = AccessMode.READ_ONLY)
private String pricePair;
// Constructors // Constructors
// necessary for JAX-RS serialization // Necessary for JAXB serialization
protected OrderData() { protected OrderData() {
} }
public OrderData(byte[] orderId, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal fulfilled, BigDecimal price, long timestamp, boolean isClosed, boolean isFulfilled) { // Called before converting to JSON for API
public void beforeMarshal(Marshaller m) {
if (this.creator == null && this.creatorPublicKey != null)
this.creator = Crypto.toAddress(this.creatorPublicKey);
// If we don't have the extra asset name fields then we can't fill in the others
if (this.haveAssetName == null)
return;
// 'old' pricing scheme is simpler so test for that first
// XXX TODO
// 'new' pricing scheme
if (this.haveAssetId < this.wantAssetId) {
this.amountAssetId = this.wantAssetId;
this.amountAssetName = this.wantAssetName;
this.pricePair = this.haveAssetName + "/" + this.wantAssetName;
} else {
this.amountAssetId = this.haveAssetId;
this.amountAssetName = this.haveAssetName;
this.pricePair = this.wantAssetName + "/" + this.haveAssetName;
}
}
/** Constructs OrderData using data from repository, including optional API fields. */
public OrderData(byte[] orderId, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal fulfilled, BigDecimal price, long timestamp,
boolean isClosed, boolean isFulfilled, String haveAssetName, String wantAssetName) {
this.orderId = orderId; this.orderId = orderId;
this.creatorPublicKey = creatorPublicKey; this.creatorPublicKey = creatorPublicKey;
this.haveAssetId = haveAssetId; this.haveAssetId = haveAssetId;
@ -55,9 +105,17 @@ public class OrderData implements Comparable<OrderData> {
this.timestamp = timestamp; this.timestamp = timestamp;
this.isClosed = isClosed; this.isClosed = isClosed;
this.isFulfilled = isFulfilled; this.isFulfilled = isFulfilled;
this.haveAssetName = haveAssetName;
this.wantAssetName = wantAssetName;
} }
/** Constructs OrderData using typical deserialized network data */ /** Constructs OrderData using data from repository, excluding optional API fields. */
public OrderData(byte[] orderId, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal fulfilled, BigDecimal price, long timestamp, boolean isClosed, boolean isFulfilled) {
this(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, fulfilled, price, timestamp, isClosed, isFulfilled, null, null);
}
/** Constructs OrderData using data typically received from network. */
public OrderData(byte[] orderId, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal price, long timestamp) { public OrderData(byte[] orderId, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal price, long timestamp) {
this(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, BigDecimal.ZERO.setScale(8), price, timestamp, false, false); this(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, BigDecimal.ZERO.setScale(8), price, timestamp, false, false);
} }
@ -116,6 +174,28 @@ public class OrderData implements Comparable<OrderData> {
this.isFulfilled = isFulfilled; this.isFulfilled = isFulfilled;
} }
// Some JAXB/API-related getters
public String getHaveAssetName() {
return this.haveAssetName;
}
public String getWantAssetName() {
return this.wantAssetName;
}
public long getAmountAssetId() {
return this.amountAssetId;
}
public String getAmountAssetName() {
return this.amountAssetName;
}
public String getPricePair() {
return this.pricePair;
}
@Override @Override
public int compareTo(OrderData orderData) { public int compareTo(OrderData orderData) {
// Compare using prices // Compare using prices

View File

@ -2,13 +2,16 @@ package org.qora.data.transaction;
import java.math.BigDecimal; import java.math.BigDecimal;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import org.qora.block.BlockChain;
import org.qora.transaction.Transaction.TransactionType; import org.qora.transaction.Transaction.TransactionType;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
// All properties to be converted to JSON via JAXB // All properties to be converted to JSON via JAXB
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
@ -25,6 +28,23 @@ public class CreateAssetOrderTransactionData extends TransactionData {
@Schema(description = "price in lowest-assetID asset / highest-assetID asset") @Schema(description = "price in lowest-assetID asset / highest-assetID asset")
private BigDecimal price; private BigDecimal price;
// Used by API - not always present
@Schema(accessMode = AccessMode.READ_ONLY)
private String haveAssetName;
@Schema(accessMode = AccessMode.READ_ONLY)
private String wantAssetName;
@Schema(accessMode = AccessMode.READ_ONLY)
private long amountAssetId;
@Schema(accessMode = AccessMode.READ_ONLY)
private String amountAssetName;
@Schema(accessMode = AccessMode.READ_ONLY)
private String pricePair;
// Constructors // Constructors
// For JAXB // For JAXB
@ -32,16 +52,53 @@ public class CreateAssetOrderTransactionData extends TransactionData {
super(TransactionType.CREATE_ASSET_ORDER); super(TransactionType.CREATE_ASSET_ORDER);
} }
// Called before converting to JSON for API
public void beforeMarshal(Marshaller m) {
final boolean isNewPricing = this.timestamp > BlockChain.getInstance().getNewAssetPricingTimestamp();
this.amountAssetId = (isNewPricing && this.haveAssetId < this.wantAssetId) ? this.wantAssetId : this.haveAssetId;
// If we don't have the extra asset name fields then we can't fill in the others
if (this.haveAssetName == null)
return;
if (isNewPricing) {
// 'new' pricing scheme
if (this.haveAssetId < this.wantAssetId) {
this.amountAssetName = this.wantAssetName;
this.pricePair = this.haveAssetName + "/" + this.wantAssetName;
} else {
this.amountAssetName = this.haveAssetName;
this.pricePair = this.wantAssetName + "/" + this.haveAssetName;
}
} else {
// 'old' pricing scheme is simpler
this.amountAssetName = this.haveAssetName;
this.pricePair = this.wantAssetName + "/" + this.haveAssetName;
}
}
/** Constructs using data from repository, including optional asset names. */
public CreateAssetOrderTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, public CreateAssetOrderTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, long haveAssetId, long wantAssetId,
BigDecimal amount, BigDecimal price, BigDecimal fee, byte[] signature) { BigDecimal amount, BigDecimal price, BigDecimal fee, String haveAssetName, String wantAssetName, byte[] signature) {
super(TransactionType.CREATE_ASSET_ORDER, timestamp, txGroupId, reference, creatorPublicKey, fee, signature); super(TransactionType.CREATE_ASSET_ORDER, timestamp, txGroupId, reference, creatorPublicKey, fee, signature);
this.haveAssetId = haveAssetId; this.haveAssetId = haveAssetId;
this.wantAssetId = wantAssetId; this.wantAssetId = wantAssetId;
this.amount = amount; this.amount = amount;
this.price = price; this.price = price;
this.haveAssetName = haveAssetName;
this.wantAssetName = wantAssetName;
} }
/** Constructs using data from repository, excluding optional asset names. */
public CreateAssetOrderTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, long haveAssetId, long wantAssetId,
BigDecimal amount, BigDecimal price, BigDecimal fee, byte[] signature) {
this(timestamp, txGroupId, reference, creatorPublicKey, haveAssetId, wantAssetId, amount, price, fee, null, null, signature);
}
/** Constructor typically used with data from network. */
public CreateAssetOrderTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, public CreateAssetOrderTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, long haveAssetId, long wantAssetId,
BigDecimal amount, BigDecimal price, BigDecimal fee) { BigDecimal amount, BigDecimal price, BigDecimal fee) {
this(timestamp, txGroupId, reference, creatorPublicKey, haveAssetId, wantAssetId, amount, price, fee, null); this(timestamp, txGroupId, reference, creatorPublicKey, haveAssetId, wantAssetId, amount, price, fee, null);

View File

@ -9,6 +9,7 @@ import javax.xml.bind.annotation.XmlAccessorType;
import org.qora.transaction.Transaction.TransactionType; import org.qora.transaction.Transaction.TransactionType;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
// All properties to be converted to JSON via JAXB // All properties to be converted to JSON via JAXB
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
@ -21,6 +22,10 @@ public class TransferAssetTransactionData extends TransactionData {
private BigDecimal amount; private BigDecimal amount;
private long assetId; private long assetId;
// Used by API - not always present
@Schema(accessMode = AccessMode.READ_ONLY)
protected String assetName;
// Constructors // Constructors
// For JAXB // For JAXB
@ -32,16 +37,25 @@ public class TransferAssetTransactionData extends TransactionData {
this.creatorPublicKey = this.senderPublicKey; this.creatorPublicKey = this.senderPublicKey;
} }
/** Constructs using data from repository, including optional assetName. */
public TransferAssetTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] senderPublicKey, String recipient, BigDecimal amount, public TransferAssetTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] senderPublicKey, String recipient, BigDecimal amount,
long assetId, BigDecimal fee, byte[] signature) { long assetId, BigDecimal fee, String assetName, byte[] signature) {
super(TransactionType.TRANSFER_ASSET, timestamp, txGroupId, reference, senderPublicKey, fee, signature); super(TransactionType.TRANSFER_ASSET, timestamp, txGroupId, reference, senderPublicKey, fee, signature);
this.senderPublicKey = senderPublicKey; this.senderPublicKey = senderPublicKey;
this.recipient = recipient; this.recipient = recipient;
this.amount = amount; this.amount = amount;
this.assetId = assetId; this.assetId = assetId;
this.assetName = assetName;
} }
/** Constructs using data from repository, excluding optional assetName. */
public TransferAssetTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] senderPublicKey, String recipient, BigDecimal amount,
long assetId, BigDecimal fee, byte[] signature) {
this(timestamp, txGroupId, reference, senderPublicKey, recipient, amount, assetId, fee, null, signature);
}
/** Constructs using data typically received over network. */
public TransferAssetTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] senderPublicKey, String recipient, BigDecimal amount, public TransferAssetTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] senderPublicKey, String recipient, BigDecimal amount,
long assetId, BigDecimal fee) { long assetId, BigDecimal fee) {
this(timestamp, txGroupId, reference, senderPublicKey, recipient, amount, assetId, fee, null); this(timestamp, txGroupId, reference, senderPublicKey, recipient, amount, assetId, fee, null);

View File

@ -63,7 +63,7 @@ public interface TransactionRepository {
throws DataException; throws DataException;
/** /**
* Returns list of TRANSFER_ASSET transactions relating to specific asset ID/address combination. * Returns list of TRANSFER_ASSET transactions relating to specific asset ID, with optional address filter.
* *
* @param assetId * @param assetId
* @param address * @param address

View File

@ -6,6 +6,7 @@ import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -191,9 +192,13 @@ public class HSQLDBAssetRepository implements AssetRepository {
@Override @Override
public OrderData fromOrderId(byte[] orderId) throws DataException { public OrderData fromOrderId(byte[] orderId) throws DataException {
try (ResultSet resultSet = this.repository.checkedExecute( String sql = "SELECT creator, have_asset_id, want_asset_id, amount, fulfilled, price, ordered, is_closed, is_fulfilled, HaveAsset.asset_name, WantAsset.asset_name "
"SELECT creator, have_asset_id, want_asset_id, amount, fulfilled, price, ordered, is_closed, is_fulfilled FROM AssetOrders WHERE asset_order_id = ?", + "FROM AssetOrders "
orderId)) { + "JOIN Assets AS HaveAsset ON HaveAsset.asset_id = have_asset_id "
+ "JOIN Assets AS WantAsset ON WantAsset.asset_id = want_asset_id "
+ "WHERE asset_order_id = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, orderId)) {
if (resultSet == null) if (resultSet == null)
return null; return null;
@ -206,8 +211,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
long timestamp = resultSet.getTimestamp(7, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); long timestamp = resultSet.getTimestamp(7, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
boolean isClosed = resultSet.getBoolean(8); boolean isClosed = resultSet.getBoolean(8);
boolean isFulfilled = resultSet.getBoolean(9); boolean isFulfilled = resultSet.getBoolean(9);
String haveAssetName = resultSet.getString(10);
String wantAssetName = resultSet.getString(11);
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, haveAssetName, wantAssetName);
} 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);
} }
@ -216,16 +223,30 @@ public class HSQLDBAssetRepository implements AssetRepository {
@Override @Override
public List<OrderData> getOpenOrders(long haveAssetId, long wantAssetId, Integer limit, Integer offset, public List<OrderData> getOpenOrders(long haveAssetId, long wantAssetId, Integer limit, Integer offset,
Boolean reverse) throws DataException { Boolean reverse) throws DataException {
String sql = "SELECT creator, asset_order_id, amount, fulfilled, price, ordered FROM AssetOrders " List<OrderData> orders = new ArrayList<OrderData>();
+ "WHERE have_asset_id = ? AND want_asset_id = ? AND is_closed = FALSE AND is_fulfilled = FALSE ORDER BY price";
// Cache have & want asset names for later use, which also saves a table join
AssetData haveAssetData = this.fromAssetId(haveAssetId);
if (haveAssetData == null)
return orders;
AssetData wantAssetData = this.fromAssetId(wantAssetId);
if (wantAssetData == null)
return orders;
String sql = "SELECT creator, asset_order_id, amount, fulfilled, price, ordered "
+ "FROM AssetOrders "
+ "WHERE have_asset_id = ? AND want_asset_id = ? AND NOT is_closed AND NOT is_fulfilled ";
sql += "ORDER BY price";
if (reverse != null && reverse) if (reverse != null && reverse)
sql += " DESC"; sql += " DESC";
sql += ", ordered"; sql += ", ordered";
if (reverse != null && reverse) if (reverse != null && reverse)
sql += " DESC"; sql += " DESC";
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
List<OrderData> orders = new ArrayList<OrderData>(); sql += HSQLDBRepository.limitOffsetSql(limit, offset);
try (ResultSet resultSet = this.repository.checkedExecute(sql, haveAssetId, wantAssetId)) { try (ResultSet resultSet = this.repository.checkedExecute(sql, haveAssetId, wantAssetId)) {
if (resultSet == null) if (resultSet == null)
@ -242,7 +263,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
boolean isFulfilled = false; boolean isFulfilled = false;
OrderData order = new OrderData(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, fulfilled, OrderData order = new OrderData(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, fulfilled,
price, timestamp, isClosed, isFulfilled); price, timestamp, isClosed, isFulfilled, haveAssetData.getName(), wantAssetData.getName());
orders.add(order); orders.add(order);
} while (resultSet.next()); } while (resultSet.next());
@ -254,22 +275,24 @@ public class HSQLDBAssetRepository implements AssetRepository {
@Override @Override
public List<OrderData> getOpenOrdersForTrading(long haveAssetId, long wantAssetId, BigDecimal minimumPrice) throws DataException { public List<OrderData> getOpenOrdersForTrading(long haveAssetId, long wantAssetId, BigDecimal minimumPrice) throws DataException {
Object[] bindParams; List<Object> bindParams = new ArrayList<>(3);
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 NOT is_closed AND NOT is_fulfilled "; + "WHERE have_asset_id = ? AND want_asset_id = ? AND NOT is_closed AND NOT is_fulfilled ";
Collections.addAll(bindParams, haveAssetId, wantAssetId);
if (minimumPrice != null) { if (minimumPrice != null) {
sql += "AND price >= ? "; sql += "AND price >= ? ";
bindParams = new Object[] {haveAssetId, wantAssetId, minimumPrice}; bindParams.add(minimumPrice);
} else {
bindParams = new Object[] {haveAssetId, wantAssetId};
} }
sql += "ORDER BY price DESC, ordered"; sql += "ORDER BY price DESC, ordered";
List<OrderData> orders = new ArrayList<OrderData>(); List<OrderData> orders = new ArrayList<OrderData>();
try (ResultSet resultSet = this.repository.checkedExecute(sql, bindParams)) { try (ResultSet resultSet = this.repository.checkedExecute(sql, bindParams.toArray())) {
if (resultSet == null) if (resultSet == null)
return orders; return orders;
@ -283,6 +306,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
boolean isClosed = false; boolean isClosed = false;
boolean isFulfilled = false; boolean isFulfilled = false;
// We don't need asset names so we can use simpler constructor
OrderData order = new OrderData(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, fulfilled, OrderData order = new OrderData(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, fulfilled,
price, timestamp, isClosed, isFulfilled); price, timestamp, isClosed, isFulfilled);
orders.add(order); orders.add(order);
@ -297,13 +321,27 @@ public class HSQLDBAssetRepository implements AssetRepository {
@Override @Override
public List<OrderData> getAggregatedOpenOrders(long haveAssetId, long wantAssetId, Integer limit, Integer offset, public List<OrderData> getAggregatedOpenOrders(long haveAssetId, long wantAssetId, Integer limit, Integer offset,
Boolean reverse) throws DataException { Boolean reverse) throws DataException {
String sql = "SELECT price, SUM(amount - fulfilled), MAX(ordered) FROM AssetOrders " List<OrderData> orders = new ArrayList<OrderData>();
+ "WHERE have_asset_id = ? AND want_asset_id = ? AND is_closed = FALSE AND is_fulfilled = FALSE GROUP BY price ORDER BY price";
// Cache have & want asset names for later use, which also saves a table join
AssetData haveAssetData = this.fromAssetId(haveAssetId);
if (haveAssetData == null)
return orders;
AssetData wantAssetData = this.fromAssetId(wantAssetId);
if (wantAssetData == null)
return orders;
String sql = "SELECT price, SUM(amount - fulfilled), MAX(ordered) "
+ "FROM AssetOrders "
+ "WHERE have_asset_id = ? AND want_asset_id = ? AND NOT is_closed AND NOT is_fulfilled "
+ "GROUP BY price ";
sql += "ORDER BY price";
if (reverse != null && reverse) if (reverse != null && reverse)
sql += " DESC"; sql += " DESC";
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
List<OrderData> orders = new ArrayList<OrderData>(); sql += HSQLDBRepository.limitOffsetSql(limit, offset);
try (ResultSet resultSet = this.repository.checkedExecute(sql, haveAssetId, wantAssetId)) { try (ResultSet resultSet = this.repository.checkedExecute(sql, haveAssetId, wantAssetId)) {
if (resultSet == null) if (resultSet == null)
@ -315,7 +353,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
long timestamp = resultSet.getTimestamp(3).getTime(); long timestamp = resultSet.getTimestamp(3).getTime();
OrderData order = new OrderData(null, null, haveAssetId, wantAssetId, totalUnfulfilled, BigDecimal.ZERO, OrderData order = new OrderData(null, null, haveAssetId, wantAssetId, totalUnfulfilled, BigDecimal.ZERO,
price, timestamp, false, false); price, timestamp, false, false, haveAssetData.getName(), wantAssetData.getName());
orders.add(order); orders.add(order);
} while (resultSet.next()); } while (resultSet.next());
@ -328,15 +366,23 @@ public class HSQLDBAssetRepository implements AssetRepository {
@Override @Override
public List<OrderData> getAccountsOrders(byte[] publicKey, Boolean optIsClosed, Boolean optIsFulfilled, public List<OrderData> getAccountsOrders(byte[] publicKey, Boolean optIsClosed, Boolean optIsFulfilled,
Integer limit, Integer offset, Boolean reverse) 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 " // We have to join for have/want asset data as it might vary
+ "FROM AssetOrders WHERE creator = ?"; String sql = "SELECT asset_order_id, have_asset_id, want_asset_id, amount, fulfilled, price, ordered, is_closed, is_fulfilled, HaveAsset.asset_name, WantAsset.asset_name "
+ "FROM AssetOrders "
+ "JOIN Assets AS HaveAsset ON HaveAsset.asset_id = have_asset_id "
+ "JOIN Assets AS WantAsset ON WantAsset.asset_id = want_asset_id "
+ "WHERE creator = ?";
if (optIsClosed != null) if (optIsClosed != null)
sql += " AND is_closed = " + (optIsClosed ? "TRUE" : "FALSE"); sql += " AND is_closed = " + (optIsClosed ? "TRUE" : "FALSE");
if (optIsFulfilled != null) if (optIsFulfilled != null)
sql += " AND is_fulfilled = " + (optIsFulfilled ? "TRUE" : "FALSE"); sql += " AND is_fulfilled = " + (optIsFulfilled ? "TRUE" : "FALSE");
sql += " ORDER BY ordered"; sql += " ORDER BY ordered";
if (reverse != null && reverse) if (reverse != null && reverse)
sql += " DESC"; sql += " DESC";
sql += HSQLDBRepository.limitOffsetSql(limit, offset); sql += HSQLDBRepository.limitOffsetSql(limit, offset);
List<OrderData> orders = new ArrayList<OrderData>(); List<OrderData> orders = new ArrayList<OrderData>();
@ -355,9 +401,11 @@ public class HSQLDBAssetRepository implements AssetRepository {
long timestamp = resultSet.getTimestamp(7, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); long timestamp = resultSet.getTimestamp(7, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
boolean isClosed = resultSet.getBoolean(8); boolean isClosed = resultSet.getBoolean(8);
boolean isFulfilled = resultSet.getBoolean(9); boolean isFulfilled = resultSet.getBoolean(9);
String haveAssetName = resultSet.getString(10);
String wantAssetName = resultSet.getString(11);
OrderData order = new OrderData(orderId, publicKey, haveAssetId, wantAssetId, amount, fulfilled, OrderData order = new OrderData(orderId, publicKey, haveAssetId, wantAssetId, amount, fulfilled,
price, timestamp, isClosed, isFulfilled); price, timestamp, isClosed, isFulfilled, haveAssetName, wantAssetName);
orders.add(order); orders.add(order);
} while (resultSet.next()); } while (resultSet.next());
@ -370,18 +418,32 @@ public class HSQLDBAssetRepository implements AssetRepository {
@Override @Override
public List<OrderData> getAccountsOrders(byte[] publicKey, long haveAssetId, long wantAssetId, Boolean optIsClosed, public List<OrderData> getAccountsOrders(byte[] publicKey, long haveAssetId, long wantAssetId, Boolean optIsClosed,
Boolean optIsFulfilled, Integer limit, Integer offset, Boolean reverse) throws DataException { Boolean optIsFulfilled, Integer limit, Integer offset, Boolean reverse) throws DataException {
List<OrderData> orders = new ArrayList<OrderData>();
// Cache have & want asset names for later use, which also saves a table join
AssetData haveAssetData = this.fromAssetId(haveAssetId);
if (haveAssetData == null)
return orders;
AssetData wantAssetData = this.fromAssetId(wantAssetId);
if (wantAssetData == null)
return orders;
String sql = "SELECT asset_order_id, amount, fulfilled, price, ordered, is_closed, is_fulfilled " 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 = ?"; + "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");
if (optIsFulfilled != null) if (optIsFulfilled != null)
sql += " AND is_fulfilled = " + (optIsFulfilled ? "TRUE" : "FALSE"); sql += " AND is_fulfilled = " + (optIsFulfilled ? "TRUE" : "FALSE");
sql += " ORDER BY ordered"; sql += " ORDER BY ordered";
if (reverse != null && reverse) if (reverse != null && reverse)
sql += " DESC"; sql += " DESC";
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
List<OrderData> orders = new ArrayList<OrderData>(); sql += HSQLDBRepository.limitOffsetSql(limit, offset);
try (ResultSet resultSet = this.repository.checkedExecute(sql, publicKey, haveAssetId, wantAssetId)) { try (ResultSet resultSet = this.repository.checkedExecute(sql, publicKey, haveAssetId, wantAssetId)) {
if (resultSet == null) if (resultSet == null)
@ -397,7 +459,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
boolean isFulfilled = resultSet.getBoolean(7); boolean isFulfilled = resultSet.getBoolean(7);
OrderData order = new OrderData(orderId, publicKey, haveAssetId, wantAssetId, amount, fulfilled, OrderData order = new OrderData(orderId, publicKey, haveAssetId, wantAssetId, amount, fulfilled,
price, timestamp, isClosed, isFulfilled); price, timestamp, isClosed, isFulfilled, haveAssetData.getName(), wantAssetData.getName());
orders.add(order); orders.add(order);
} while (resultSet.next()); } while (resultSet.next());

View File

@ -17,8 +17,13 @@ public class HSQLDBCreateAssetOrderTransactionRepository extends HSQLDBTransacti
} }
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 String sql = "SELECT have_asset_id, amount, want_asset_id, price, HaveAsset.asset_name, WantAsset.asset_name "
.checkedExecute("SELECT have_asset_id, amount, want_asset_id, price FROM CreateAssetOrderTransactions WHERE signature = ?", signature)) { + "FROM CreateAssetOrderTransactions "
+ "JOIN Assets AS HaveAsset ON HaveAsset.asset_id = have_asset_id "
+ "JOIN Assets AS WantAsset ON WantAsset.asset_id = want_asset_id "
+ "WHERE signature = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, signature)) {
if (resultSet == null) if (resultSet == null)
return null; return null;
@ -26,8 +31,10 @@ public class HSQLDBCreateAssetOrderTransactionRepository extends HSQLDBTransacti
BigDecimal amount = resultSet.getBigDecimal(2); BigDecimal amount = resultSet.getBigDecimal(2);
long wantAssetId = resultSet.getLong(3); long wantAssetId = resultSet.getLong(3);
BigDecimal price = resultSet.getBigDecimal(4); BigDecimal price = resultSet.getBigDecimal(4);
String haveAssetName = resultSet.getString(5);
String wantAssetName = resultSet.getString(6);
return new CreateAssetOrderTransactionData(timestamp, txGroupId, reference, creatorPublicKey, haveAssetId, wantAssetId, amount, price, fee, signature); return new CreateAssetOrderTransactionData(timestamp, txGroupId, reference, creatorPublicKey, haveAssetId, wantAssetId, amount, price, fee, haveAssetName, wantAssetName, signature);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Unable to fetch create order transaction from repository", e); throw new DataException("Unable to fetch create order transaction from repository", e);
} }

View File

@ -535,18 +535,30 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
@Override @Override
public List<TransferAssetTransactionData> getAssetTransfers(long assetId, String address, Integer limit, Integer offset, Boolean reverse) public List<TransferAssetTransactionData> getAssetTransfers(long assetId, String address, Integer limit, Integer offset, Boolean reverse)
throws DataException { throws DataException {
String sql = "SELECT creation, tx_group_id, reference, fee, signature, sender, recipient, amount FROM TransferAssetTransactions " List<Object> bindParams = new ArrayList<>(3);
+ "JOIN Transactions USING (signature) "
+ "JOIN Accounts ON public_key = sender " String sql = "SELECT creation, tx_group_id, reference, fee, signature, sender, recipient, amount, asset_name "
+ "WHERE asset_id = ? AND ? IN (account, recipient) " + "FROM TransferAssetTransactions JOIN Transactions USING (signature) ";
+ "ORDER by creation ";
if (address != null)
sql += "JOIN Accounts ON public_key = sender ";
sql += "JOIN Assets USING (asset_id) WHERE asset_id = ? ";
bindParams.add(assetId);
if (address != null) {
sql += "AND ? IN (account, recipient) ";
bindParams.add(address);
}
sql += "ORDER by creation ";
sql += (reverse == null || !reverse) ? "ASC" : "DESC"; sql += (reverse == null || !reverse) ? "ASC" : "DESC";
sql += HSQLDBRepository.limitOffsetSql(limit, offset); sql += HSQLDBRepository.limitOffsetSql(limit, offset);
List<TransferAssetTransactionData> assetTransfers = new ArrayList<>(); List<TransferAssetTransactionData> assetTransfers = new ArrayList<>();
try (ResultSet resultSet = this.repository.checkedExecute(sql, assetId, address)) { try (ResultSet resultSet = this.repository.checkedExecute(sql, bindParams.toArray())) {
if (resultSet == null) if (resultSet == null)
return assetTransfers; return assetTransfers;
@ -559,8 +571,9 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
byte[] creatorPublicKey = resultSet.getBytes(6); byte[] creatorPublicKey = resultSet.getBytes(6);
String recipient = resultSet.getString(7); String recipient = resultSet.getString(7);
BigDecimal amount = resultSet.getBigDecimal(8); BigDecimal amount = resultSet.getBigDecimal(8);
String assetName = resultSet.getString(9);
assetTransfers.add(new TransferAssetTransactionData(timestamp, txGroupId, reference, creatorPublicKey, recipient, amount, assetId, fee, signature)); assetTransfers.add(new TransferAssetTransactionData(timestamp, txGroupId, reference, creatorPublicKey, recipient, amount, assetId, fee, assetName, signature));
} while (resultSet.next()); } while (resultSet.next());
return assetTransfers; return assetTransfers;

View File

@ -17,16 +17,18 @@ public class HSQLDBTransferAssetTransactionRepository extends HSQLDBTransactionR
} }
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 String sql = "SELECT recipient, asset_id, amount, asset_name FROM TransferAssetTransactions JOIN Assets USING (asset_id) WHERE signature = ?";
.checkedExecute("SELECT recipient, asset_id, amount FROM TransferAssetTransactions WHERE signature = ?", signature)) {
try (ResultSet resultSet = this.repository.checkedExecute(sql, signature)) {
if (resultSet == null) if (resultSet == null)
return null; return null;
String recipient = resultSet.getString(1); String recipient = resultSet.getString(1);
long assetId = resultSet.getLong(2); long assetId = resultSet.getLong(2);
BigDecimal amount = resultSet.getBigDecimal(3); BigDecimal amount = resultSet.getBigDecimal(3);
String assetName = resultSet.getString(4);
return new TransferAssetTransactionData(timestamp, txGroupId, reference, creatorPublicKey, recipient, amount, assetId, fee, signature); return new TransferAssetTransactionData(timestamp, txGroupId, reference, creatorPublicKey, recipient, amount, assetId, fee, assetName, signature);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Unable to fetch transfer asset transaction from repository", e); throw new DataException("Unable to fetch transfer asset transaction from repository", e);
} }