mirror of
https://github.com/Qortal/qortal.git
synced 2025-05-20 08:26:59 +00:00
Completing work on new asset trading changes
Changed API call GET /assets to NOT return asset "data" fields as they can be huge. If need be, call GET /assets/info to fetch a specific asset's data field. Improve asset trade amount granularity, especially for indivisible assets, under "new" pricing scheme only. Added corresponding tests for granularity adjustments. Fix/unify asset order logging text under "old" and "new" pricing schemes. Change asset order related API data models so that old "price" is now "unitPrice" and add new "return" as in amount of want-asset to receive if have-asset "amount" was fully matched. (Affects OrderData, CreateAssetOrderTransactionData) Some changes to the HSQLDB tables. Don't forget to add "newAssetPricingTimestamp" to your blockchain config's "featureTriggers" map.
This commit is contained in:
parent
60e562566e
commit
26e3adb92b
@ -75,7 +75,7 @@ public class AssetsResource {
|
|||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "List all known assets",
|
summary = "List all known assets (without data field)",
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "asset info",
|
description = "asset info",
|
||||||
@ -100,7 +100,11 @@ public class AssetsResource {
|
|||||||
ref = "reverse"
|
ref = "reverse"
|
||||||
) @QueryParam("reverse") Boolean reverse) {
|
) @QueryParam("reverse") Boolean reverse) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
return repository.getAssetRepository().getAllAssets(limit, offset, reverse);
|
List<AssetData> assets = repository.getAssetRepository().getAllAssets(limit, offset, reverse);
|
||||||
|
|
||||||
|
assets.forEach(asset -> asset.setData(null));
|
||||||
|
|
||||||
|
return assets;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
|
@ -71,14 +71,22 @@ public class Order {
|
|||||||
* @param theirPrice
|
* @param theirPrice
|
||||||
* @return unit price of want asset
|
* @return unit price of want asset
|
||||||
*/
|
*/
|
||||||
public static BigDecimal calculateAmountGranularity(AssetData haveAssetData, AssetData wantAssetData, BigDecimal theirPrice) {
|
public static BigDecimal calculateAmountGranularity(AssetData haveAssetData, AssetData wantAssetData, OrderData theirOrderData) {
|
||||||
// Multiplier to scale BigDecimal.setScale(8) fractional amounts into integers, essentially 1e8
|
// Multiplier to scale BigDecimal fractional amounts into integer domain
|
||||||
BigInteger multiplier = BigInteger.valueOf(1_0000_0000L);
|
BigInteger multiplier = BigInteger.valueOf(1_0000_0000L);
|
||||||
|
|
||||||
// Calculate the minimum increment at which I can buy using greatest-common-divisor
|
// Calculate the minimum increment at which I can buy using greatest-common-divisor
|
||||||
BigInteger haveAmount = multiplier; // 1 unit (* multiplier)
|
BigInteger haveAmount;
|
||||||
//BigInteger wantAmount = BigDecimal.valueOf(100_000_000L).setScale(Asset.BD_SCALE).divide(theirOrderData.getUnitPrice(), RoundingMode.DOWN).toBigInteger();
|
BigInteger wantAmount;
|
||||||
BigInteger wantAmount = theirPrice.movePointRight(8).toBigInteger();
|
if (theirOrderData.getTimestamp() >= BlockChain.getInstance().getNewAssetPricingTimestamp()) {
|
||||||
|
// "new" pricing scheme
|
||||||
|
haveAmount = theirOrderData.getAmount().movePointRight(8).toBigInteger();
|
||||||
|
wantAmount = theirOrderData.getWantAmount().movePointRight(8).toBigInteger();
|
||||||
|
} else {
|
||||||
|
// legacy "old" behaviour
|
||||||
|
haveAmount = multiplier; // 1 unit (* multiplier)
|
||||||
|
wantAmount = theirOrderData.getUnitPrice().movePointRight(8).toBigInteger();
|
||||||
|
}
|
||||||
|
|
||||||
BigInteger gcd = haveAmount.gcd(wantAmount);
|
BigInteger gcd = haveAmount.gcd(wantAmount);
|
||||||
haveAmount = haveAmount.divide(gcd);
|
haveAmount = haveAmount.divide(gcd);
|
||||||
@ -115,7 +123,6 @@ public class Order {
|
|||||||
if (LOGGER.getLevel().isMoreSpecificThan(Level.DEBUG))
|
if (LOGGER.getLevel().isMoreSpecificThan(Level.DEBUG))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
final boolean isNewPricing = orderData.getTimestamp() >= BlockChain.getInstance().getNewAssetPricingTimestamp();
|
|
||||||
final String weThey = isMatchingNotInitial ? "They" : "We";
|
final String weThey = isMatchingNotInitial ? "They" : "We";
|
||||||
|
|
||||||
AssetData haveAssetData = this.repository.getAssetRepository().fromAssetId(orderData.getHaveAssetId());
|
AssetData haveAssetData = this.repository.getAssetRepository().fromAssetId(orderData.getHaveAssetId());
|
||||||
@ -125,16 +132,10 @@ public class Order {
|
|||||||
|
|
||||||
LOGGER.trace(String.format("%s have: %s %s", weThey, orderData.getAmount().stripTrailingZeros().toPlainString(), haveAssetData.getName()));
|
LOGGER.trace(String.format("%s have: %s %s", weThey, orderData.getAmount().stripTrailingZeros().toPlainString(), haveAssetData.getName()));
|
||||||
|
|
||||||
if (isNewPricing) {
|
|
||||||
LOGGER.trace(String.format("%s want: %s %s (@ %s %s each)", weThey,
|
|
||||||
orderData.getWantAmount().stripTrailingZeros().toPlainString(), wantAssetData.getName(),
|
|
||||||
orderData.getUnitPrice().toPlainString(), haveAssetData.getName()));
|
|
||||||
} else {
|
|
||||||
LOGGER.trace(String.format("%s want at least %s %s per %s (minimum %s %s total)", weThey,
|
LOGGER.trace(String.format("%s want at least %s %s per %s (minimum %s %s total)", weThey,
|
||||||
orderData.getUnitPrice().toPlainString(), wantAssetData.getName(), haveAssetData.getName(),
|
orderData.getUnitPrice().toPlainString(), wantAssetData.getName(), haveAssetData.getName(),
|
||||||
orderData.getWantAmount().stripTrailingZeros().toPlainString(), wantAssetData.getName()));
|
orderData.getWantAmount().stripTrailingZeros().toPlainString(), wantAssetData.getName()));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void process() throws DataException {
|
public void process() throws DataException {
|
||||||
AssetRepository assetRepository = this.repository.getAssetRepository();
|
AssetRepository assetRepository = this.repository.getAssetRepository();
|
||||||
@ -254,18 +255,13 @@ public class Order {
|
|||||||
if (matchedWantAmount.compareTo(BigDecimal.ZERO) <= 0)
|
if (matchedWantAmount.compareTo(BigDecimal.ZERO) <= 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// We can skip granularity if theirWantAmountLeft is an [integer] multiple of matchedWantAmount as that obviously fits
|
// Calculate want-amount granularity, based on price and both assets' divisibility, so that have-amount traded is a valid amount (integer or to 8 d.p.)
|
||||||
if (!isTheirOrderNewAssetPricing || theirWantAmountLeft.remainder(matchedWantAmount).compareTo(BigDecimal.ZERO) > 0) {
|
BigDecimal wantGranularity = calculateAmountGranularity(haveAssetData, wantAssetData, theirOrderData);
|
||||||
// Not an integer multiple so do granularity check
|
|
||||||
|
|
||||||
// Calculate amount granularity based on both assets' divisibility
|
|
||||||
BigDecimal wantGranularity = calculateAmountGranularity(haveAssetData, wantAssetData, theirOrderData.getUnitPrice());
|
|
||||||
LOGGER.trace("wantGranularity (want-asset amount granularity): " + wantGranularity.stripTrailingZeros().toPlainString() + " " + wantAssetData.getName());
|
LOGGER.trace("wantGranularity (want-asset amount granularity): " + wantGranularity.stripTrailingZeros().toPlainString() + " " + wantAssetData.getName());
|
||||||
|
|
||||||
// Reduce matched amount (if need be) to fit granularity
|
// Reduce matched amount (if need be) to fit granularity
|
||||||
matchedWantAmount = matchedWantAmount.subtract(matchedWantAmount.remainder(wantGranularity));
|
matchedWantAmount = matchedWantAmount.subtract(matchedWantAmount.remainder(wantGranularity));
|
||||||
LOGGER.trace("matchedWantAmount adjusted for granularity: " + matchedWantAmount.stripTrailingZeros().toPlainString() + " " + wantAssetData.getName());
|
LOGGER.trace("matchedWantAmount adjusted for granularity: " + matchedWantAmount.stripTrailingZeros().toPlainString() + " " + wantAssetData.getName());
|
||||||
}
|
|
||||||
|
|
||||||
// If we can't buy anything then try another order
|
// If we can't buy anything then try another order
|
||||||
if (matchedWantAmount.compareTo(BigDecimal.ZERO) <= 0)
|
if (matchedWantAmount.compareTo(BigDecimal.ZERO) <= 0)
|
||||||
|
@ -4,6 +4,7 @@ import java.math.BigDecimal;
|
|||||||
|
|
||||||
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 io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
@ -24,7 +25,8 @@ public class OrderData implements Comparable<OrderData> {
|
|||||||
@Schema(description = "amount of \"have\" asset to trade")
|
@Schema(description = "amount of \"have\" asset to trade")
|
||||||
private BigDecimal amount;
|
private BigDecimal amount;
|
||||||
|
|
||||||
@Schema(description = "amount of \"want\" asset to receive")
|
@Schema(name = "return", description = "amount of \"want\" asset to receive")
|
||||||
|
@XmlElement(name = "return")
|
||||||
private BigDecimal wantAmount;
|
private BigDecimal wantAmount;
|
||||||
|
|
||||||
@Schema(description = "amount of \"want\" asset to receive per unit of \"have\" asset traded")
|
@Schema(description = "amount of \"want\" asset to receive per unit of \"have\" asset traded")
|
||||||
|
@ -22,8 +22,9 @@ public class CreateAssetOrderTransactionData extends TransactionData {
|
|||||||
private long wantAssetId;
|
private long wantAssetId;
|
||||||
@Schema(description = "amount of \"have\" asset to trade")
|
@Schema(description = "amount of \"have\" asset to trade")
|
||||||
private BigDecimal amount;
|
private BigDecimal amount;
|
||||||
@Schema(description = "amount of \"want\" asset to receive")
|
@Schema(name = "return", description = "amount of \"want\" asset to receive")
|
||||||
private BigDecimal price;
|
@XmlElement(name = "return")
|
||||||
|
private BigDecimal wantAmount;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
@ -33,18 +34,18 @@ public class CreateAssetOrderTransactionData extends TransactionData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 wantAmount, BigDecimal fee, 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.wantAmount = wantAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 wantAmount, BigDecimal fee) {
|
||||||
this(timestamp, txGroupId, reference, creatorPublicKey, haveAssetId, wantAssetId, amount, price, fee, null);
|
this(timestamp, txGroupId, reference, creatorPublicKey, haveAssetId, wantAssetId, amount, wantAmount, fee, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters/Setters
|
// Getters/Setters
|
||||||
@ -61,8 +62,8 @@ public class CreateAssetOrderTransactionData extends TransactionData {
|
|||||||
return this.amount;
|
return this.amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getPrice() {
|
public BigDecimal getWantAmount() {
|
||||||
return this.price;
|
return this.wantAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-expose creatorPublicKey for this transaction type for JAXB
|
// Re-expose creatorPublicKey for this transaction type for JAXB
|
||||||
|
@ -651,8 +651,8 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
stmt.execute("UPDATE AssetOrders set want_amount = amount * unit_price");
|
stmt.execute("UPDATE AssetOrders set want_amount = amount * unit_price");
|
||||||
// want-amounts all set, so disallow NULL
|
// want-amounts all set, so disallow NULL
|
||||||
stmt.execute("ALTER TABLE AssetOrders ALTER COLUMN want_amount SET NOT NULL");
|
stmt.execute("ALTER TABLE AssetOrders ALTER COLUMN want_amount SET NOT NULL");
|
||||||
// Convert old "price" into buying unit price
|
// Rename corresponding column in CreateAssetOrderTransactions
|
||||||
stmt.execute("UPDATE AssetOrders set unit_price = 1 / unit_price");
|
stmt.execute("ALTER TABLE CreateAssetOrderTransactions ALTER COLUMN price RENAME TO want_amount");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -18,16 +18,16 @@ 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
|
try (ResultSet resultSet = this.repository
|
||||||
.checkedExecute("SELECT have_asset_id, amount, want_asset_id, price FROM CreateAssetOrderTransactions WHERE signature = ?", signature)) {
|
.checkedExecute("SELECT have_asset_id, amount, want_asset_id, want_amount FROM CreateAssetOrderTransactions WHERE signature = ?", signature)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
long haveAssetId = resultSet.getLong(1);
|
long haveAssetId = resultSet.getLong(1);
|
||||||
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 wantAmount = resultSet.getBigDecimal(4);
|
||||||
|
|
||||||
return new CreateAssetOrderTransactionData(timestamp, txGroupId, reference, creatorPublicKey, haveAssetId, wantAssetId, amount, price, fee, signature);
|
return new CreateAssetOrderTransactionData(timestamp, txGroupId, reference, creatorPublicKey, haveAssetId, wantAssetId, amount, wantAmount, fee, 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);
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ public class HSQLDBCreateAssetOrderTransactionRepository extends HSQLDBTransacti
|
|||||||
|
|
||||||
saveHelper.bind("signature", createOrderTransactionData.getSignature()).bind("creator", createOrderTransactionData.getCreatorPublicKey())
|
saveHelper.bind("signature", createOrderTransactionData.getSignature()).bind("creator", createOrderTransactionData.getCreatorPublicKey())
|
||||||
.bind("have_asset_id", createOrderTransactionData.getHaveAssetId()).bind("amount", createOrderTransactionData.getAmount())
|
.bind("have_asset_id", createOrderTransactionData.getHaveAssetId()).bind("amount", createOrderTransactionData.getAmount())
|
||||||
.bind("want_asset_id", createOrderTransactionData.getWantAssetId()).bind("price", createOrderTransactionData.getPrice());
|
.bind("want_asset_id", createOrderTransactionData.getWantAssetId()).bind("want_amount", createOrderTransactionData.getWantAmount());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saveHelper.execute(this.repository);
|
saveHelper.execute(this.repository);
|
||||||
|
@ -83,7 +83,7 @@ public class CreateAssetOrderTransaction extends Transaction {
|
|||||||
return ValidationResult.NEGATIVE_AMOUNT;
|
return ValidationResult.NEGATIVE_AMOUNT;
|
||||||
|
|
||||||
// Check price is positive
|
// Check price is positive
|
||||||
if (createOrderTransactionData.getPrice().compareTo(BigDecimal.ZERO) <= 0)
|
if (createOrderTransactionData.getWantAmount().compareTo(BigDecimal.ZERO) <= 0)
|
||||||
return ValidationResult.NEGATIVE_PRICE;
|
return ValidationResult.NEGATIVE_PRICE;
|
||||||
|
|
||||||
// Check fee is positive
|
// Check fee is positive
|
||||||
@ -133,12 +133,12 @@ public class CreateAssetOrderTransaction extends Transaction {
|
|||||||
// Check total return from fulfilled order would be integer if "want" asset is not divisible
|
// Check total return from fulfilled order would be integer if "want" asset is not divisible
|
||||||
if (createOrderTransactionData.getTimestamp() >= BlockChain.getInstance().getNewAssetPricingTimestamp()) {
|
if (createOrderTransactionData.getTimestamp() >= BlockChain.getInstance().getNewAssetPricingTimestamp()) {
|
||||||
// "new" asset pricing
|
// "new" asset pricing
|
||||||
if (!wantAssetData.getIsDivisible() && createOrderTransactionData.getPrice().stripTrailingZeros().scale() > 0)
|
if (!wantAssetData.getIsDivisible() && createOrderTransactionData.getWantAmount().stripTrailingZeros().scale() > 0)
|
||||||
return ValidationResult.INVALID_RETURN;
|
return ValidationResult.INVALID_RETURN;
|
||||||
} else {
|
} else {
|
||||||
// "old" asset pricing
|
// "old" asset pricing
|
||||||
if (!wantAssetData.getIsDivisible()
|
if (!wantAssetData.getIsDivisible()
|
||||||
&& createOrderTransactionData.getAmount().multiply(createOrderTransactionData.getPrice()).stripTrailingZeros().scale() > 0)
|
&& createOrderTransactionData.getAmount().multiply(createOrderTransactionData.getWantAmount()).stripTrailingZeros().scale() > 0)
|
||||||
return ValidationResult.INVALID_RETURN;
|
return ValidationResult.INVALID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,12 +166,12 @@ public class CreateAssetOrderTransaction extends Transaction {
|
|||||||
|
|
||||||
if (createOrderTransactionData.getTimestamp() >= BlockChain.getInstance().getNewAssetPricingTimestamp()) {
|
if (createOrderTransactionData.getTimestamp() >= BlockChain.getInstance().getNewAssetPricingTimestamp()) {
|
||||||
// "new" asset pricing: want-amount provided, unit price to be calculated
|
// "new" asset pricing: want-amount provided, unit price to be calculated
|
||||||
wantAmount = createOrderTransactionData.getPrice();
|
wantAmount = createOrderTransactionData.getWantAmount();
|
||||||
unitPrice = wantAmount.setScale(Order.BD_PRICE_STORAGE_SCALE).divide(createOrderTransactionData.getAmount().setScale(Order.BD_PRICE_STORAGE_SCALE), RoundingMode.DOWN);
|
unitPrice = wantAmount.setScale(Order.BD_PRICE_STORAGE_SCALE).divide(createOrderTransactionData.getAmount().setScale(Order.BD_PRICE_STORAGE_SCALE), RoundingMode.DOWN);
|
||||||
} else {
|
} else {
|
||||||
// "old" asset pricing: selling unit price provided, want-amount to be calculated
|
// "old" asset pricing: selling unit price provided, want-amount to be calculated
|
||||||
wantAmount = createOrderTransactionData.getAmount().multiply(createOrderTransactionData.getPrice());
|
wantAmount = createOrderTransactionData.getAmount().multiply(createOrderTransactionData.getWantAmount());
|
||||||
unitPrice = createOrderTransactionData.getPrice();
|
unitPrice = createOrderTransactionData.getWantAmount(); // getWantAmount() was getPrice() in the "old" pricing scheme
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the order itself
|
// Process the order itself
|
||||||
|
@ -88,7 +88,7 @@ public class CreateAssetOrderTransactionTransformer extends TransactionTransform
|
|||||||
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getAmount(), AMOUNT_LENGTH);
|
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getAmount(), AMOUNT_LENGTH);
|
||||||
|
|
||||||
// Under "new" asset pricing, this is actually the want-amount
|
// Under "new" asset pricing, this is actually the want-amount
|
||||||
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getPrice(), AMOUNT_LENGTH);
|
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getWantAmount(), AMOUNT_LENGTH);
|
||||||
|
|
||||||
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getFee());
|
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getFee());
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ public class CreateAssetOrderTransactionTransformer extends TransactionTransform
|
|||||||
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getAmount(), AMOUNT_LENGTH);
|
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getAmount(), AMOUNT_LENGTH);
|
||||||
|
|
||||||
// This is the crucial difference
|
// This is the crucial difference
|
||||||
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getPrice(), FEE_LENGTH);
|
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getWantAmount(), FEE_LENGTH);
|
||||||
|
|
||||||
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getFee());
|
Serialization.serializeBigDecimal(bytes, createOrderTransactionData.getFee());
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ 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.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
import org.qora.transaction.DeployAtTransaction;
|
import org.qora.transaction.DeployAtTransaction;
|
||||||
import org.qora.transform.TransformationException;
|
import org.qora.transform.TransformationException;
|
||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
|
@ -11,6 +11,7 @@ import org.qora.data.transaction.TransactionData;
|
|||||||
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;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
import org.qora.transaction.Transaction;
|
import org.qora.transaction.Transaction;
|
||||||
import org.qora.transform.TransformationException;
|
import org.qora.transform.TransformationException;
|
||||||
import org.qora.transform.block.BlockTransformer;
|
import org.qora.transform.block.BlockTransformer;
|
||||||
|
@ -3,6 +3,7 @@ package org.qora.test;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qora.block.BlockChain;
|
import org.qora.block.BlockChain;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
|
|
||||||
public class BlockchainTests extends Common {
|
public class BlockchainTests extends Common {
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package org.qora.test;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qora.block.BlockChain;
|
import org.qora.block.BlockChain;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import org.qora.data.transaction.TransactionData;
|
|||||||
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;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
import org.qora.transaction.Transaction;
|
import org.qora.transaction.Transaction;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
@ -12,6 +12,7 @@ import org.qora.group.Group.ApprovalThreshold;
|
|||||||
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;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
import org.qora.transaction.CreateGroupTransaction;
|
import org.qora.transaction.CreateGroupTransaction;
|
||||||
import org.qora.transaction.PaymentTransaction;
|
import org.qora.transaction.PaymentTransaction;
|
||||||
import org.qora.transaction.Transaction;
|
import org.qora.transaction.Transaction;
|
||||||
|
@ -8,6 +8,7 @@ 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;
|
||||||
import org.qora.repository.TransactionRepository;
|
import org.qora.repository.TransactionRepository;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ 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;
|
||||||
import org.qora.repository.TransactionRepository;
|
import org.qora.repository.TransactionRepository;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import org.junit.Test;
|
|||||||
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;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ 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.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
|
|
||||||
public class SaveTests extends Common {
|
public class SaveTests extends Common {
|
||||||
|
@ -9,6 +9,7 @@ import org.qora.data.transaction.TransactionData;
|
|||||||
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;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
import org.qora.transaction.GenesisTransaction;
|
import org.qora.transaction.GenesisTransaction;
|
||||||
import org.qora.transaction.Transaction;
|
import org.qora.transaction.Transaction;
|
||||||
import org.qora.transaction.Transaction.TransactionType;
|
import org.qora.transaction.Transaction.TransactionType;
|
||||||
|
@ -8,6 +8,7 @@ import org.qora.data.block.BlockData;
|
|||||||
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;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
import org.qora.utils.Base58;
|
import org.qora.utils.Base58;
|
||||||
import org.qora.utils.NTP;
|
import org.qora.utils.NTP;
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ import org.qora.repository.AssetRepository;
|
|||||||
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;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
import org.qora.transaction.BuyNameTransaction;
|
import org.qora.transaction.BuyNameTransaction;
|
||||||
import org.qora.transaction.CancelAssetOrderTransaction;
|
import org.qora.transaction.CancelAssetOrderTransaction;
|
||||||
import org.qora.transaction.CancelSellNameTransaction;
|
import org.qora.transaction.CancelSellNameTransaction;
|
||||||
|
@ -4,14 +4,16 @@ import org.junit.After;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qora.asset.Asset;
|
import org.qora.asset.Asset;
|
||||||
|
import org.qora.asset.Order;
|
||||||
|
import org.qora.block.BlockChain;
|
||||||
|
import org.qora.data.asset.AssetData;
|
||||||
|
import org.qora.data.asset.OrderData;
|
||||||
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;
|
||||||
import org.qora.test.Common;
|
|
||||||
import org.qora.test.common.AccountUtils;
|
import org.qora.test.common.AccountUtils;
|
||||||
import org.qora.test.common.AssetUtils;
|
import org.qora.test.common.AssetUtils;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -25,6 +27,46 @@ public class TradingTests extends Common {
|
|||||||
|
|
||||||
@After
|
@After
|
||||||
public void afterTest() throws DataException {
|
public void afterTest() throws DataException {
|
||||||
|
Common.orphanCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check granularity adjustment values.
|
||||||
|
* <p>
|
||||||
|
* If trading at a price of 12 eggs for 1 coin
|
||||||
|
* then trades can only happen at multiples of
|
||||||
|
* 0.000000001 or 0.00000012 depending on direction.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDivisibleGranularities() {
|
||||||
|
testGranularity(true, true, "12", "1", "0.00000012");
|
||||||
|
testGranularity(true, true, "1", "12", "0.00000001");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check granularity adjustment values.
|
||||||
|
* <p>
|
||||||
|
* If trading at a price of 123 riches per 50301 rags,
|
||||||
|
* then the GCD(123, 50301) is 3 and so trades can only
|
||||||
|
* happen at multiples of (50301/3) = 16767 rags or
|
||||||
|
* (123/3) = 41 riches.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testIndivisibleGranularities() {
|
||||||
|
testGranularity(false, false, "50301", "123", "16767");
|
||||||
|
testGranularity(false, false, "123", "50301", "41");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testGranularity(boolean isOurHaveDivisible, boolean isOurWantDivisible, String theirHaveAmount, String theirWantAmount, String expectedGranularity) {
|
||||||
|
final long newPricingTimestamp = BlockChain.getInstance().getNewAssetPricingTimestamp() + 1;
|
||||||
|
|
||||||
|
final AssetData ourHaveAssetData = new AssetData(null, null, null, 0, isOurHaveDivisible, null, 0, null);
|
||||||
|
final AssetData ourWantAssetData = new AssetData(null, null, null, 0, isOurWantDivisible, null, 0, null);
|
||||||
|
|
||||||
|
OrderData theirOrderData = new OrderData(null, null, 0, 0, new BigDecimal(theirHaveAmount), new BigDecimal(theirWantAmount), null, newPricingTimestamp);
|
||||||
|
|
||||||
|
BigDecimal granularity = Order.calculateAmountGranularity(ourHaveAssetData, ourWantAssetData, theirOrderData);
|
||||||
|
assertEqualBigDecimals("Granularity incorrect", new BigDecimal(expectedGranularity), granularity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -164,8 +206,7 @@ public class TradingTests extends Common {
|
|||||||
|
|
||||||
BigDecimal expectedFulfilled = asset113Matched2;
|
BigDecimal expectedFulfilled = asset113Matched2;
|
||||||
BigDecimal actualFulfilled = repository.getAssetRepository().fromOrderId(furtherOrderId).getFulfilled();
|
BigDecimal actualFulfilled = repository.getAssetRepository().fromOrderId(furtherOrderId).getFulfilled();
|
||||||
assertTrue(String.format("Order fulfilled incorrect: expected %s, actual %s", expectedFulfilled.toPlainString(), actualFulfilled.toPlainString()),
|
assertEqualBigDecimals("Order fulfilled incorrect", expectedFulfilled, actualFulfilled);
|
||||||
actualFulfilled.compareTo(expectedFulfilled) == 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,8 +435,7 @@ public class TradingTests extends Common {
|
|||||||
private static void assertBalance(Repository repository, String accountName, long assetId, BigDecimal expectedBalance) throws DataException {
|
private static void assertBalance(Repository repository, String accountName, long assetId, BigDecimal expectedBalance) throws DataException {
|
||||||
BigDecimal actualBalance = Common.getTestAccount(repository, accountName).getConfirmedBalance(assetId);
|
BigDecimal actualBalance = Common.getTestAccount(repository, accountName).getConfirmedBalance(assetId);
|
||||||
|
|
||||||
assertTrue(String.format("Test account '%s' asset %d balance incorrect: expected %s, actual %s", accountName, assetId, expectedBalance.toPlainString(), actualBalance.toPlainString()),
|
assertEqualBigDecimals(String.format("Test account '%s' asset %d balance incorrect", accountName, assetId), expectedBalance, actualBalance);
|
||||||
actualBalance.compareTo(expectedBalance) == 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -6,7 +6,6 @@ import java.util.Map;
|
|||||||
|
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.test.Common;
|
|
||||||
|
|
||||||
public class AccountUtils {
|
public class AccountUtils {
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ import org.qora.data.transaction.TransferAssetTransactionData;
|
|||||||
import org.qora.group.Group;
|
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.test.Common;
|
|
||||||
|
|
||||||
public class AssetUtils {
|
public class AssetUtils {
|
||||||
|
|
||||||
@ -26,7 +25,7 @@ public class AssetUtils {
|
|||||||
|
|
||||||
TransactionData transactionData = new IssueAssetTransactionData(timestamp, AssetUtils.txGroupId, reference, account.getPublicKey(), account.getAddress(), assetName, "desc", quantity, isDivisible, "{}", AssetUtils.fee);
|
TransactionData transactionData = new IssueAssetTransactionData(timestamp, AssetUtils.txGroupId, reference, account.getPublicKey(), account.getAddress(), assetName, "desc", quantity, isDivisible, "{}", AssetUtils.fee);
|
||||||
|
|
||||||
Common.signAndForge(repository, transactionData, account);
|
TransactionUtils.signAndForge(repository, transactionData, account);
|
||||||
|
|
||||||
return repository.getAssetRepository().fromAssetName(assetName).getAssetId();
|
return repository.getAssetRepository().fromAssetName(assetName).getAssetId();
|
||||||
}
|
}
|
||||||
@ -40,7 +39,7 @@ public class AssetUtils {
|
|||||||
|
|
||||||
TransactionData transactionData = new TransferAssetTransactionData(timestamp, AssetUtils.txGroupId, reference, fromAccount.getPublicKey(), toAccount.getAddress(), amount, assetId, AssetUtils.fee);
|
TransactionData transactionData = new TransferAssetTransactionData(timestamp, AssetUtils.txGroupId, reference, fromAccount.getPublicKey(), toAccount.getAddress(), amount, assetId, AssetUtils.fee);
|
||||||
|
|
||||||
Common.signAndForge(repository, transactionData, fromAccount);
|
TransactionUtils.signAndForge(repository, transactionData, fromAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] createOrder(Repository repository, String accountName, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal wantAmount) throws DataException {
|
public static byte[] createOrder(Repository repository, String accountName, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal wantAmount) throws DataException {
|
||||||
@ -52,7 +51,7 @@ public class AssetUtils {
|
|||||||
// Note: "price" is not the same in V2 as in V1
|
// Note: "price" is not the same in V2 as in V1
|
||||||
TransactionData transactionData = new CreateAssetOrderTransactionData(timestamp, txGroupId, reference, account.getPublicKey(), haveAssetId, wantAssetId, amount, wantAmount, fee);
|
TransactionData transactionData = new CreateAssetOrderTransactionData(timestamp, txGroupId, reference, account.getPublicKey(), haveAssetId, wantAssetId, amount, wantAmount, fee);
|
||||||
|
|
||||||
Common.signAndForge(repository, transactionData, account);
|
TransactionUtils.signAndForge(repository, transactionData, account);
|
||||||
|
|
||||||
return repository.getAssetRepository().getAccountsOrders(account.getPublicKey(), null, null, null, null, true).get(0).getOrderId();
|
return repository.getAssetRepository().getAccountsOrders(account.getPublicKey(), null, null, null, null, true).get(0).getOrderId();
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package org.qora.test;
|
package org.qora.test.common;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.bitcoinj.core.Base58;
|
import org.bitcoinj.core.Base58;
|
||||||
@ -14,28 +18,33 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|||||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.qora.account.PrivateKeyAccount;
|
import org.qora.block.Block;
|
||||||
import org.qora.api.resource.TransactionsResource.ConfirmationStatus;
|
|
||||||
import org.qora.block.BlockChain;
|
import org.qora.block.BlockChain;
|
||||||
import org.qora.block.BlockGenerator;
|
import org.qora.data.account.AccountBalanceData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.asset.AssetData;
|
||||||
|
import org.qora.data.block.BlockData;
|
||||||
|
import org.qora.data.group.GroupData;
|
||||||
|
import org.qora.repository.AccountRepository.BalanceOrdering;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryFactory;
|
import org.qora.repository.RepositoryFactory;
|
||||||
import org.qora.repository.RepositoryManager;
|
import org.qora.repository.RepositoryManager;
|
||||||
import org.qora.repository.hsqldb.HSQLDBRepositoryFactory;
|
import org.qora.repository.hsqldb.HSQLDBRepositoryFactory;
|
||||||
import org.qora.settings.Settings;
|
import org.qora.settings.Settings;
|
||||||
import org.qora.test.common.TestAccount;
|
|
||||||
import org.qora.transaction.Transaction;
|
|
||||||
import org.qora.transaction.Transaction.ValidationResult;
|
|
||||||
|
|
||||||
public class Common {
|
public class Common {
|
||||||
|
|
||||||
public static final String testConnectionUrl = "jdbc:hsqldb:mem:testdb";
|
public static final String testConnectionUrl = "jdbc:hsqldb:mem:testdb";
|
||||||
|
// For debugging, use this instead to write DB to disk for examination:
|
||||||
// public static final String testConnectionUrl = "jdbc:hsqldb:file:testdb/blockchain;create=true";
|
// public static final String testConnectionUrl = "jdbc:hsqldb:file:testdb/blockchain;create=true";
|
||||||
|
|
||||||
public static final String testSettingsFilename = "test-settings-v2.json";
|
public static final String testSettingsFilename = "test-settings-v2.json";
|
||||||
|
|
||||||
|
private static List<AssetData> initialAssets;
|
||||||
|
private static List<GroupData> initialGroups;
|
||||||
|
private static List<AccountBalanceData> initialBalances;
|
||||||
|
|
||||||
|
// TODO: converts users of these constants to TestAccount schema
|
||||||
public static final byte[] v2testPrivateKey = Base58.decode("A9MNsATgQgruBUjxy2rjWY36Yf19uRioKZbiLFT2P7c6");
|
public static final byte[] v2testPrivateKey = Base58.decode("A9MNsATgQgruBUjxy2rjWY36Yf19uRioKZbiLFT2P7c6");
|
||||||
public static final byte[] v2testPublicKey = Base58.decode("2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP");
|
public static final byte[] v2testPublicKey = Base58.decode("2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP");
|
||||||
public static final String v2testAddress = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v";
|
public static final String v2testAddress = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v";
|
||||||
@ -53,7 +62,6 @@ public class Common {
|
|||||||
Settings.fileInstance(testSettingsUrl.getPath());
|
Settings.fileInstance(testSettingsUrl.getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, TransactionData> lastTransactionByAddress;
|
|
||||||
private static Map<String, TestAccount> testAccountsByName = new HashMap<>();
|
private static Map<String, TestAccount> testAccountsByName = new HashMap<>();
|
||||||
static {
|
static {
|
||||||
testAccountsByName.put("alice", new TestAccount(null, "alice", "A9MNsATgQgruBUjxy2rjWY36Yf19uRioKZbiLFT2P7c6"));
|
testAccountsByName.put("alice", new TestAccount(null, "alice", "A9MNsATgQgruBUjxy2rjWY36Yf19uRioKZbiLFT2P7c6"));
|
||||||
@ -90,35 +98,52 @@ public class Common {
|
|||||||
public static void resetBlockchain() throws DataException {
|
public static void resetBlockchain() throws DataException {
|
||||||
BlockChain.validate();
|
BlockChain.validate();
|
||||||
|
|
||||||
lastTransactionByAddress = new HashMap<>();
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
// Build snapshot of initial state in case we want to compare with post-test orphaning
|
||||||
|
initialAssets = repository.getAssetRepository().getAllAssets();
|
||||||
|
initialGroups = repository.getGroupRepository().getAllGroups();
|
||||||
|
initialBalances = repository.getAccountRepository().getAssetBalances(Collections.emptyList(), Collections.emptyList(), BalanceOrdering.ASSET_ACCOUNT, null, null, null);
|
||||||
|
|
||||||
try (Repository repository = RepositoryManager.getRepository()) {
|
// Check that each test account can fetch their last reference
|
||||||
for (TestAccount account : testAccountsByName.values()) {
|
for (TestAccount testAccount : getTestAccounts(repository))
|
||||||
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, null, account.getAddress(), ConfirmationStatus.BOTH, 1, null, true);
|
assertNotNull(String.format("Test account '%s' should have existing transaction", testAccount.accountName), testAccount.getLastReference());
|
||||||
assertFalse(String.format("Test account '%s' should have existing transaction", account.accountName), signatures.isEmpty());
|
|
||||||
|
|
||||||
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signatures.get(0));
|
|
||||||
lastTransactionByAddress.put(account.getAddress(), transactionData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void signAndForge(Repository repository, TransactionData transactionData, PrivateKeyAccount signingAccount) throws DataException {
|
/** Orphan back to genesis block and compare initial snapshot. */
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
public static void orphanCheck() throws DataException {
|
||||||
transaction.sign(signingAccount);
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
// Orphan back to genesis block
|
||||||
// Add to unconfirmed
|
while (repository.getBlockRepository().getBlockchainHeight() > 1) {
|
||||||
assertTrue("Transaction's signature should be valid", transaction.isSignatureValid());
|
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||||
|
Block block = new Block(repository, blockData);
|
||||||
ValidationResult result = transaction.isValidUnconfirmed();
|
block.orphan();
|
||||||
assertEquals("Transaction invalid", ValidationResult.OK, result);
|
|
||||||
|
|
||||||
repository.getTransactionRepository().save(transactionData);
|
|
||||||
repository.getTransactionRepository().unconfirmTransaction(transactionData);
|
|
||||||
repository.saveChanges();
|
repository.saveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
// Generate block
|
List<AssetData> remainingAssets = repository.getAssetRepository().getAllAssets();
|
||||||
BlockGenerator.generateTestingBlock(repository, signingAccount);
|
checkOrphanedLists("asset", initialAssets, remainingAssets, AssetData::getAssetId);
|
||||||
|
|
||||||
|
List<GroupData> remainingGroups = repository.getGroupRepository().getAllGroups();
|
||||||
|
checkOrphanedLists("group", initialGroups, remainingGroups, GroupData::getGroupId);
|
||||||
|
|
||||||
|
List<AccountBalanceData> remainingBalances = repository.getAccountRepository().getAssetBalances(Collections.emptyList(), Collections.emptyList(), BalanceOrdering.ASSET_ACCOUNT, null, null, null);
|
||||||
|
checkOrphanedLists("account balance", initialBalances, remainingBalances, entry -> entry.getAssetName() + "-" + entry.getAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> void checkOrphanedLists(String typeName, List<T> initial, List<T> remaining, Function<T, ? extends Object> keyExtractor) {
|
||||||
|
Predicate<T> isInitial = entry -> initial.stream().anyMatch(initialEntry -> keyExtractor.apply(initialEntry).equals(keyExtractor.apply(entry)));
|
||||||
|
Predicate<T> isRemaining = entry -> remaining.stream().anyMatch(remainingEntry -> keyExtractor.apply(remainingEntry).equals(keyExtractor.apply(entry)));
|
||||||
|
|
||||||
|
// Check all initial entries remain
|
||||||
|
for (T initialEntry : initial)
|
||||||
|
assertTrue(String.format("Genesis %s %s missing", typeName, keyExtractor.apply(initialEntry)), isRemaining.test(initialEntry));
|
||||||
|
|
||||||
|
// Remove initial entries from remaining to see there are any leftover
|
||||||
|
remaining.removeIf(isInitial);
|
||||||
|
|
||||||
|
assertTrue(String.format("Non-genesis %s remains", typeName), remaining.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
@ -136,4 +161,9 @@ public class Common {
|
|||||||
assertEquals("Blockchain should be empty for this test", 0, repository.getBlockRepository().getBlockchainHeight());
|
assertEquals("Blockchain should be empty for this test", 0, repository.getBlockRepository().getBlockchainHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void assertEqualBigDecimals(String message, BigDecimal expected, BigDecimal actual) {
|
||||||
|
assertTrue(String.format("%s: expected %s, actual %s", message, expected.toPlainString(), actual.toPlainString()),
|
||||||
|
actual.compareTo(expected) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
34
src/test/java/org/qora/test/common/TransactionUtils.java
Normal file
34
src/test/java/org/qora/test/common/TransactionUtils.java
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package org.qora.test.common;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.qora.account.PrivateKeyAccount;
|
||||||
|
import org.qora.block.BlockGenerator;
|
||||||
|
import org.qora.data.transaction.TransactionData;
|
||||||
|
import org.qora.repository.DataException;
|
||||||
|
import org.qora.repository.Repository;
|
||||||
|
import org.qora.transaction.Transaction;
|
||||||
|
import org.qora.transaction.Transaction.ValidationResult;
|
||||||
|
|
||||||
|
public class TransactionUtils {
|
||||||
|
|
||||||
|
public static void signAndForge(Repository repository, TransactionData transactionData, PrivateKeyAccount signingAccount) throws DataException {
|
||||||
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
transaction.sign(signingAccount);
|
||||||
|
|
||||||
|
// Add to unconfirmed
|
||||||
|
assertTrue("Transaction's signature should be valid", transaction.isSignatureValid());
|
||||||
|
|
||||||
|
ValidationResult result = transaction.isValidUnconfirmed();
|
||||||
|
assertEquals("Transaction invalid", ValidationResult.OK, result);
|
||||||
|
|
||||||
|
repository.getTransactionRepository().save(transactionData);
|
||||||
|
repository.getTransactionRepository().unconfirmTransaction(transactionData);
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
// Generate block
|
||||||
|
BlockGenerator.generateTestingBlock(repository, signingAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user