3
0
mirror of https://github.com/Qortal/qortal.git synced 2025-02-14 11:15:49 +00:00

Massive conversion from BigDecimal to long.

Now possible thanks to removing Qora v1 support.

Maximum asset quantities now unified to 10_000_000_000,
to 8 decimal places, removing prior 10 billion billion
indivisible maximum.

All values can now fit into a 64bit long.
(Except maybe when processing asset trades).

Added a general-use JAXB AmountTypeAdapter for converting
amounts to/from String/long.

Asset trading engine split into more methods for easier
readability.

Switched to using FIXED founder block reward distribution code,
ready for launch.

In HSQLDBDatabaseUpdates,
QortalAmount changed from DECIMAL(27, 0) to BIGINT
RewardSharePercent added to replace DECIMAL(5,2) with INT

Ripped out unused Transaction.isInvolved and Transaction.getAmount
in all subclasses.

Changed
  Transaction.getRecipientAccounts() : List<Account>
to
  Transaction.getRecipientAddresses() : List<String>
as only addresses are ever used.

Corrected returned values for above getRecipientAddresses() for
some transaction subclasses.

Added some account caching to some transactions to reduce repeated
loads during validation and then processing.

Transaction transformers:

Changed serialization of asset amounts from using 12 bytes to
now standard 8 byte long.

Updated transaction 'layouts' to reflect new sizes.

RewardShareTransactionTransformer still uses 8byte long to represent
reward share percent.

Updated some unit tests - more work needed!
This commit is contained in:
catbref 2020-04-29 17:30:25 +01:00
parent 0006911e0a
commit 9eaf31707a
150 changed files with 1795 additions and 2767 deletions

View File

@ -1,6 +1,6 @@
package org.qortal.account;
import java.math.BigDecimal;
import static org.qortal.utils.Amounts.prettyAmount;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@ -54,32 +54,24 @@ public class Account {
// Balance manipulations - assetId is 0 for QORT
public BigDecimal getBalance(long assetId) throws DataException {
public long getConfirmedBalance(long assetId) throws DataException {
AccountBalanceData accountBalanceData = this.repository.getAccountRepository().getBalance(this.address, assetId);
if (accountBalanceData == null)
return BigDecimal.ZERO.setScale(8);
return 0;
return accountBalanceData.getBalance();
}
public BigDecimal getConfirmedBalance(long assetId) throws DataException {
AccountBalanceData accountBalanceData = this.repository.getAccountRepository().getBalance(this.address, assetId);
if (accountBalanceData == null)
return BigDecimal.ZERO.setScale(8);
return accountBalanceData.getBalance();
}
public void setConfirmedBalance(long assetId, BigDecimal balance) throws DataException {
public void setConfirmedBalance(long assetId, long balance) throws DataException {
// Safety feature!
if (balance.compareTo(BigDecimal.ZERO) < 0) {
String message = String.format("Refusing to set negative balance %s [assetId %d] for %s", balance.toPlainString(), assetId, this.address);
if (balance < 0) {
String message = String.format("Refusing to set negative balance %s [assetId %d] for %s", prettyAmount(balance), assetId, this.address);
LOGGER.error(message);
throw new DataException(message);
}
// Delete account balance record instead of setting balance to zero
if (balance.signum() == 0) {
if (balance == 0) {
this.repository.getAccountRepository().delete(this.address, assetId);
return;
}
@ -90,7 +82,7 @@ public class Account {
AccountBalanceData accountBalanceData = new AccountBalanceData(this.address, assetId, balance);
this.repository.getAccountRepository().save(accountBalanceData);
LOGGER.trace(() -> String.format("%s balance now %s [assetId %s]", this.address, balance.toPlainString(), assetId));
LOGGER.trace(() -> String.format("%s balance now %s [assetId %s]", this.address, prettyAmount(balance), assetId));
}
public void deleteBalance(long assetId) throws DataException {

View File

@ -0,0 +1,27 @@
package org.qortal.api;
import java.math.BigDecimal;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.qortal.utils.Amounts;
public class AmountTypeAdapter extends XmlAdapter<String, Long> {
@Override
public Long unmarshal(String input) throws Exception {
if (input == null)
return null;
return new BigDecimal(input).setScale(8).unscaledValue().longValue();
}
@Override
public String marshal(Long output) throws Exception {
if (output == null)
return null;
return Amounts.prettyAmount(output);
}
}

View File

@ -1,11 +1,10 @@
package org.qortal.api.model;
import java.math.BigDecimal;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.qortal.data.asset.OrderData;
@ -29,12 +28,14 @@ public class AggregatedOrder {
}
@XmlElement(name = "price")
public BigDecimal getPrice() {
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
public long getPrice() {
return this.orderData.getPrice();
}
@XmlElement(name = "unfulfilled")
public BigDecimal getUnfulfilled() {
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
public long getUnfulfilled() {
return this.orderData.getAmount();
}

View File

@ -46,6 +46,7 @@ import org.qortal.transaction.Transaction.ValidationResult;
import org.qortal.transform.TransformationException;
import org.qortal.transform.Transformer;
import org.qortal.transform.transaction.RewardShareTransactionTransformer;
import org.qortal.utils.Amounts;
import org.qortal.utils.Base58;
@Path("/addresses")
@ -195,7 +196,7 @@ public class AddressesResource {
else if (!repository.getAssetRepository().assetExists(assetId))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
return account.getBalance(assetId);
return Amounts.toBigDecimal(account.getConfirmedBalance(assetId));
} catch (ApiException e) {
throw e;
} catch (DataException e) {

View File

@ -25,7 +25,8 @@ public class Asset {
public static final int MAX_DESCRIPTION_SIZE = 4000;
public static final int MAX_DATA_SIZE = 400000;
public static final long MAX_QUANTITY = 10_000_000_000L; // but also to 8 decimal places
public static final long MULTIPLIER = 100000000L;
public static final long MAX_QUANTITY = 10_000_000_000L * MULTIPLIER; // but also to 8 decimal places
// Properties
private Repository repository;

View File

@ -1,8 +1,7 @@
package org.qortal.asset;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import static org.qortal.utils.Amounts.prettyAmount;
import java.util.Arrays;
import java.util.List;
@ -14,9 +13,9 @@ import org.qortal.account.PublicKeyAccount;
import org.qortal.data.asset.AssetData;
import org.qortal.data.asset.OrderData;
import org.qortal.data.asset.TradeData;
import org.qortal.repository.AssetRepository;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
import org.qortal.utils.Base58;
public class Order {
@ -57,16 +56,16 @@ public class Order {
// More information
public static BigDecimal getAmountLeft(OrderData orderData) {
return orderData.getAmount().subtract(orderData.getFulfilled());
public static long getAmountLeft(OrderData orderData) {
return orderData.getAmount() - orderData.getFulfilled();
}
public BigDecimal getAmountLeft() {
public long getAmountLeft() {
return Order.getAmountLeft(this.orderData);
}
public static boolean isFulfilled(OrderData orderData) {
return orderData.getFulfilled().compareTo(orderData.getAmount()) == 0;
return orderData.getFulfilled() == orderData.getAmount();
}
public boolean isFulfilled() {
@ -83,31 +82,28 @@ public class Order {
* <p>
* @return granularity of matched-amount
*/
public static BigDecimal calculateAmountGranularity(boolean isAmountAssetDivisible, boolean isReturnAssetDivisible, BigDecimal price) {
// Multiplier to scale BigDecimal fractional amounts into integer domain
BigInteger multiplier = BigInteger.valueOf(1_0000_0000L);
public static long calculateAmountGranularity(boolean isAmountAssetDivisible, boolean isReturnAssetDivisible, long price) {
// Calculate the minimum increment for matched-amount using greatest-common-divisor
BigInteger returnAmount = multiplier; // 1 unit (* multiplier)
BigInteger matchedAmount = price.movePointRight(8).toBigInteger();
long returnAmount = Asset.MULTIPLIER; // 1 unit * multiplier
long matchedAmount = price;
BigInteger gcd = returnAmount.gcd(matchedAmount);
returnAmount = returnAmount.divide(gcd);
matchedAmount = matchedAmount.divide(gcd);
long gcd = Amounts.greatestCommonDivisor(returnAmount, matchedAmount);
returnAmount /= gcd;
matchedAmount /= gcd;
// Calculate GCD in combination with divisibility
if (isAmountAssetDivisible)
returnAmount = returnAmount.multiply(multiplier);
returnAmount *= Asset.MULTIPLIER;
if (isReturnAssetDivisible)
matchedAmount = matchedAmount.multiply(multiplier);
matchedAmount *= Asset.MULTIPLIER;
gcd = returnAmount.gcd(matchedAmount);
gcd = Amounts.greatestCommonDivisor(returnAmount, matchedAmount);
// Calculate the granularity at which we have to buy
BigDecimal granularity = new BigDecimal(returnAmount.divide(gcd));
long granularity = returnAmount / gcd;
if (isAmountAssetDivisible)
granularity = granularity.movePointLeft(8);
granularity /= Asset.MULTIPLIER;
// Return
return granularity;
@ -145,23 +141,23 @@ public class Order {
}
/** Returns amount of have-asset to remove from order's creator's balance on placing this order. */
private BigDecimal calcHaveAssetCommittment() {
BigDecimal committedCost = this.orderData.getAmount();
private long calcHaveAssetCommittment() {
long committedCost = this.orderData.getAmount();
// If "amount" is in want-asset then we need to convert
if (haveAssetId < wantAssetId)
committedCost = committedCost.multiply(this.orderData.getPrice()).setScale(8, RoundingMode.HALF_UP);
committedCost *= this.orderData.getPrice() + 1; // +1 to round up
return committedCost;
}
/** Returns amount of remaining have-asset to refund to order's creator's balance on cancelling this order. */
private BigDecimal calcHaveAssetRefund() {
BigDecimal refund = getAmountLeft();
private long calcHaveAssetRefund() {
long refund = getAmountLeft();
// If "amount" is in want-asset then we need to convert
if (haveAssetId < wantAssetId)
refund = refund.multiply(this.orderData.getPrice()).setScale(8, RoundingMode.HALF_UP);
refund *= this.orderData.getPrice() + 1; // +1 to round up
return refund;
}
@ -229,37 +225,30 @@ public class Order {
final AssetData amountAssetData = this.repository.getAssetRepository().fromAssetId(amountAssetId);
final AssetData returnAssetData = this.repository.getAssetRepository().fromAssetId(returnAssetId);
LOGGER.debug(String.format("%s %s", orderPrefix, Base58.encode(orderData.getOrderId())));
LOGGER.debug(() -> String.format("%s %s", orderPrefix, Base58.encode(orderData.getOrderId())));
LOGGER.trace(String.format("%s have %s, want %s.", weThey, haveAssetData.getName(), wantAssetData.getName()));
LOGGER.trace(() -> String.format("%s have %s, want %s.", weThey, haveAssetData.getName(), wantAssetData.getName()));
LOGGER.trace(String.format("%s amount: %s (ordered) - %s (fulfilled) = %s %s left", ourTheir,
orderData.getAmount().stripTrailingZeros().toPlainString(),
orderData.getFulfilled().stripTrailingZeros().toPlainString(),
Order.getAmountLeft(orderData).stripTrailingZeros().toPlainString(),
LOGGER.trace(() -> String.format("%s amount: %s (ordered) - %s (fulfilled) = %s %s left", ourTheir,
prettyAmount(orderData.getAmount()),
prettyAmount(orderData.getFulfilled()),
prettyAmount(Order.getAmountLeft(orderData)),
amountAssetData.getName()));
BigDecimal maxReturnAmount = Order.getAmountLeft(orderData).multiply(orderData.getPrice()).setScale(8, RoundingMode.HALF_UP);
long maxReturnAmount = Order.getAmountLeft(orderData) * (orderData.getPrice() + 1); // +1 to round up
String pricePair = getPricePair();
LOGGER.trace(String.format("%s price: %s %s (%s %s tradable)", ourTheir,
orderData.getPrice().toPlainString(), getPricePair(),
maxReturnAmount.stripTrailingZeros().toPlainString(), returnAssetData.getName()));
LOGGER.trace(() -> String.format("%s price: %s %s (%s %s tradable)", ourTheir,
prettyAmount(orderData.getPrice()),
pricePair,
prettyAmount(maxReturnAmount),
returnAssetData.getName()));
}
public void process() throws DataException {
AssetRepository assetRepository = this.repository.getAssetRepository();
AssetData haveAssetData = getHaveAsset();
AssetData wantAssetData = getWantAsset();
/** The asset while working out amount that matches. */
AssetData matchingAssetData = getAmountAsset();
/** The return asset traded if trade completes. */
AssetData returnAssetData = getReturnAsset();
// Subtract have-asset from creator
Account creator = new PublicKeyAccount(this.repository, this.orderData.getCreatorPublicKey());
creator.setConfirmedBalance(haveAssetId, creator.getConfirmedBalance(haveAssetId).subtract(this.calcHaveAssetCommittment()));
creator.setConfirmedBalance(haveAssetId, creator.getConfirmedBalance(haveAssetId) - this.calcHaveAssetCommittment());
// Save this order into repository so it's available for matching, possibly by itself
this.repository.getAssetRepository().save(this.orderData);
@ -268,12 +257,24 @@ public class Order {
// Fetch corresponding open orders that might potentially match, hence reversed want/have assetIDs.
// Returned orders are sorted with lowest "price" first.
List<OrderData> orders = assetRepository.getOpenOrdersForTrading(wantAssetId, haveAssetId, this.orderData.getPrice());
LOGGER.trace("Open orders fetched from repository: " + orders.size());
List<OrderData> orders = this.repository.getAssetRepository().getOpenOrdersForTrading(wantAssetId, haveAssetId, this.orderData.getPrice());
LOGGER.trace(() -> String.format("Open orders fetched from repository: %d", orders.size()));
if (orders.isEmpty())
return;
matchOrders(orders);
}
private void matchOrders(List<OrderData> orders) throws DataException {
AssetData haveAssetData = getHaveAsset();
AssetData wantAssetData = getWantAsset();
/** The asset while working out amount that matches. */
AssetData matchingAssetData = getAmountAsset();
/** The return asset traded if trade completes. */
AssetData returnAssetData = getReturnAsset();
// Attempt to match orders
/*
@ -295,86 +296,70 @@ public class Order {
* If their order only had 36 GOLD left, only 36 * 486.00074844 = 17496.02694384 QORT would be traded.
*/
BigDecimal ourPrice = this.orderData.getPrice();
long ourPrice = this.orderData.getPrice();
String pricePair = getPricePair();
for (OrderData theirOrderData : orders) {
logOrder("Considering order", false, theirOrderData);
// Determine their order price
BigDecimal theirPrice;
// Pricing units are the same way round for both orders, so no conversion needed.
theirPrice = theirOrderData.getPrice();
LOGGER.trace(String.format("Their price: %s %s", theirPrice.toPlainString(), getPricePair()));
long theirPrice = theirOrderData.getPrice();
LOGGER.trace(() -> String.format("Their price: %s %s", prettyAmount(theirPrice), pricePair));
// If their price is worse than what we're willing to accept then we're done as prices only get worse as we iterate through list of orders
if (haveAssetId < wantAssetId && theirPrice.compareTo(ourPrice) > 0)
break;
if (haveAssetId > wantAssetId && theirPrice.compareTo(ourPrice) < 0)
if ((haveAssetId < wantAssetId && theirPrice > ourPrice) || (haveAssetId > wantAssetId && theirPrice < ourPrice))
break;
// Calculate how much we could buy at their price, "amount" is expressed in terms of asset with highest assetID.
BigDecimal ourMaxAmount = this.getAmountLeft();
LOGGER.trace("ourMaxAmount (max we could trade at their price): " + ourMaxAmount.stripTrailingZeros().toPlainString() + " " + matchingAssetData.getName());
long ourMaxAmount = this.getAmountLeft();
LOGGER.trace(() -> String.format("ourMaxAmount (max we could trade at their price): %s %s", prettyAmount(ourMaxAmount), matchingAssetData.getName()));
// How much is remaining available in their order.
BigDecimal theirAmountLeft = Order.getAmountLeft(theirOrderData);
LOGGER.trace("theirAmountLeft (max amount remaining in their order): " + theirAmountLeft.stripTrailingZeros().toPlainString() + " " + matchingAssetData.getName());
long theirAmountLeft = Order.getAmountLeft(theirOrderData);
LOGGER.trace(() -> String.format("theirAmountLeft (max amount remaining in their order): %s %s", prettyAmount(theirAmountLeft), matchingAssetData.getName()));
// So matchable want-asset amount is the minimum of above two values
BigDecimal matchedAmount = ourMaxAmount.min(theirAmountLeft);
LOGGER.trace("matchedAmount: " + matchedAmount.stripTrailingZeros().toPlainString() + " " + matchingAssetData.getName());
long interimMatchedAmount = Math.min(ourMaxAmount, theirAmountLeft);
LOGGER.trace(() -> String.format("matchedAmount: %s %s", prettyAmount(interimMatchedAmount), matchingAssetData.getName()));
// If we can't buy anything then try another order
if (matchedAmount.compareTo(BigDecimal.ZERO) <= 0)
if (interimMatchedAmount <= 0)
continue;
// Calculate amount granularity, based on price and both assets' divisibility, so that return-amount traded is a valid value (integer or to 8 d.p.)
BigDecimal granularity = calculateAmountGranularity(matchingAssetData.getIsDivisible(), returnAssetData.getIsDivisible(), theirOrderData.getPrice());
LOGGER.trace("granularity (amount granularity): " + granularity.stripTrailingZeros().toPlainString() + " " + matchingAssetData.getName());
long granularity = calculateAmountGranularity(matchingAssetData.getIsDivisible(), returnAssetData.getIsDivisible(), theirOrderData.getPrice());
LOGGER.trace(() -> String.format("granularity (amount granularity): %s %s", prettyAmount(granularity), matchingAssetData.getName()));
// Reduce matched amount (if need be) to fit granularity
matchedAmount = matchedAmount.subtract(matchedAmount.remainder(granularity));
LOGGER.trace("matchedAmount adjusted for granularity: " + matchedAmount.stripTrailingZeros().toPlainString() + " " + matchingAssetData.getName());
long matchedAmount = interimMatchedAmount - interimMatchedAmount % granularity;
LOGGER.trace(() -> String.format("matchedAmount adjusted for granularity: %s %s", prettyAmount(matchedAmount), matchingAssetData.getName()));
// If we can't buy anything then try another order
if (matchedAmount.compareTo(BigDecimal.ZERO) <= 0)
if (matchedAmount <= 0)
continue;
// Safety check
if (!matchingAssetData.getIsDivisible() && matchedAmount.stripTrailingZeros().scale() > 0) {
Account participant = new PublicKeyAccount(this.repository, theirOrderData.getCreatorPublicKey());
String message = String.format("Refusing to trade fractional %s [indivisible assetID %d] for %s",
matchedAmount.toPlainString(), matchingAssetData.getAssetId(), participant.getAddress());
LOGGER.error(message);
throw new DataException(message);
}
checkDivisibility(matchingAssetData, matchedAmount, theirOrderData);
// Trade can go ahead!
// Calculate the total cost to us, in return-asset, based on their price
BigDecimal returnAmountTraded = matchedAmount.multiply(theirOrderData.getPrice()).setScale(8, RoundingMode.DOWN);
LOGGER.trace("returnAmountTraded: " + returnAmountTraded.stripTrailingZeros().toPlainString() + " " + returnAssetData.getName());
long returnAmountTraded = matchedAmount * theirOrderData.getPrice();
LOGGER.trace(() -> String.format("returnAmountTraded: %s %s", prettyAmount(returnAmountTraded), returnAssetData.getName()));
// Safety check
if (!returnAssetData.getIsDivisible() && returnAmountTraded.stripTrailingZeros().scale() > 0) {
String message = String.format("Refusing to trade fractional %s [indivisible assetID %d] for %s",
returnAmountTraded.toPlainString(), returnAssetData.getAssetId(), creator.getAddress());
LOGGER.error(message);
throw new DataException(message);
}
checkDivisibility(returnAssetData, returnAmountTraded, this.orderData);
BigDecimal tradedWantAmount = (haveAssetId > wantAssetId) ? returnAmountTraded : matchedAmount;
BigDecimal tradedHaveAmount = (haveAssetId > wantAssetId) ? matchedAmount : returnAmountTraded;
long tradedWantAmount = (haveAssetId > wantAssetId) ? returnAmountTraded : matchedAmount;
long tradedHaveAmount = (haveAssetId > wantAssetId) ? matchedAmount : returnAmountTraded;
// We also need to know how much have-asset to refund based on price improvement (only one direction applies)
BigDecimal haveAssetRefund = haveAssetId < wantAssetId ? ourPrice.subtract(theirPrice).abs().multiply(matchedAmount).setScale(8, RoundingMode.DOWN) : BigDecimal.ZERO;
long haveAssetRefund = haveAssetId < wantAssetId ? Math.abs(ourPrice -theirPrice) * matchedAmount : 0;
LOGGER.trace(String.format("We traded %s %s (have-asset) for %s %s (want-asset), saving %s %s (have-asset)",
tradedHaveAmount.toPlainString(), haveAssetData.getName(),
tradedWantAmount.toPlainString(), wantAssetData.getName(),
haveAssetRefund.toPlainString(), haveAssetData.getName()));
LOGGER.trace(() -> String.format("We traded %s %s (have-asset) for %s %s (want-asset), saving %s %s (have-asset)",
prettyAmount(tradedHaveAmount), haveAssetData.getName(),
prettyAmount(tradedWantAmount), wantAssetData.getName(),
prettyAmount(haveAssetRefund), haveAssetData.getName()));
// Construct trade
TradeData tradeData = new TradeData(this.orderData.getOrderId(), theirOrderData.getOrderId(),
@ -384,17 +369,33 @@ public class Order {
trade.process();
// Update our order in terms of fulfilment, etc. but do not save into repository as that's handled by Trade above
BigDecimal amountFulfilled = matchedAmount;
this.orderData.setFulfilled(this.orderData.getFulfilled().add(amountFulfilled));
LOGGER.trace("Updated our order's fulfilled amount to: " + this.orderData.getFulfilled().stripTrailingZeros().toPlainString() + " " + matchingAssetData.getName());
LOGGER.trace("Our order's amount remaining: " + this.getAmountLeft().stripTrailingZeros().toPlainString() + " " + matchingAssetData.getName());
long amountFulfilled = matchedAmount;
this.orderData.setFulfilled(this.orderData.getFulfilled() + amountFulfilled);
LOGGER.trace(() -> String.format("Updated our order's fulfilled amount to: %s %s", prettyAmount(this.orderData.getFulfilled()), matchingAssetData.getName()));
LOGGER.trace(() -> String.format("Our order's amount remaining: %s %s", prettyAmount(this.getAmountLeft()), matchingAssetData.getName()));
// Continue on to process other open orders if we still have amount left to match
if (this.getAmountLeft().compareTo(BigDecimal.ZERO) <= 0)
if (this.getAmountLeft() <= 0)
break;
}
}
/**
* Check amount has no fractional part if asset is indivisible.
*
* @throws DataException if divisibility check fails
*/
private void checkDivisibility(AssetData assetData, long amount, OrderData orderData) throws DataException {
if (assetData.getIsDivisible() || amount % Asset.MULTIPLIER == 0)
// Asset is divisible or amount has no fractional part
return;
String message = String.format("Refusing to trade fractional %s [indivisible assetID %d] for order %s",
prettyAmount(amount), assetData.getAssetId(), Base58.encode(orderData.getOrderId()));
LOGGER.error(message);
throw new DataException(message);
}
public void orphan() throws DataException {
// Orphan trades that occurred as a result of this order
for (TradeData tradeData : getTrades())
@ -408,7 +409,7 @@ public class Order {
// Return asset to creator
Account creator = new PublicKeyAccount(this.repository, this.orderData.getCreatorPublicKey());
creator.setConfirmedBalance(haveAssetId, creator.getConfirmedBalance(haveAssetId).add(this.calcHaveAssetCommittment()));
creator.setConfirmedBalance(haveAssetId, creator.getConfirmedBalance(haveAssetId) + this.calcHaveAssetCommittment());
}
// This is called by CancelOrderTransaction so that an Order can no longer trade
@ -418,14 +419,14 @@ public class Order {
// Update creator's balance with unfulfilled amount
Account creator = new PublicKeyAccount(this.repository, this.orderData.getCreatorPublicKey());
creator.setConfirmedBalance(haveAssetId, creator.getConfirmedBalance(haveAssetId).add(calcHaveAssetRefund()));
creator.setConfirmedBalance(haveAssetId, creator.getConfirmedBalance(haveAssetId) + calcHaveAssetRefund());
}
// Opposite of cancel() above for use during orphaning
public void reopen() throws DataException {
// Update creator's balance with unfulfilled amount
Account creator = new PublicKeyAccount(this.repository, this.orderData.getCreatorPublicKey());
creator.setConfirmedBalance(haveAssetId, creator.getConfirmedBalance(haveAssetId).subtract(calcHaveAssetRefund()));
creator.setConfirmedBalance(haveAssetId, creator.getConfirmedBalance(haveAssetId) - calcHaveAssetRefund());
this.orderData.setIsClosed(false);
this.repository.getAssetRepository().save(this.orderData);

View File

@ -1,7 +1,5 @@
package org.qortal.asset;
import java.math.BigDecimal;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.data.asset.OrderData;
@ -20,7 +18,7 @@ public class Trade {
private OrderData initiatingOrder;
private OrderData targetOrder;
private BigDecimal fulfilled;
private long fulfilled;
// Constructors
@ -42,7 +40,7 @@ public class Trade {
// "amount" and "fulfilled" are the same asset for both orders
// which is the matchedAmount in asset with highest assetID
this.fulfilled = (initiatingOrder.getHaveAssetId() < initiatingOrder.getWantAssetId()) ? this.tradeData.getTargetAmount() : this.tradeData.getInitiatorAmount();
this.fulfilled = initiatingOrder.getHaveAssetId() < initiatingOrder.getWantAssetId() ? this.tradeData.getTargetAmount() : this.tradeData.getInitiatorAmount();
}
public void process() throws DataException {
@ -55,13 +53,13 @@ public class Trade {
commonPrep();
// Update corresponding Orders on both sides of trade
initiatingOrder.setFulfilled(initiatingOrder.getFulfilled().add(fulfilled));
initiatingOrder.setFulfilled(initiatingOrder.getFulfilled() + fulfilled);
initiatingOrder.setIsFulfilled(Order.isFulfilled(initiatingOrder));
// Set isClosed to true if isFulfilled now true
initiatingOrder.setIsClosed(initiatingOrder.getIsFulfilled());
assetRepository.save(initiatingOrder);
targetOrder.setFulfilled(targetOrder.getFulfilled().add(fulfilled));
targetOrder.setFulfilled(targetOrder.getFulfilled() + fulfilled);
targetOrder.setIsFulfilled(Order.isFulfilled(targetOrder));
// Set isClosed to true if isFulfilled now true
targetOrder.setIsClosed(targetOrder.getIsFulfilled());
@ -69,33 +67,31 @@ public class Trade {
// Actually transfer asset balances
Account initiatingCreator = new PublicKeyAccount(this.repository, initiatingOrder.getCreatorPublicKey());
initiatingCreator.setConfirmedBalance(initiatingOrder.getWantAssetId(), initiatingCreator.getConfirmedBalance(initiatingOrder.getWantAssetId()).add(tradeData.getTargetAmount()));
initiatingCreator.setConfirmedBalance(initiatingOrder.getWantAssetId(), initiatingCreator.getConfirmedBalance(initiatingOrder.getWantAssetId()) + tradeData.getTargetAmount());
Account targetCreator = new PublicKeyAccount(this.repository, targetOrder.getCreatorPublicKey());
targetCreator.setConfirmedBalance(targetOrder.getWantAssetId(), targetCreator.getConfirmedBalance(targetOrder.getWantAssetId()).add(tradeData.getInitiatorAmount()));
targetCreator.setConfirmedBalance(targetOrder.getWantAssetId(), targetCreator.getConfirmedBalance(targetOrder.getWantAssetId()) + tradeData.getInitiatorAmount());
// Possible partial saving to refund to initiator
BigDecimal initiatorSaving = this.tradeData.getInitiatorSaving();
if (initiatorSaving.compareTo(BigDecimal.ZERO) > 0)
initiatingCreator.setConfirmedBalance(initiatingOrder.getHaveAssetId(), initiatingCreator.getConfirmedBalance(initiatingOrder.getHaveAssetId()).add(initiatorSaving));
long initiatorSaving = this.tradeData.getInitiatorSaving();
if (initiatorSaving > 0)
initiatingCreator.setConfirmedBalance(initiatingOrder.getHaveAssetId(), initiatingCreator.getConfirmedBalance(initiatingOrder.getHaveAssetId()) + initiatorSaving);
}
public void orphan() throws DataException {
AssetRepository assetRepository = this.repository.getAssetRepository();
// Note: targetAmount is amount traded FROM target order
// Note: initiatorAmount is amount traded FROM initiating order
commonPrep();
// Revert corresponding Orders on both sides of trade
initiatingOrder.setFulfilled(initiatingOrder.getFulfilled().subtract(fulfilled));
initiatingOrder.setFulfilled(initiatingOrder.getFulfilled() - fulfilled);
initiatingOrder.setIsFulfilled(Order.isFulfilled(initiatingOrder));
// Set isClosed to false if isFulfilled now false
initiatingOrder.setIsClosed(initiatingOrder.getIsFulfilled());
assetRepository.save(initiatingOrder);
targetOrder.setFulfilled(targetOrder.getFulfilled().subtract(fulfilled));
targetOrder.setFulfilled(targetOrder.getFulfilled() - fulfilled);
targetOrder.setIsFulfilled(Order.isFulfilled(targetOrder));
// Set isClosed to false if isFulfilled now false
targetOrder.setIsClosed(targetOrder.getIsFulfilled());
@ -103,15 +99,15 @@ public class Trade {
// Reverse asset transfers
Account initiatingCreator = new PublicKeyAccount(this.repository, initiatingOrder.getCreatorPublicKey());
initiatingCreator.setConfirmedBalance(initiatingOrder.getWantAssetId(), initiatingCreator.getConfirmedBalance(initiatingOrder.getWantAssetId()).subtract(tradeData.getTargetAmount()));
initiatingCreator.setConfirmedBalance(initiatingOrder.getWantAssetId(), initiatingCreator.getConfirmedBalance(initiatingOrder.getWantAssetId()) - tradeData.getTargetAmount());
Account targetCreator = new PublicKeyAccount(this.repository, targetOrder.getCreatorPublicKey());
targetCreator.setConfirmedBalance(targetOrder.getWantAssetId(), targetCreator.getConfirmedBalance(targetOrder.getWantAssetId()).subtract(tradeData.getInitiatorAmount()));
targetCreator.setConfirmedBalance(targetOrder.getWantAssetId(), targetCreator.getConfirmedBalance(targetOrder.getWantAssetId()) - tradeData.getInitiatorAmount());
// Possible partial saving to claw back from initiator
BigDecimal initiatorSaving = this.tradeData.getInitiatorSaving();
if (initiatorSaving.compareTo(BigDecimal.ZERO) > 0)
initiatingCreator.setConfirmedBalance(initiatingOrder.getHaveAssetId(), initiatingCreator.getConfirmedBalance(initiatingOrder.getHaveAssetId()).subtract(initiatorSaving));
long initiatorSaving = this.tradeData.getInitiatorSaving();
if (initiatorSaving > 0)
initiatingCreator.setConfirmedBalance(initiatingOrder.getHaveAssetId(), initiatingCreator.getConfirmedBalance(initiatingOrder.getHaveAssetId()) - initiatorSaving);
// Remove trade from repository
assetRepository.delete(tradeData);

View File

@ -1,6 +1,5 @@
package org.qortal.at;
import java.math.BigDecimal;
import java.util.List;
import org.ciyam.at.MachineState;
@ -51,7 +50,7 @@ public class AT {
byte[] stateData = machineState.toBytes();
byte[] stateHash = Crypto.digest(stateData);
this.atStateData = new ATStateData(atAddress, height, creation, stateData, stateHash, BigDecimal.ZERO.setScale(8));
this.atStateData = new ATStateData(atAddress, height, creation, stateData, stateHash, 0L);
}
// Getters / setters
@ -97,7 +96,7 @@ public class AT {
long creation = this.atData.getCreation();
byte[] stateData = state.toBytes();
byte[] stateHash = Crypto.digest(stateData);
BigDecimal atFees = api.calcFinalFees(state);
long atFees = api.calcFinalFees(state);
this.atStateData = new ATStateData(atAddress, height, creation, stateData, stateHash, atFees);

View File

@ -3,7 +3,6 @@ package org.qortal.at;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@ -85,16 +84,16 @@ public enum BlockchainAPI {
switch (transactionData.getType()) {
case PAYMENT:
return ((PaymentTransactionData) transactionData).getAmount().unscaledValue().longValue();
return ((PaymentTransactionData) transactionData).getAmount();
case AT:
BigDecimal amount = ((ATTransactionData) transactionData).getAmount();
Long amount = ((ATTransactionData) transactionData).getAmount();
if (amount != null)
return amount.unscaledValue().longValue();
else
if (amount == null)
return 0xffffffffffffffffL;
return amount;
default:
return 0xffffffffffffffffL;
}

View File

@ -1,6 +1,5 @@
package org.qortal.at;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
@ -34,7 +33,7 @@ import com.google.common.primitives.Bytes;
public class QortalATAPI extends API {
// Useful constants
private static final BigDecimal FEE_PER_STEP = BigDecimal.valueOf(1.0).setScale(8); // 1 QORT per "step"
private static final long FEE_PER_STEP = 1 * Asset.MULTIPLIER; // 1 QORT per "step"
private static final int MAX_STEPS_PER_ROUND = 500;
private static final int STEPS_PER_FUNCTION_CALL = 10;
private static final int MINUTES_PER_BLOCK = 10;
@ -62,8 +61,8 @@ public class QortalATAPI extends API {
return this.transactions;
}
public BigDecimal calcFinalFees(MachineState state) {
return FEE_PER_STEP.multiply(BigDecimal.valueOf(state.getSteps()));
public long calcFinalFees(MachineState state) {
return state.getSteps() * FEE_PER_STEP;
}
// Inherited methods from CIYAM AT API
@ -83,7 +82,7 @@ public class QortalATAPI extends API {
@Override
public long getFeePerStep() {
return FEE_PER_STEP.unscaledValue().longValue();
return FEE_PER_STEP;
}
@Override
@ -254,23 +253,22 @@ public class QortalATAPI extends API {
try {
Account atAccount = this.getATAccount();
return atAccount.getConfirmedBalance(Asset.QORT).unscaledValue().longValue();
return atAccount.getConfirmedBalance(Asset.QORT);
} catch (DataException e) {
throw new RuntimeException("AT API unable to fetch AT's current balance?", e);
}
}
@Override
public void payAmountToB(long unscaledAmount, MachineState state) {
public void payAmountToB(long amount, MachineState state) {
byte[] publicKey = state.getB();
PublicKeyAccount recipient = new PublicKeyAccount(this.repository, publicKey);
long timestamp = this.getNextTransactionTimestamp();
byte[] reference = this.getLastReference();
BigDecimal amount = BigDecimal.valueOf(unscaledAmount, 8);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, NullAccount.PUBLIC_KEY, BigDecimal.ZERO, null);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, NullAccount.PUBLIC_KEY, 0L, null);
ATTransactionData atTransactionData = new ATTransactionData(baseTransactionData, this.atData.getATAddress(),
recipient.getAddress(), amount, this.atData.getAssetId(), new byte[0]);
AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData);
@ -289,9 +287,9 @@ public class QortalATAPI extends API {
long timestamp = this.getNextTransactionTimestamp();
byte[] reference = this.getLastReference();
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, NullAccount.PUBLIC_KEY, BigDecimal.ZERO, null);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, NullAccount.PUBLIC_KEY, 0L, null);
ATTransactionData atTransactionData = new ATTransactionData(baseTransactionData, this.atData.getATAddress(),
recipient.getAddress(), BigDecimal.ZERO, this.atData.getAssetId(), message);
recipient.getAddress(), 0L, this.atData.getAssetId(), message);
AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData);
// Add to our transactions
@ -314,11 +312,10 @@ public class QortalATAPI extends API {
Account creator = this.getCreator();
long timestamp = this.getNextTransactionTimestamp();
byte[] reference = this.getLastReference();
BigDecimal amount = BigDecimal.valueOf(finalBalance, 8);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, NullAccount.PUBLIC_KEY, BigDecimal.ZERO, null);
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, Group.NO_GROUP, reference, NullAccount.PUBLIC_KEY, 0L, null);
ATTransactionData atTransactionData = new ATTransactionData(baseTransactionData, this.atData.getATAddress(),
creator.getAddress(), amount, this.atData.getAssetId(), new byte[0]);
creator.getAddress(), finalBalance, this.atData.getAssetId(), new byte[0]);
AtTransaction atTransaction = new AtTransaction(this.repository, atTransactionData);
// Add to our transactions

View File

@ -50,6 +50,7 @@ import org.qortal.transform.TransformationException;
import org.qortal.transform.Transformer;
import org.qortal.transform.block.BlockTransformer;
import org.qortal.transform.transaction.TransactionTransformer;
import org.qortal.utils.Amounts;
import org.qortal.utils.Base58;
import org.qortal.utils.NTP;
import org.roaringbitmap.IntIterator;
@ -123,15 +124,14 @@ public class Block {
/** Locally-generated AT states */
protected List<ATStateData> ourAtStates;
/** Locally-generated AT fees */
protected BigDecimal ourAtFees; // Generated locally
protected long ourAtFees; // Generated locally
/** Lazy-instantiated expanded info on block's online accounts. */
static class ExpandedAccount {
private static final BigDecimal oneHundred = BigDecimal.valueOf(100L);
private final Repository repository;
private final RewardShareData rewardShareData;
private final int sharePercent;
private final boolean isRecipientAlsoMinter;
private final Account mintingAccount;
@ -145,6 +145,7 @@ public class Block {
ExpandedAccount(Repository repository, int accountIndex) throws DataException {
this.repository = repository;
this.rewardShareData = repository.getAccountRepository().getRewardShareByIndex(accountIndex);
this.sharePercent = this.rewardShareData.getSharePercent();
this.mintingAccount = new Account(repository, this.rewardShareData.getMinter());
this.mintingAccountData = repository.getAccountRepository().getAccount(this.mintingAccount.getAddress());
@ -187,26 +188,23 @@ public class Block {
return -1;
}
void distribute(BigDecimal accountAmount) throws DataException {
void distribute(long accountAmount) throws DataException {
if (this.isRecipientAlsoMinter) {
// minter & recipient the same - simpler case
LOGGER.trace(() -> String.format("Minter/recipient account %s share: %s", this.mintingAccount.getAddress(), accountAmount.toPlainString()));
if (accountAmount.signum() != 0)
// this.mintingAccount.setConfirmedBalance(Asset.QORT, this.mintingAccount.getConfirmedBalance(Asset.QORT).add(accountAmount));
LOGGER.trace(() -> String.format("Minter/recipient account %s share: %s", this.mintingAccount.getAddress(), Amounts.prettyAmount(accountAmount)));
if (accountAmount != 0)
this.repository.getAccountRepository().modifyAssetBalance(this.mintingAccount.getAddress(), Asset.QORT, accountAmount);
} else {
// minter & recipient different - extra work needed
BigDecimal recipientAmount = accountAmount.multiply(this.rewardShareData.getSharePercent()).divide(oneHundred, RoundingMode.DOWN);
BigDecimal minterAmount = accountAmount.subtract(recipientAmount);
long recipientAmount = (accountAmount * this.sharePercent) / 100L / 100L; // because scaled by 2dp and 'percent' means "per 1e2"
long minterAmount = accountAmount - recipientAmount;
LOGGER.trace(() -> String.format("Minter account %s share: %s", this.mintingAccount.getAddress(), minterAmount.toPlainString()));
if (minterAmount.signum() != 0)
// this.mintingAccount.setConfirmedBalance(Asset.QORT, this.mintingAccount.getConfirmedBalance(Asset.QORT).add(minterAmount));
LOGGER.trace(() -> String.format("Minter account %s share: %s", this.mintingAccount.getAddress(), Amounts.prettyAmount(minterAmount)));
if (minterAmount != 0)
this.repository.getAccountRepository().modifyAssetBalance(this.mintingAccount.getAddress(), Asset.QORT, minterAmount);
LOGGER.trace(() -> String.format("Recipient account %s share: %s", this.recipientAccount.getAddress(), recipientAmount.toPlainString()));
if (recipientAmount.signum() != 0)
// this.recipientAccount.setConfirmedBalance(Asset.QORT, this.recipientAccount.getConfirmedBalance(Asset.QORT).add(recipientAmount));
LOGGER.trace(() -> String.format("Recipient account %s share: %s", this.recipientAccount.getAddress(), Amounts.prettyAmount(recipientAmount)));
if (recipientAmount != 0)
this.repository.getAccountRepository().modifyAssetBalance(this.recipientAccount.getAddress(), Asset.QORT, recipientAmount);
}
}
@ -256,17 +254,17 @@ public class Block {
this.transactions = new ArrayList<>();
BigDecimal totalFees = BigDecimal.ZERO.setScale(8);
long totalFees = 0;
// We have to sum fees too
for (TransactionData transactionData : transactions) {
this.transactions.add(Transaction.fromData(repository, transactionData));
totalFees = totalFees.add(transactionData.getFee());
totalFees += transactionData.getFee();
}
this.atStates = atStates;
for (ATStateData atState : atStates)
totalFees = totalFees.add(atState.getFees());
totalFees += atState.getFees();
this.blockData.setTotalFees(totalFees);
}
@ -361,8 +359,8 @@ public class Block {
int height = parentBlockData.getHeight() + 1;
int atCount = 0;
BigDecimal atFees = BigDecimal.ZERO.setScale(8);
BigDecimal totalFees = atFees;
long atFees = 0;
long totalFees = 0;
// This instance used for AT processing
BlockData preAtBlockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
@ -427,12 +425,12 @@ public class Block {
newBlock.transactions = this.transactions;
int transactionCount = this.blockData.getTransactionCount();
BigDecimal totalFees = this.blockData.getTotalFees();
long totalFees = this.blockData.getTotalFees();
byte[] transactionsSignature = null; // We'll calculate this later
Integer height = this.blockData.getHeight();
int atCount = newBlock.ourAtStates.size();
BigDecimal atFees = newBlock.ourAtFees;
long atFees = newBlock.ourAtFees;
byte[] encodedOnlineAccounts = this.blockData.getEncodedOnlineAccounts();
int onlineAccountsCount = this.blockData.getOnlineAccountsCount();
@ -648,7 +646,7 @@ public class Block {
this.blockData.setTransactionCount(this.blockData.getTransactionCount() + 1);
// Update totalFees
this.blockData.setTotalFees(this.blockData.getTotalFees().add(transactionData.getFee()));
this.blockData.setTotalFees(this.blockData.getTotalFees() + transactionData.getFee());
// We've added a transaction, so recalculate transactions signature
calcTransactionsSignature();
@ -691,7 +689,7 @@ public class Block {
this.blockData.setTransactionCount(this.blockData.getTransactionCount() - 1);
// Update totalFees
this.blockData.setTotalFees(this.blockData.getTotalFees().subtract(transactionData.getFee()));
this.blockData.setTotalFees(this.blockData.getTotalFees() - transactionData.getFee());
// We've removed a transaction, so recalculate transactions signature
calcTransactionsSignature();
@ -1118,7 +1116,7 @@ public class Block {
if (this.ourAtStates.size() != this.blockData.getATCount())
return ValidationResult.AT_STATES_MISMATCH;
if (this.ourAtFees.compareTo(this.blockData.getATFees()) != 0)
if (this.ourAtFees != this.blockData.getATFees())
return ValidationResult.AT_STATES_MISMATCH;
// Note: this.atStates fully loaded thanks to this.getATStates() call above
@ -1132,7 +1130,7 @@ public class Block {
if (!Arrays.equals(ourAtState.getStateHash(), theirAtState.getStateHash()))
return ValidationResult.AT_STATES_MISMATCH;
if (ourAtState.getFees().compareTo(theirAtState.getFees()) != 0)
if (ourAtState.getFees() != theirAtState.getFees())
return ValidationResult.AT_STATES_MISMATCH;
}
@ -1167,7 +1165,7 @@ public class Block {
List<AtTransaction> allAtTransactions = new ArrayList<>();
this.ourAtStates = new ArrayList<>();
this.ourAtFees = BigDecimal.ZERO.setScale(8);
this.ourAtFees = 0;
// Find all executable ATs, ordered by earliest creation date first
List<ATData> executableATs = this.repository.getATRepository().getAllExecutableATs();
@ -1182,7 +1180,7 @@ public class Block {
ATStateData atStateData = at.getATStateData();
this.ourAtStates.add(atStateData);
this.ourAtFees = this.ourAtFees.add(atStateData.getFees());
this.ourAtFees += atStateData.getFees();
}
// Prepend our entire AT-Transactions/states to block's transactions
@ -1313,7 +1311,7 @@ public class Block {
}
protected void processBlockRewards() throws DataException {
BigDecimal reward = BlockChain.getInstance().getRewardAtHeight(this.blockData.getHeight());
Long reward = BlockChain.getInstance().getRewardAtHeight(this.blockData.getHeight());
// No reward for our height?
if (reward == null)
@ -1396,10 +1394,10 @@ public class Block {
}
protected void rewardTransactionFees() throws DataException {
BigDecimal blockFees = this.blockData.getTotalFees();
long blockFees = this.blockData.getTotalFees();
// No transaction fees?
if (blockFees.compareTo(BigDecimal.ZERO) <= 0)
if (blockFees <= 0)
return;
distributeBlockReward(blockFees);
@ -1412,7 +1410,7 @@ public class Block {
Account atAccount = new Account(this.repository, atState.getATAddress());
// Subtract AT-generated fees from AT accounts
atAccount.setConfirmedBalance(Asset.QORT, atAccount.getConfirmedBalance(Asset.QORT).subtract(atState.getFees()));
atAccount.setConfirmedBalance(Asset.QORT, atAccount.getConfirmedBalance(Asset.QORT) - atState.getFees());
atRepository.save(atState);
}
@ -1439,8 +1437,7 @@ public class Block {
// No longer unconfirmed
transactionRepository.confirmTransaction(transactionData.getSignature());
List<Account> participants = transaction.getInvolvedAccounts();
List<String> participantAddresses = participants.stream().map(Account::getAddress).collect(Collectors.toList());
List<String> participantAddresses = transaction.getInvolvedAddresses();
transactionRepository.saveParticipants(transactionData, participantAddresses);
}
}
@ -1547,23 +1544,23 @@ public class Block {
}
protected void orphanBlockRewards() throws DataException {
BigDecimal reward = BlockChain.getInstance().getRewardAtHeight(this.blockData.getHeight());
Long reward = BlockChain.getInstance().getRewardAtHeight(this.blockData.getHeight());
// No reward for our height?
if (reward == null)
return;
distributeBlockReward(reward.negate());
distributeBlockReward(0 - reward);
}
protected void deductTransactionFees() throws DataException {
BigDecimal blockFees = this.blockData.getTotalFees();
long blockFees = this.blockData.getTotalFees();
// No transaction fees?
if (blockFees.compareTo(BigDecimal.ZERO) <= 0)
if (blockFees <= 0)
return;
distributeBlockReward(blockFees.negate());
distributeBlockReward(0 - blockFees);
}
protected void orphanAtFeesAndStates() throws DataException {
@ -1572,7 +1569,7 @@ public class Block {
Account atAccount = new Account(this.repository, atState.getATAddress());
// Return AT-generated fees to AT accounts
atAccount.setConfirmedBalance(Asset.QORT, atAccount.getConfirmedBalance(Asset.QORT).add(atState.getFees()));
atAccount.setConfirmedBalance(Asset.QORT, atAccount.getConfirmedBalance(Asset.QORT) + atState.getFees());
}
// Delete ATStateData for this height
@ -1630,175 +1627,168 @@ public class Block {
}
}
protected void distributeBlockReward(BigDecimal totalAmount) throws DataException {
LOGGER.trace(() -> String.format("Distributing: %s", totalAmount.toPlainString()));
protected void distributeBlockReward(long totalAmount) throws DataException {
LOGGER.trace(() -> String.format("Distributing: %s", Amounts.prettyAmount(totalAmount)));
// Distribute according to account level
BigDecimal sharedByLevelAmount = distributeBlockRewardByLevel(totalAmount);
LOGGER.trace(() -> String.format("Shared %s of %s based on account levels", sharedByLevelAmount.toPlainString(), totalAmount.toPlainString()));
long sharedByLevelAmount = distributeBlockRewardByLevel(totalAmount);
LOGGER.trace(() -> String.format("Shared %s of %s based on account levels", Amounts.prettyAmount(sharedByLevelAmount), Amounts.prettyAmount(totalAmount)));
// Distribute amongst legacy QORA holders
BigDecimal sharedByQoraHoldersAmount = distributeBlockRewardToQoraHolders(totalAmount);
LOGGER.trace(() -> String.format("Shared %s of %s to legacy QORA holders", sharedByQoraHoldersAmount.toPlainString(), totalAmount.toPlainString()));
long sharedByQoraHoldersAmount = distributeBlockRewardToQoraHolders(totalAmount);
LOGGER.trace(() -> String.format("Shared %s of %s to legacy QORA holders", Amounts.prettyAmount(sharedByQoraHoldersAmount), Amounts.prettyAmount(totalAmount)));
// Spread remainder across founder accounts
BigDecimal foundersAmount = totalAmount.subtract(sharedByLevelAmount).subtract(sharedByQoraHoldersAmount);
long foundersAmount = totalAmount - sharedByLevelAmount - sharedByQoraHoldersAmount;
distributeBlockRewardToFounders(foundersAmount);
}
private BigDecimal distributeBlockRewardByLevel(BigDecimal totalAmount) throws DataException {
private long distributeBlockRewardByLevel(long totalAmount) throws DataException {
List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
List<ShareByLevel> sharesByLevel = BlockChain.getInstance().getBlockSharesByLevel();
// Distribute amount across bins
BigDecimal sharedAmount = BigDecimal.ZERO;
long sharedAmount = 0;
for (int s = 0; s < sharesByLevel.size(); ++s) {
final int binIndex = s;
BigDecimal binAmount = sharesByLevel.get(binIndex).share.multiply(totalAmount).setScale(8, RoundingMode.DOWN);
LOGGER.trace(() -> String.format("Bin %d share of %s: %s", binIndex, totalAmount.toPlainString(), binAmount.toPlainString()));
long binAmount = (totalAmount * sharesByLevel.get(binIndex).unscaledShare) / 100000000L;
LOGGER.trace(() -> String.format("Bin %d share of %s: %s", binIndex, Amounts.prettyAmount(totalAmount), Amounts.prettyAmount(binAmount)));
// Spread across all accounts in bin. getShareBin() returns -1 for minter accounts that are also founders, so they are effectively filtered out.
List<ExpandedAccount> binnedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.getShareBin() == binIndex).collect(Collectors.toList());
if (binnedAccounts.isEmpty())
continue;
BigDecimal binSize = BigDecimal.valueOf(binnedAccounts.size());
BigDecimal accountAmount = binAmount.divide(binSize, RoundingMode.DOWN);
long perAccountAmount = binAmount / binnedAccounts.size();
for (int a = 0; a < binnedAccounts.size(); ++a) {
ExpandedAccount expandedAccount = binnedAccounts.get(a);
expandedAccount.distribute(accountAmount);
sharedAmount = sharedAmount.add(accountAmount);
expandedAccount.distribute(perAccountAmount);
sharedAmount += perAccountAmount;
}
}
return sharedAmount;
}
private BigDecimal distributeBlockRewardToQoraHolders(BigDecimal totalAmount) throws DataException {
BigDecimal qoraHoldersAmount = BlockChain.getInstance().getQoraHoldersShare().multiply(totalAmount).setScale(8, RoundingMode.DOWN);
LOGGER.trace(() -> String.format("Legacy QORA holders share of %s: %s", totalAmount.toPlainString(), qoraHoldersAmount.toPlainString()));
private long distributeBlockRewardToQoraHolders(long totalAmount) throws DataException {
long qoraHoldersAmount = (BlockChain.getInstance().getQoraHoldersUnscaledShare() * totalAmount) / 100000000L;
LOGGER.trace(() -> String.format("Legacy QORA holders share of %s: %s", Amounts.prettyAmount(totalAmount), Amounts.prettyAmount(qoraHoldersAmount)));
final boolean isProcessingNotOrphaning = totalAmount.signum() >= 0;
final boolean isProcessingNotOrphaning = totalAmount >= 0;
BigDecimal qoraPerQortReward = BlockChain.getInstance().getQoraPerQortReward();
long qoraPerQortReward = BlockChain.getInstance().getUnscaledQoraPerQortReward();
List<AccountBalanceData> qoraHolders = this.repository.getAccountRepository().getEligibleLegacyQoraHolders(isProcessingNotOrphaning ? null : this.blockData.getHeight());
BigDecimal totalQoraHeld = BigDecimal.ZERO;
long totalQoraHeld = 0;
for (int i = 0; i < qoraHolders.size(); ++i)
totalQoraHeld = totalQoraHeld.add(qoraHolders.get(i).getBalance());
totalQoraHeld += qoraHolders.get(i).getBalance();
BigDecimal finalTotalQoraHeld = totalQoraHeld;
LOGGER.trace(() -> String.format("Total legacy QORA held: %s", finalTotalQoraHeld.toPlainString()));
long finalTotalQoraHeld = totalQoraHeld;
LOGGER.trace(() -> String.format("Total legacy QORA held: %s", Amounts.prettyAmount(finalTotalQoraHeld)));
BigDecimal sharedAmount = BigDecimal.ZERO;
if (totalQoraHeld.signum() <= 0)
long sharedAmount = 0;
if (totalQoraHeld <= 0)
return sharedAmount;
for (int h = 0; h < qoraHolders.size(); ++h) {
AccountBalanceData qoraHolder = qoraHolders.get(h);
BigDecimal holderReward = qoraHoldersAmount.multiply(qoraHolder.getBalance()).divide(totalQoraHeld, RoundingMode.DOWN).setScale(8, RoundingMode.DOWN);
BigDecimal finalHolderReward = holderReward;
long holderReward = (qoraHoldersAmount * qoraHolder.getBalance()) / totalQoraHeld;
long finalHolderReward = holderReward;
LOGGER.trace(() -> String.format("QORA holder %s has %s / %s QORA so share: %s",
qoraHolder.getAddress(), qoraHolder.getBalance().toPlainString(), finalTotalQoraHeld, finalHolderReward.toPlainString()));
qoraHolder.getAddress(), Amounts.prettyAmount(qoraHolder.getBalance()), finalTotalQoraHeld, Amounts.prettyAmount(finalHolderReward)));
// Too small to register this time?
if (holderReward.signum() == 0)
if (holderReward == 0)
continue;
Account qoraHolderAccount = new Account(repository, qoraHolder.getAddress());
BigDecimal newQortFromQoraBalance = qoraHolderAccount.getConfirmedBalance(Asset.QORT_FROM_QORA).add(holderReward);
long newQortFromQoraBalance = qoraHolderAccount.getConfirmedBalance(Asset.QORT_FROM_QORA) + holderReward;
// If processing, make sure we don't overpay
if (isProcessingNotOrphaning) {
BigDecimal maxQortFromQora = qoraHolder.getBalance().divide(qoraPerQortReward, RoundingMode.DOWN);
long maxQortFromQora = qoraHolder.getBalance() / qoraPerQortReward;
if (newQortFromQoraBalance.compareTo(maxQortFromQora) >= 0) {
if (newQortFromQoraBalance >= maxQortFromQora) {
// Reduce final QORT-from-QORA payment to match max
BigDecimal adjustment = newQortFromQoraBalance.subtract(maxQortFromQora);
long adjustment = newQortFromQoraBalance - maxQortFromQora;
holderReward = holderReward.subtract(adjustment);
newQortFromQoraBalance = newQortFromQoraBalance.subtract(adjustment);
holderReward -= adjustment;
newQortFromQoraBalance -= adjustment;
// This is also the QORA holder's final QORT-from-QORA block
QortFromQoraData qortFromQoraData = new QortFromQoraData(qoraHolder.getAddress(), holderReward, this.blockData.getHeight());
this.repository.getAccountRepository().save(qortFromQoraData);
BigDecimal finalAdjustedHolderReward = holderReward;
long finalAdjustedHolderReward = holderReward;
LOGGER.trace(() -> String.format("QORA holder %s final share %s at height %d",
qoraHolder.getAddress(), finalAdjustedHolderReward.toPlainString(), this.blockData.getHeight()));
qoraHolder.getAddress(), Amounts.prettyAmount(finalAdjustedHolderReward), this.blockData.getHeight()));
}
} else {
// Orphaning
QortFromQoraData qortFromQoraData = this.repository.getAccountRepository().getQortFromQoraInfo(qoraHolder.getAddress());
if (qortFromQoraData != null) {
// Final QORT-from-QORA amount from repository was stored during processing, and hence positive.
// So we use add() here as qortFromQora is negative during orphaning.
// More efficient than holderReward.subtract(final-qort-from-qora.negate())
BigDecimal adjustment = holderReward.add(qortFromQoraData.getFinalQortFromQora());
// So we use + here as qortFromQora is negative during orphaning.
// More efficient than "holderReward - (0 - final-qort-from-qora)"
long adjustment = holderReward + qortFromQoraData.getFinalQortFromQora();
holderReward = holderReward.subtract(adjustment);
newQortFromQoraBalance = newQortFromQoraBalance.subtract(adjustment);
holderReward -= adjustment;
newQortFromQoraBalance -= adjustment;
this.repository.getAccountRepository().deleteQortFromQoraInfo(qoraHolder.getAddress());
BigDecimal finalAdjustedHolderReward = holderReward;
long finalAdjustedHolderReward = holderReward;
LOGGER.trace(() -> String.format("QORA holder %s final share %s was at height %d",
qoraHolder.getAddress(), finalAdjustedHolderReward.toPlainString(), this.blockData.getHeight()));
qoraHolder.getAddress(), Amounts.prettyAmount(finalAdjustedHolderReward), this.blockData.getHeight()));
}
}
// qoraHolderAccount.setConfirmedBalance(Asset.QORT, qoraHolderAccount.getConfirmedBalance(Asset.QORT).add(holderReward));
this.repository.getAccountRepository().modifyAssetBalance(qoraHolder.getAddress(), Asset.QORT, holderReward);
if (newQortFromQoraBalance.signum() > 0)
if (newQortFromQoraBalance > 0)
qoraHolderAccount.setConfirmedBalance(Asset.QORT_FROM_QORA, newQortFromQoraBalance);
else
// Remove QORT_FROM_QORA balance as it's zero
qoraHolderAccount.deleteBalance(Asset.QORT_FROM_QORA);
sharedAmount = sharedAmount.add(holderReward);
sharedAmount += holderReward;
}
return sharedAmount;
}
private void distributeBlockRewardToFounders(BigDecimal foundersAmount) throws DataException {
private void distributeBlockRewardToFounders(long foundersAmount) throws DataException {
// Remaining reward portion is spread across all founders, online or not
List<AccountData> founderAccounts = this.repository.getAccountRepository().getFlaggedAccounts(Account.FOUNDER_FLAG);
BigDecimal foundersCount = BigDecimal.valueOf(founderAccounts.size());
BigDecimal perFounderAmount = foundersAmount.divide(foundersCount, RoundingMode.DOWN);
long foundersCount = founderAccounts.size();
long perFounderAmount = foundersAmount / foundersCount;
LOGGER.trace(() -> String.format("Sharing remaining %s to %d founder%s, %s each",
foundersAmount.toPlainString(), founderAccounts.size(), (founderAccounts.size() != 1 ? "s" : ""),
perFounderAmount.toPlainString()));
Amounts.prettyAmount(foundersAmount),
founderAccounts.size(), (founderAccounts.size() != 1 ? "s" : ""),
Amounts.prettyAmount(perFounderAmount)));
List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
for (int a = 0; a < founderAccounts.size(); ++a) {
Account founderAccount = new Account(this.repository, founderAccounts.get(a).getAddress());
// If founder is minter in any online reward-shares then founder's amount is spread across these, otherwise founder gets whole amount.
/* Fixed version:
List<ExpandedAccount> founderExpandedAccounts = expandedAccounts.stream().filter(
accountInfo -> accountInfo.isMinterFounder &&
accountInfo.mintingAccountData.getAddress().equals(founderAccount.getAddress())
).collect(Collectors.toList());
*/
// Broken version:
List<ExpandedAccount> founderExpandedAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.isMinterFounder).collect(Collectors.toList());
if (founderExpandedAccounts.isEmpty()) {
// Simple case: no founder-as-minter reward-shares online so founder gets whole amount.
// founderAccount.setConfirmedBalance(Asset.QORT, founderAccount.getConfirmedBalance(Asset.QORT).add(perFounderAmount));
this.repository.getAccountRepository().modifyAssetBalance(founderAccount.getAddress(), Asset.QORT, perFounderAmount);
} else {
// Distribute over reward-shares
BigDecimal perFounderRewardShareAmount = perFounderAmount.divide(BigDecimal.valueOf(founderExpandedAccounts.size()), RoundingMode.DOWN);
long perFounderRewardShareAmount = perFounderAmount / founderExpandedAccounts.size();
for (int fea = 0; fea < founderExpandedAccounts.size(); ++fea)
founderExpandedAccounts.get(fea).distribute(perFounderRewardShareAmount);

View File

@ -4,7 +4,6 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.MathContext;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
@ -57,8 +56,9 @@ public class BlockChain {
private long transactionExpiryPeriod;
private BigDecimal unitFee;
private BigDecimal maxBytesPerUnitFee;
private BigDecimal minFeePerByte;
private long unscaledUnitFee; // calculated after unmarshal
private int maxBytesPerUnitFee;
/** Maximum acceptable timestamp disagreement offset in milliseconds. */
private long blockTimestampMargin;
@ -87,6 +87,7 @@ public class BlockChain {
public static class RewardByHeight {
public int height;
public BigDecimal reward;
public long unscaledReward; // reward * 1e8, calculated after unmarshal
}
List<RewardByHeight> rewardsByHeight;
@ -94,13 +95,18 @@ public class BlockChain {
public static class ShareByLevel {
public List<Integer> levels;
public BigDecimal share;
public long unscaledShare; // share * 1e8, calculated after unmarshal
}
List<ShareByLevel> sharesByLevel;
/** Share of block reward/fees to legacy QORA coin holders */
BigDecimal qoraHoldersShare;
/** Unscaled (* 1e8) share of block reward/fees to legacy QORA coin holders */
private long qoraHoldersUnscaledShare; // calculated after unmarshal
/** How many legacy QORA per 1 QORT of block reward. */
BigDecimal qoraPerQortReward;
/** How many legacy QORA per 1 QORT of block reward. */
private long unscaledQoraPerQortReward;
/**
* Number of minted blocks required to reach next level from previous.
@ -265,12 +271,12 @@ public class BlockChain {
return this.unitFee;
}
public BigDecimal getMaxBytesPerUnitFee() {
return this.maxBytesPerUnitFee;
public long getUnscaledUnitFee() {
return this.unscaledUnitFee;
}
public BigDecimal getMinFeePerByte() {
return this.minFeePerByte;
public int getMaxBytesPerUnitFee() {
return this.maxBytesPerUnitFee;
}
public long getTransactionExpiryPeriod() {
@ -318,10 +324,18 @@ public class BlockChain {
return this.qoraHoldersShare;
}
public long getQoraHoldersUnscaledShare() {
return this.qoraHoldersUnscaledShare;
}
public BigDecimal getQoraPerQortReward() {
return this.qoraPerQortReward;
}
public long getUnscaledQoraPerQortReward() {
return this.unscaledQoraPerQortReward;
}
public int getMinAccountLevelToMint() {
return this.minAccountLevelToMint;
}
@ -350,11 +364,11 @@ public class BlockChain {
// More complex getters for aspects that change by height or timestamp
public BigDecimal getRewardAtHeight(int ourHeight) {
public Long getRewardAtHeight(int ourHeight) {
// Scan through for reward at our height
for (int i = rewardsByHeight.size() - 1; i >= 0; --i)
if (rewardsByHeight.get(i).height <= ourHeight)
return rewardsByHeight.get(i).reward;
return rewardsByHeight.get(i).unscaledReward;
return null;
}
@ -416,9 +430,8 @@ public class BlockChain {
/** Minor normalization, cached value generation, etc. */
private void fixUp() {
this.maxBytesPerUnitFee = this.maxBytesPerUnitFee.setScale(8);
this.unitFee = this.unitFee.setScale(8);
this.minFeePerByte = this.unitFee.divide(this.maxBytesPerUnitFee, MathContext.DECIMAL32);
this.unscaledUnitFee = this.unitFee.unscaledValue().longValue();
// Pre-calculate cumulative blocks required for each level
int cumulativeBlocks = 0;
@ -430,6 +443,17 @@ public class BlockChain {
cumulativeBlocks += this.blocksNeededByLevel.get(level);
}
// Calculate unscaled long versions of block rewards by height
for (RewardByHeight rewardByHeight : this.rewardsByHeight)
rewardByHeight.unscaledReward = rewardByHeight.reward.setScale(8).unscaledValue().longValue();
// Calculate unscaled long versions of block reward shares-by-level
for (ShareByLevel shareByLevel : this.sharesByLevel)
shareByLevel.unscaledShare = shareByLevel.share.setScale(8).unscaledValue().longValue();
// Calculate unscaled long version of Qora-holders block reward share
this.qoraHoldersUnscaledShare = this.qoraHoldersShare.setScale(8).unscaledValue().longValue();
// Convert collections to unmodifiable form
this.rewardsByHeight = Collections.unmodifiableList(this.rewardsByHeight);
this.sharesByLevel = Collections.unmodifiableList(this.sharesByLevel);

View File

@ -2,7 +2,6 @@ package org.qortal.block;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -86,7 +85,7 @@ public class GenesisBlock extends Block {
// Add default values to transactions
transactionsData.stream().forEach(transactionData -> {
if (transactionData.getFee() == null)
transactionData.setFee(BigDecimal.ZERO.setScale(8));
transactionData.setFee(0L);
if (transactionData.getCreatorPublicKey() == null)
transactionData.setCreatorPublicKey(NullAccount.PUBLIC_KEY);
@ -97,14 +96,14 @@ public class GenesisBlock extends Block {
byte[] reference = GENESIS_BLOCK_REFERENCE;
int transactionCount = transactionsData.size();
BigDecimal totalFees = BigDecimal.ZERO.setScale(8);
long totalFees = 0;
byte[] minterPublicKey = NullAccount.PUBLIC_KEY;
byte[] bytesForSignature = getBytesForMinterSignature(info.timestamp, reference, minterPublicKey);
byte[] minterSignature = calcGenesisMinterSignature(bytesForSignature);
byte[] transactionsSignature = calcGenesisTransactionsSignature();
int height = 1;
int atCount = 0;
BigDecimal atFees = BigDecimal.ZERO.setScale(8);
long atFees = 0;
genesisBlockData = new BlockData(info.version, reference, transactionCount, totalFees, transactionsSignature, height, info.timestamp,
minterPublicKey, minterSignature, atCount, atFees);

View File

@ -1,18 +1,21 @@
package org.qortal.data;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
// All properties to be converted to JSON via JAXB
@XmlAccessorType(XmlAccessType.FIELD)
public class PaymentData {
// Properties
private String recipient;
private long assetId;
private BigDecimal amount;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long amount;
// Constructors
@ -20,7 +23,7 @@ public class PaymentData {
protected PaymentData() {
}
public PaymentData(String recipient, long assetId, BigDecimal amount) {
public PaymentData(String recipient, long assetId, long amount) {
this.recipient = recipient;
this.assetId = assetId;
this.amount = amount;
@ -36,7 +39,7 @@ public class PaymentData {
return this.assetId;
}
public BigDecimal getAmount() {
public long getAmount() {
return this.amount;
}

View File

@ -1,9 +1,9 @@
package org.qortal.data.account;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
// All properties to be converted to JSON via JAXB
@XmlAccessorType(XmlAccessType.FIELD)
@ -12,7 +12,9 @@ public class AccountBalanceData {
// Properties
private String address;
private long assetId;
private BigDecimal balance;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long balance;
// Not always present:
private Integer height;
@ -24,19 +26,19 @@ public class AccountBalanceData {
protected AccountBalanceData() {
}
public AccountBalanceData(String address, long assetId, BigDecimal balance) {
public AccountBalanceData(String address, long assetId, long balance) {
this.address = address;
this.assetId = assetId;
this.balance = balance;
}
public AccountBalanceData(String address, long assetId, BigDecimal balance, int height) {
public AccountBalanceData(String address, long assetId, long balance, int height) {
this(address, assetId, balance);
this.height = height;
}
public AccountBalanceData(String address, long assetId, BigDecimal balance, String assetName) {
public AccountBalanceData(String address, long assetId, long balance, String assetName) {
this(address, assetId, balance);
this.assetName = assetName;
@ -52,11 +54,11 @@ public class AccountBalanceData {
return this.assetId;
}
public BigDecimal getBalance() {
public long getBalance() {
return this.balance;
}
public void setBalance(BigDecimal balance) {
public void setBalance(long balance) {
this.balance = balance;
}

View File

@ -1,18 +1,23 @@
package org.qortal.data.account;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
// All properties to be converted to JSON via JAXB
@XmlAccessorType(XmlAccessType.FIELD)
public class QortFromQoraData {
// Properties
private String address;
// Not always present:
private BigDecimal finalQortFromQora;
// Not always present
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private Long finalQortFromQora;
// Not always present
private Integer finalBlockHeight;
// Constructors
@ -21,7 +26,7 @@ public class QortFromQoraData {
protected QortFromQoraData() {
}
public QortFromQoraData(String address, BigDecimal finalQortFromQora, Integer finalBlockHeight) {
public QortFromQoraData(String address, Long finalQortFromQora, Integer finalBlockHeight) {
this.address = address;
this.finalQortFromQora = finalQortFromQora;
this.finalBlockHeight = finalBlockHeight;
@ -33,11 +38,11 @@ public class QortFromQoraData {
return this.address;
}
public BigDecimal getFinalQortFromQora() {
public Long getFinalQortFromQora() {
return this.finalQortFromQora;
}
public void setFinalQortFromQora(BigDecimal finalQortFromQora) {
public void setFinalQortFromQora(Long finalQortFromQora) {
this.finalQortFromQora = finalQortFromQora;
}

View File

@ -23,7 +23,11 @@ public class RewardShareData {
private String recipient;
private byte[] rewardSharePublicKey;
private BigDecimal sharePercent;
// JAXB to use separate getter
@XmlTransient
@Schema(hidden = true)
private int sharePercent;
// Constructors
@ -32,7 +36,7 @@ public class RewardShareData {
}
// Used when fetching from repository
public RewardShareData(byte[] minterPublicKey, String minter, String recipient, byte[] rewardSharePublicKey, BigDecimal sharePercent) {
public RewardShareData(byte[] minterPublicKey, String minter, String recipient, byte[] rewardSharePublicKey, int sharePercent) {
this.minterPublicKey = minterPublicKey;
this.minter = minter;
this.recipient = recipient;
@ -58,13 +62,21 @@ public class RewardShareData {
return this.rewardSharePublicKey;
}
public BigDecimal getSharePercent() {
/** Returns share percent scaled by 100. i.e. 12.34% is represented by 1234 */
public int getSharePercent() {
return this.sharePercent;
}
// Some JAXB/API-related getters
@XmlElement(name = "mintingAccount")
public String getMintingAccount() {
return this.minter;
}
@XmlElement(name = "sharePercent")
public BigDecimal getSharePercentJaxb() {
return BigDecimal.valueOf(this.sharePercent, 2);
}
}

View File

@ -27,7 +27,7 @@ public class AssetData {
// Constructors
// necessary for JAX-RS serialization
// necessary for JAXB serialization
protected AssetData() {
}

View File

@ -1,10 +1,9 @@
package org.qortal.data.asset;
import java.math.BigDecimal;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.qortal.crypto.Crypto;
@ -26,13 +25,16 @@ public class OrderData implements Comparable<OrderData> {
private long wantAssetId;
@Schema(description = "amount of highest-assetID asset to trade")
private BigDecimal amount;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long amount;
@Schema(description = "price in lowest-assetID asset / highest-assetID asset")
private BigDecimal price;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long price;
@Schema(description = "how much of \"amount\" has traded")
private BigDecimal fulfilled;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long fulfilled;
private long timestamp;
@ -73,26 +75,23 @@ public class OrderData implements Comparable<OrderData> {
if (this.creator == null && this.creatorPublicKey != null)
this.creator = Crypto.toAddress(this.creatorPublicKey);
this.amountAssetId = Math.max(this.haveAssetId, this.wantAssetId);
// If we don't have the extra asset name fields then we can't fill in the others
if (this.haveAssetName == null)
return;
// TODO: fill in for 'old' pricing scheme
// '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,
public OrderData(byte[] orderId, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, long amount, long fulfilled, long price, long timestamp,
boolean isClosed, boolean isFulfilled, String haveAssetName, String wantAssetName) {
this.orderId = orderId;
this.creatorPublicKey = creatorPublicKey;
@ -110,13 +109,13 @@ public class OrderData implements Comparable<OrderData> {
}
/** 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) {
public OrderData(byte[] orderId, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, long amount, long fulfilled, long 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) {
this(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, BigDecimal.ZERO.setScale(8), price, timestamp, false, false);
public OrderData(byte[] orderId, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, long amount, long price, long timestamp) {
this(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, 0 /*fulfilled*/, price, timestamp, false, false);
}
// Getters/setters
@ -137,19 +136,19 @@ public class OrderData implements Comparable<OrderData> {
return this.wantAssetId;
}
public BigDecimal getAmount() {
public long getAmount() {
return this.amount;
}
public BigDecimal getFulfilled() {
public long getFulfilled() {
return this.fulfilled;
}
public void setFulfilled(BigDecimal fulfilled) {
public void setFulfilled(long fulfilled) {
this.fulfilled = fulfilled;
}
public BigDecimal getPrice() {
public long getPrice() {
return this.price;
}
@ -198,7 +197,7 @@ public class OrderData implements Comparable<OrderData> {
@Override
public int compareTo(OrderData orderData) {
// Compare using prices
return this.price.compareTo(orderData.getPrice());
return Long.compare(this.price, orderData.getPrice());
}
}

View File

@ -20,9 +20,7 @@ public class RecentTradeData {
private BigDecimal amount;
@Schema(
description = "when trade happened"
)
@Schema(description = "when trade happened")
private long timestamp;
// Constructors
@ -31,11 +29,11 @@ public class RecentTradeData {
protected RecentTradeData() {
}
public RecentTradeData(long assetId, long otherAssetId, BigDecimal otherAmount, BigDecimal amount, long timestamp) {
public RecentTradeData(long assetId, long otherAssetId, long otherAmount, long amount, long timestamp) {
this.assetId = assetId;
this.otherAssetId = otherAssetId;
this.otherAmount = otherAmount;
this.amount = amount;
this.otherAmount = BigDecimal.valueOf(otherAmount, 8);
this.amount = BigDecimal.valueOf(amount, 8);
this.timestamp = timestamp;
}

View File

@ -1,12 +1,11 @@
package org.qortal.data.asset;
import java.math.BigDecimal;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
@ -24,17 +23,17 @@ public class TradeData {
@XmlElement(name = "targetOrderId")
private byte[] target;
@Schema(name = "targetAmount", description = "amount traded from target")
@XmlElement(name = "targetAmount")
private BigDecimal targetAmount;
@Schema(description = "amount traded from target")
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long targetAmount;
@Schema(name = "initiatorAmount", description = "amount traded from initiator")
@XmlElement(name = "initiatorAmount")
private BigDecimal initiatorAmount;
@Schema(description = "amount traded from initiator")
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long initiatorAmount;
@Schema(name = "initiatorSaving", description = "amount refunded to initiator due to price improvement")
@XmlElement(name = "initiatorSaving")
private BigDecimal initiatorSaving;
@Schema(description = "amount refunded to initiator due to price improvement")
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long initiatorSaving;
@Schema(description = "when trade happened")
private long timestamp;
@ -95,7 +94,7 @@ public class TradeData {
}
}
public TradeData(byte[] initiator, byte[] target, BigDecimal targetAmount, BigDecimal initiatorAmount, BigDecimal initiatorSaving, long timestamp,
public TradeData(byte[] initiator, byte[] target, long targetAmount, long initiatorAmount, long initiatorSaving, long timestamp,
Long haveAssetId, String haveAssetName, Long wantAssetId, String wantAssetName) {
this.initiator = initiator;
this.target = target;
@ -110,7 +109,7 @@ public class TradeData {
this.wantAssetName = wantAssetName;
}
public TradeData(byte[] initiator, byte[] target, BigDecimal targetAmount, BigDecimal initiatorAmount, BigDecimal initiatorSaving, long timestamp) {
public TradeData(byte[] initiator, byte[] target, long targetAmount, long initiatorAmount, long initiatorSaving, long timestamp) {
this(initiator, target, targetAmount, initiatorAmount, initiatorSaving, timestamp, null, null, null, null);
}
@ -124,15 +123,15 @@ public class TradeData {
return this.target;
}
public BigDecimal getTargetAmount() {
public long getTargetAmount() {
return this.targetAmount;
}
public BigDecimal getInitiatorAmount() {
public long getInitiatorAmount() {
return this.initiatorAmount;
}
public BigDecimal getInitiatorSaving() {
public long getInitiatorSaving() {
return this.initiatorSaving;
}

View File

@ -1,7 +1,5 @@
package org.qortal.data.at;
import java.math.BigDecimal;
public class ATData {
// Properties
@ -16,12 +14,12 @@ public class ATData {
private boolean isFinished;
private boolean hadFatalError;
private boolean isFrozen;
private BigDecimal frozenBalance;
private Long frozenBalance;
// Constructors
public ATData(String ATAddress, byte[] creatorPublicKey, long creation, int version, long assetId, byte[] codeBytes, boolean isSleeping,
Integer sleepUntilHeight, boolean isFinished, boolean hadFatalError, boolean isFrozen, BigDecimal frozenBalance) {
Integer sleepUntilHeight, boolean isFinished, boolean hadFatalError, boolean isFrozen, Long frozenBalance) {
this.ATAddress = ATAddress;
this.creatorPublicKey = creatorPublicKey;
this.creation = creation;
@ -36,16 +34,6 @@ public class ATData {
this.frozenBalance = frozenBalance;
}
public ATData(String ATAddress, byte[] creatorPublicKey, long creation, int version, long assetId, byte[] codeBytes, boolean isSleeping,
Integer sleepUntilHeight, boolean isFinished, boolean hadFatalError, boolean isFrozen, Long frozenBalance) {
this(ATAddress, creatorPublicKey, creation, version, assetId, codeBytes, isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen,
(BigDecimal) null);
// Convert Long frozenBalance to BigDecimal
if (frozenBalance != null)
this.frozenBalance = BigDecimal.valueOf(frozenBalance, 8);
}
// Getters / setters
public String getATAddress() {
@ -112,11 +100,11 @@ public class ATData {
this.isFrozen = isFrozen;
}
public BigDecimal getFrozenBalance() {
public Long getFrozenBalance() {
return this.frozenBalance;
}
public void setFrozenBalance(BigDecimal frozenBalance) {
public void setFrozenBalance(Long frozenBalance) {
this.frozenBalance = frozenBalance;
}

View File

@ -1,7 +1,5 @@
package org.qortal.data.at;
import java.math.BigDecimal;
public class ATStateData {
// Properties
@ -10,12 +8,12 @@ public class ATStateData {
private Long creation;
private byte[] stateData;
private byte[] stateHash;
private BigDecimal fees;
private Long fees;
// Constructors
/** Create new ATStateData */
public ATStateData(String ATAddress, Integer height, Long creation, byte[] stateData, byte[] stateHash, BigDecimal fees) {
public ATStateData(String ATAddress, Integer height, Long creation, byte[] stateData, byte[] stateHash, Long fees) {
this.ATAddress = ATAddress;
this.height = height;
this.creation = creation;
@ -25,7 +23,7 @@ public class ATStateData {
}
/** For recreating per-block ATStateData from repository where not all info is needed */
public ATStateData(String ATAddress, int height, byte[] stateHash, BigDecimal fees) {
public ATStateData(String ATAddress, int height, byte[] stateHash, Long fees) {
this(ATAddress, height, null, null, stateHash, fees);
}
@ -35,7 +33,7 @@ public class ATStateData {
}
/** For creating ATStateData from serialized bytes when we don't have all the info */
public ATStateData(String ATAddress, byte[] stateHash, BigDecimal fees) {
public ATStateData(String ATAddress, byte[] stateHash, Long fees) {
this(ATAddress, null, null, null, stateHash, fees);
}
@ -66,7 +64,7 @@ public class ATStateData {
return this.stateHash;
}
public BigDecimal getFees() {
public Long getFees() {
return this.fees;
}

View File

@ -3,11 +3,11 @@ package org.qortal.data.block;
import com.google.common.primitives.Bytes;
import java.io.Serializable;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.qortal.crypto.Crypto;
@ -23,14 +23,20 @@ public class BlockData implements Serializable {
private int version;
private byte[] reference;
private int transactionCount;
private BigDecimal totalFees;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long totalFees;
private byte[] transactionsSignature;
private Integer height;
private long timestamp;
private byte[] minterPublicKey;
private byte[] minterSignature;
private int atCount;
private BigDecimal atFees;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long atFees;
private byte[] encodedOnlineAccounts;
private int onlineAccountsCount;
private Long onlineAccountsTimestamp;
@ -42,8 +48,8 @@ public class BlockData implements Serializable {
protected BlockData() {
}
public BlockData(int version, byte[] reference, int transactionCount, BigDecimal totalFees, byte[] transactionsSignature, Integer height, long timestamp,
byte[] minterPublicKey, byte[] minterSignature, int atCount, BigDecimal atFees,
public BlockData(int version, byte[] reference, int transactionCount, long totalFees, byte[] transactionsSignature, Integer height, long timestamp,
byte[] minterPublicKey, byte[] minterSignature, int atCount, long atFees,
byte[] encodedOnlineAccounts, int onlineAccountsCount, Long onlineAccountsTimestamp, byte[] onlineAccountsSignatures) {
this.version = version;
this.reference = reference;
@ -67,8 +73,8 @@ public class BlockData implements Serializable {
this.signature = null;
}
public BlockData(int version, byte[] reference, int transactionCount, BigDecimal totalFees, byte[] transactionsSignature, Integer height, long timestamp,
byte[] minterPublicKey, byte[] minterSignature, int atCount, BigDecimal atFees) {
public BlockData(int version, byte[] reference, int transactionCount, long totalFees, byte[] transactionsSignature, Integer height, long timestamp,
byte[] minterPublicKey, byte[] minterSignature, int atCount, long atFees) {
this(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, minterPublicKey, minterSignature, atCount, atFees,
null, 0, null, null);
}
@ -103,11 +109,11 @@ public class BlockData implements Serializable {
this.transactionCount = transactionCount;
}
public BigDecimal getTotalFees() {
public long getTotalFees() {
return this.totalFees;
}
public void setTotalFees(BigDecimal totalFees) {
public void setTotalFees(long totalFees) {
this.totalFees = totalFees;
}
@ -151,11 +157,11 @@ public class BlockData implements Serializable {
this.atCount = atCount;
}
public BigDecimal getATFees() {
public long getATFees() {
return this.atFees;
}
public void setATFees(BigDecimal atFees) {
public void setATFees(long atFees) {
this.atFees = atFees;
}

View File

@ -1,10 +1,9 @@
package org.qortal.data.naming;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import io.swagger.v3.oas.annotations.media.Schema;
@ -20,17 +19,14 @@ public class NameData {
private Long updated;
// No need to expose this via API
@XmlTransient
@Schema(
hidden = true
)
@Schema(hidden = true)
private byte[] reference;
private boolean isForSale;
private BigDecimal salePrice;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private Long salePrice;
// For internal use
@XmlTransient
@Schema(
hidden = true
)
@Schema(hidden = true)
private int creationGroupId;
// Constructors
@ -39,7 +35,7 @@ public class NameData {
protected NameData() {
}
public NameData(String owner, String name, String data, long registered, Long updated, byte[] reference, boolean isForSale, BigDecimal salePrice,
public NameData(String owner, String name, String data, long registered, Long updated, byte[] reference, boolean isForSale, Long salePrice,
int creationGroupId) {
this.owner = owner;
this.name = name;
@ -106,11 +102,11 @@ public class NameData {
this.isForSale = isForSale;
}
public BigDecimal getSalePrice() {
public Long getSalePrice() {
return this.salePrice;
}
public void setSalePrice(BigDecimal salePrice) {
public void setSalePrice(Long salePrice) {
this.salePrice = salePrice;
}

View File

@ -5,6 +5,7 @@ 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.qortal.account.NullAccount;
import org.qortal.transaction.Transaction.TransactionType;
@ -17,10 +18,19 @@ import io.swagger.v3.oas.annotations.media.Schema;
public class ATTransactionData extends TransactionData {
// Properties
private String atAddress;
private String recipient;
private BigDecimal amount;
@XmlTransient
@Schema(hidden = true)
// Not always present
private Long amount;
// Not always present
private Long assetId;
private byte[] message;
// Constructors
@ -35,7 +45,7 @@ public class ATTransactionData extends TransactionData {
}
/** From repository */
public ATTransactionData(BaseTransactionData baseTransactionData, String atAddress, String recipient, BigDecimal amount, Long assetId, byte[] message) {
public ATTransactionData(BaseTransactionData baseTransactionData, String atAddress, String recipient, Long amount, Long assetId, byte[] message) {
super(TransactionType.AT, baseTransactionData);
this.creatorPublicKey = NullAccount.PUBLIC_KEY;
@ -56,7 +66,7 @@ public class ATTransactionData extends TransactionData {
return this.recipient;
}
public BigDecimal getAmount() {
public Long getAmount() {
return this.amount;
}
@ -68,4 +78,14 @@ public class ATTransactionData extends TransactionData {
return this.message;
}
// Some JAXB/API-related getters
@Schema(name = "amount")
public BigDecimal getAmountJaxb() {
if (this.amount == null)
return null;
return BigDecimal.valueOf(this.amount, 8);
}
}

View File

@ -1,13 +1,11 @@
package org.qortal.data.transaction;
import java.math.BigDecimal;
import org.qortal.transaction.Transaction.ApprovalStatus;
public class BaseTransactionData extends TransactionData {
/** Constructor for use by repository. */
public BaseTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, BigDecimal fee,
public BaseTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, Long fee,
ApprovalStatus approvalStatus, Integer blockHeight, Integer approvalHeight, byte[] signature) {
this.timestamp = timestamp;
this.txGroupId = txGroupId;
@ -21,7 +19,7 @@ public class BaseTransactionData extends TransactionData {
}
/** Constructor for use by transaction transformer. */
public BaseTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, BigDecimal fee, byte[] signature) {
public BaseTransactionData(long timestamp, int txGroupId, byte[] reference, byte[] creatorPublicKey, Long fee, byte[] signature) {
this(timestamp, txGroupId, reference, creatorPublicKey, fee, null, null, null, signature);
}

View File

@ -1,7 +1,5 @@
package org.qortal.data.transaction;
import java.math.BigDecimal;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@ -18,16 +16,17 @@ import io.swagger.v3.oas.annotations.media.Schema;
public class BuyNameTransactionData extends TransactionData {
// Properties
@Schema(description = "buyer's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
private byte[] buyerPublicKey;
@Schema(description = "which name to buy", example = "my-name")
private String name;
@Schema(description = "selling price", example = "123.456")
@XmlJavaTypeAdapter(
type = BigDecimal.class,
value = org.qortal.api.BigDecimalTypeAdapter.class
)
private BigDecimal amount;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long amount;
@Schema(description = "seller's address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v")
private String seller;
@ -49,7 +48,7 @@ public class BuyNameTransactionData extends TransactionData {
/** From repository */
public BuyNameTransactionData(BaseTransactionData baseTransactionData,
String name, BigDecimal amount, String seller, byte[] nameReference) {
String name, long amount, String seller, byte[] nameReference) {
super(TransactionType.BUY_NAME, baseTransactionData);
this.buyerPublicKey = baseTransactionData.creatorPublicKey;
@ -60,7 +59,7 @@ public class BuyNameTransactionData extends TransactionData {
}
/** From network/API */
public BuyNameTransactionData(BaseTransactionData baseTransactionData, String name, BigDecimal amount, String seller) {
public BuyNameTransactionData(BaseTransactionData baseTransactionData, String name, long amount, String seller) {
this(baseTransactionData, name, amount, seller, null);
}
@ -74,7 +73,7 @@ public class BuyNameTransactionData extends TransactionData {
return this.name;
}
public BigDecimal getAmount() {
public long getAmount() {
return this.amount;
}

View File

@ -1,11 +1,10 @@
package org.qortal.data.transaction;
import java.math.BigDecimal;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.qortal.transaction.Transaction.TransactionType;
@ -18,14 +17,20 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
public class CreateAssetOrderTransactionData extends TransactionData {
// Properties
@Schema(description = "ID of asset on offer to give by order creator", example = "1")
private long haveAssetId;
@Schema(description = "ID of asset wanted to receive by order creator", example = "0")
private long wantAssetId;
@Schema(description = "amount of highest-assetID asset to trade")
private BigDecimal amount;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long amount;
@Schema(description = "price in lowest-assetID asset / highest-assetID asset")
private BigDecimal price;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long price;
// Used by API - not always present
@ -70,7 +75,7 @@ public class CreateAssetOrderTransactionData extends TransactionData {
/** Constructs using data from repository, including optional asset names. */
public CreateAssetOrderTransactionData(BaseTransactionData baseTransactionData,
long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal price, String haveAssetName, String wantAssetName) {
long haveAssetId, long wantAssetId, long amount, long price, String haveAssetName, String wantAssetName) {
super(TransactionType.CREATE_ASSET_ORDER, baseTransactionData);
this.haveAssetId = haveAssetId;
@ -83,7 +88,7 @@ public class CreateAssetOrderTransactionData extends TransactionData {
}
/** Constructor excluding optional asset names. */
public CreateAssetOrderTransactionData(BaseTransactionData baseTransactionData, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal price) {
public CreateAssetOrderTransactionData(BaseTransactionData baseTransactionData, long haveAssetId, long wantAssetId, long amount, long price) {
this(baseTransactionData, haveAssetId, wantAssetId, amount, price, null, null);
}
@ -97,11 +102,11 @@ public class CreateAssetOrderTransactionData extends TransactionData {
return this.wantAssetId;
}
public BigDecimal getAmount() {
public long getAmount() {
return this.amount;
}
public BigDecimal getPrice() {
public long getPrice() {
return this.price;
}

View File

@ -1,9 +1,8 @@
package org.qortal.data.transaction;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.qortal.transaction.Transaction.TransactionType;
@ -20,7 +19,8 @@ public class DeployAtTransactionData extends TransactionData {
private String aTType;
private String tags;
private byte[] creationBytes;
private BigDecimal amount;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long amount;
private long assetId;
private String aTAddress;
@ -33,7 +33,7 @@ public class DeployAtTransactionData extends TransactionData {
/** From repository */
public DeployAtTransactionData(BaseTransactionData baseTransactionData,
String aTAddress, String name, String description, String aTType, String tags, byte[] creationBytes, BigDecimal amount, long assetId) {
String aTAddress, String name, String description, String aTType, String tags, byte[] creationBytes, long amount, long assetId) {
super(TransactionType.DEPLOY_AT, baseTransactionData);
this.aTAddress = aTAddress;
@ -48,7 +48,7 @@ public class DeployAtTransactionData extends TransactionData {
/** From network/API */
public DeployAtTransactionData(BaseTransactionData baseTransactionData,
String name, String description, String aTType, String tags, byte[] creationBytes, BigDecimal amount, long assetId) {
String name, String description, String aTType, String tags, byte[] creationBytes, long amount, long assetId) {
this(baseTransactionData, null, name, description, aTType, tags, creationBytes, amount, assetId);
}
@ -74,7 +74,7 @@ public class DeployAtTransactionData extends TransactionData {
return this.creationBytes;
}
public BigDecimal getAmount() {
public long getAmount() {
return this.amount;
}

View File

@ -1,9 +1,8 @@
package org.qortal.data.transaction;
import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
import org.qortal.asset.Asset;
@ -13,18 +12,18 @@ import io.swagger.v3.oas.annotations.media.Schema;
// All properties to be converted to JSON via JAXB
@XmlAccessorType(XmlAccessType.FIELD)
@Schema(
allOf = {
TransactionData.class
}
)
@Schema(allOf = {TransactionData.class})
//JAXB: use this subclass if XmlDiscriminatorNode matches XmlDiscriminatorValue below:
@XmlDiscriminatorValue("GENESIS")
public class GenesisTransactionData extends TransactionData {
// Properties
private String recipient;
private BigDecimal amount;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long amount;
private long assetId;
// Constructors
@ -35,7 +34,7 @@ public class GenesisTransactionData extends TransactionData {
}
/** From repository */
public GenesisTransactionData(BaseTransactionData baseTransactionData, String recipient, BigDecimal amount, long assetId) {
public GenesisTransactionData(BaseTransactionData baseTransactionData, String recipient, long amount, long assetId) {
super(TransactionType.GENESIS, baseTransactionData);
this.recipient = recipient;
@ -44,7 +43,7 @@ public class GenesisTransactionData extends TransactionData {
}
/** From repository (where asset locked to QORT) */
public GenesisTransactionData(BaseTransactionData baseTransactionData, String recipient, BigDecimal amount) {
public GenesisTransactionData(BaseTransactionData baseTransactionData, String recipient, long amount) {
this(baseTransactionData, recipient, amount, Asset.QORT);
}
@ -54,7 +53,7 @@ public class GenesisTransactionData extends TransactionData {
return this.recipient;
}
public BigDecimal getAmount() {
public long getAmount() {
return this.amount;
}

View File

@ -1,10 +1,9 @@
package org.qortal.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.adapters.XmlJavaTypeAdapter;
import org.qortal.asset.Asset;
import org.qortal.transaction.Transaction.TransactionType;
@ -21,7 +20,8 @@ public class MessageTransactionData extends TransactionData {
private int version;
private String recipient;
private Long assetId;
private BigDecimal amount;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long amount;
private byte[] data;
private boolean isText;
private boolean isEncrypted;
@ -38,7 +38,7 @@ public class MessageTransactionData extends TransactionData {
}
public MessageTransactionData(BaseTransactionData baseTransactionData,
int version, String recipient, Long assetId, BigDecimal amount, byte[] data, boolean isText, boolean isEncrypted) {
int version, String recipient, Long assetId, long amount, byte[] data, boolean isText, boolean isEncrypted) {
super(TransactionType.MESSAGE, baseTransactionData);
this.senderPublicKey = baseTransactionData.creatorPublicKey;
@ -74,7 +74,7 @@ public class MessageTransactionData extends TransactionData {
return this.assetId;
}
public BigDecimal getAmount() {
public long getAmount() {
return this.amount;
}

View File

@ -1,7 +1,5 @@
package org.qortal.data.transaction;
import java.math.BigDecimal;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@ -17,16 +15,16 @@ import io.swagger.v3.oas.annotations.media.Schema;
public class PaymentTransactionData extends TransactionData {
// Properties
@Schema(description = "sender's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
private byte[] senderPublicKey;
@Schema(description = "recipient's address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v")
private String recipient;
@Schema(description = "amount to send", example = "123.456")
@XmlJavaTypeAdapter(
type = BigDecimal.class,
value = org.qortal.api.BigDecimalTypeAdapter.class
)
private BigDecimal amount;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long amount;
// Constructors
@ -40,7 +38,7 @@ public class PaymentTransactionData extends TransactionData {
}
/** From repository */
public PaymentTransactionData(BaseTransactionData baseTransactionData, String recipient, BigDecimal amount) {
public PaymentTransactionData(BaseTransactionData baseTransactionData, String recipient, long amount) {
super(TransactionType.PAYMENT, baseTransactionData);
this.senderPublicKey = baseTransactionData.creatorPublicKey;
@ -58,7 +56,7 @@ public class PaymentTransactionData extends TransactionData {
return this.recipient;
}
public BigDecimal getAmount() {
public long getAmount() {
return this.amount;
}

View File

@ -5,6 +5,7 @@ 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.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
@ -27,13 +28,15 @@ public class RewardShareTransactionData extends TransactionData {
@Schema(example = "reward_share_public_key")
private byte[] rewardSharePublicKey;
@Schema(description = "Percentage of block rewards that minter shares to recipient, or negative value to cancel existing reward-share")
private BigDecimal sharePercent;
// JAXB will use special getter below
@XmlTransient
@Schema(hidden = true)
private int sharePercent;
// No need to ever expose this via API
@XmlTransient
@Schema(hidden = true)
private BigDecimal previousSharePercent;
private Integer previousSharePercent;
// Constructors
@ -48,7 +51,7 @@ public class RewardShareTransactionData extends TransactionData {
/** From repository */
public RewardShareTransactionData(BaseTransactionData baseTransactionData,
String recipient, byte[] rewardSharePublicKey, BigDecimal sharePercent, BigDecimal previousSharePercent) {
String recipient, byte[] rewardSharePublicKey, int sharePercent, Integer previousSharePercent) {
super(TransactionType.REWARD_SHARE, baseTransactionData);
this.minterPublicKey = baseTransactionData.creatorPublicKey;
@ -60,7 +63,7 @@ public class RewardShareTransactionData extends TransactionData {
/** From network/API */
public RewardShareTransactionData(BaseTransactionData baseTransactionData,
String recipient, byte[] rewardSharePublicKey, BigDecimal sharePercent) {
String recipient, byte[] rewardSharePublicKey, int sharePercent) {
this(baseTransactionData, recipient, rewardSharePublicKey, sharePercent, null);
}
@ -78,16 +81,24 @@ public class RewardShareTransactionData extends TransactionData {
return this.rewardSharePublicKey;
}
public BigDecimal getSharePercent() {
public int getSharePercent() {
return this.sharePercent;
}
public BigDecimal getPreviousSharePercent() {
public Integer getPreviousSharePercent() {
return this.previousSharePercent;
}
public void setPreviousSharePercent(BigDecimal previousSharePercent) {
public void setPreviousSharePercent(Integer previousSharePercent) {
this.previousSharePercent = previousSharePercent;
}
// Special JAXB getters
@Schema(name = "sharePercent", description = "Percentage of block rewards that minter shares to recipient, or negative value to cancel existing reward-share")
@XmlElement(name = "sharePercent")
public BigDecimal getSharePercentJaxb() {
return BigDecimal.valueOf(this.sharePercent, 2);
}
}

View File

@ -1,7 +1,5 @@
package org.qortal.data.transaction;
import java.math.BigDecimal;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@ -17,16 +15,16 @@ import io.swagger.v3.oas.annotations.media.Schema;
public class SellNameTransactionData extends TransactionData {
// Properties
@Schema(description = "owner's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
private byte[] ownerPublicKey;
@Schema(description = "which name to sell", example = "my-name")
private String name;
@Schema(description = "selling price", example = "123.456")
@XmlJavaTypeAdapter(
type = BigDecimal.class,
value = org.qortal.api.BigDecimalTypeAdapter.class
)
private BigDecimal amount;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long amount;
// Constructors
@ -39,7 +37,7 @@ public class SellNameTransactionData extends TransactionData {
this.creatorPublicKey = this.ownerPublicKey;
}
public SellNameTransactionData(BaseTransactionData baseTransactionData, String name, BigDecimal amount) {
public SellNameTransactionData(BaseTransactionData baseTransactionData, String name, long amount) {
super(TransactionType.SELL_NAME, baseTransactionData);
this.ownerPublicKey = baseTransactionData.creatorPublicKey;
@ -57,7 +55,7 @@ public class SellNameTransactionData extends TransactionData {
return this.name;
}
public BigDecimal getAmount() {
public long getAmount() {
return this.amount;
}

View File

@ -58,8 +58,9 @@ public abstract class TransactionData {
protected long timestamp;
@Schema(description = "sender's last transaction ID", example = "real_transaction_reference_in_base58")
protected byte[] reference;
@Schema(description = "fee for processing transaction", example = "1.0")
protected BigDecimal fee;
@XmlTransient
@Schema(hidden = true)
protected Long fee; // can be null if fee not calculated yet
@Schema(accessMode = AccessMode.READ_ONLY, description = "signature for transaction's raw bytes, using sender's private key", example = "real_transaction_signature_in_base58")
protected byte[] signature;
@Schema(description = "groupID for this transaction")
@ -138,11 +139,11 @@ public abstract class TransactionData {
this.creatorPublicKey = creatorPublicKey;
}
public BigDecimal getFee() {
public Long getFee() {
return this.fee;
}
public void setFee(BigDecimal fee) {
public void setFee(Long fee) {
this.fee = fee;
}
@ -183,6 +184,14 @@ public abstract class TransactionData {
// JAXB special
@Schema(name = "fee", description = "fee for processing transaction", example = "0.0001")
protected BigDecimal getFeeJaxb() {
if (this.fee == null)
return null;
return BigDecimal.valueOf(this.fee, 8);
}
@XmlElement(name = "creatorAddress")
protected String getCreatorAddress() {
return Crypto.toAddress(this.creatorPublicKey);

View File

@ -1,10 +1,9 @@
package org.qortal.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.adapters.XmlJavaTypeAdapter;
import org.qortal.transaction.Transaction.TransactionType;
@ -17,11 +16,15 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
public class TransferAssetTransactionData extends TransactionData {
// Properties
@Schema(example = "sender_public_key")
private byte[] senderPublicKey;
private String recipient;
private BigDecimal amount;
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
private long amount;
private long assetId;
// Used by API - not always present
@ -40,7 +43,7 @@ public class TransferAssetTransactionData extends TransactionData {
}
/** Constructs using data from repository, including optional assetName. */
public TransferAssetTransactionData(BaseTransactionData baseTransactionData, String recipient, BigDecimal amount, long assetId, String assetName) {
public TransferAssetTransactionData(BaseTransactionData baseTransactionData, String recipient, long amount, long assetId, String assetName) {
super(TransactionType.TRANSFER_ASSET, baseTransactionData);
this.senderPublicKey = baseTransactionData.creatorPublicKey;
@ -51,7 +54,7 @@ public class TransferAssetTransactionData extends TransactionData {
}
/** Constructor excluding optional assetName. */
public TransferAssetTransactionData(BaseTransactionData baseTransactionData, String recipient, BigDecimal amount, long assetId) {
public TransferAssetTransactionData(BaseTransactionData baseTransactionData, String recipient, long amount, long assetId) {
this(baseTransactionData, recipient, amount, assetId, null);
}
@ -65,7 +68,7 @@ public class TransferAssetTransactionData extends TransactionData {
return this.recipient;
}
public BigDecimal getAmount() {
public long getAmount() {
return this.amount;
}

View File

@ -158,13 +158,13 @@ public class Name {
// Update seller's balance
Account seller = new Account(this.repository, this.nameData.getOwner());
seller.setConfirmedBalance(Asset.QORT, seller.getConfirmedBalance(Asset.QORT).add(buyNameTransactionData.getAmount()));
seller.setConfirmedBalance(Asset.QORT, seller.getConfirmedBalance(Asset.QORT) + buyNameTransactionData.getAmount());
// Set new owner
Account buyer = new PublicKeyAccount(this.repository, buyNameTransactionData.getBuyerPublicKey());
this.nameData.setOwner(buyer.getAddress());
// Update buyer's balance
buyer.setConfirmedBalance(Asset.QORT, buyer.getConfirmedBalance(Asset.QORT).subtract(buyNameTransactionData.getAmount()));
buyer.setConfirmedBalance(Asset.QORT, buyer.getConfirmedBalance(Asset.QORT) - buyNameTransactionData.getAmount());
// Update reference in transaction data
buyNameTransactionData.setNameReference(this.nameData.getReference());
@ -189,14 +189,14 @@ public class Name {
// Revert buyer's balance
Account buyer = new PublicKeyAccount(this.repository, buyNameTransactionData.getBuyerPublicKey());
buyer.setConfirmedBalance(Asset.QORT, buyer.getConfirmedBalance(Asset.QORT).add(buyNameTransactionData.getAmount()));
buyer.setConfirmedBalance(Asset.QORT, buyer.getConfirmedBalance(Asset.QORT) + buyNameTransactionData.getAmount());
// Previous Name's owner and/or data taken from referenced transaction
this.revert();
// Revert seller's balance
Account seller = new Account(this.repository, this.nameData.getOwner());
seller.setConfirmedBalance(Asset.QORT, seller.getConfirmedBalance(Asset.QORT).subtract(buyNameTransactionData.getAmount()));
seller.setConfirmedBalance(Asset.QORT, seller.getConfirmedBalance(Asset.QORT) - buyNameTransactionData.getAmount());
// Save reverted name data
this.repository.getNameRepository().save(this.nameData);

View File

@ -1,6 +1,5 @@
package org.qortal.payment;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@ -37,15 +36,15 @@ public class Payment {
// isValid
/** Are payments valid? */
public ValidationResult isValid(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee, boolean isZeroAmountValid) throws DataException {
public ValidationResult isValid(byte[] senderPublicKey, List<PaymentData> payments, long fee, boolean isZeroAmountValid) throws DataException {
AssetRepository assetRepository = this.repository.getAssetRepository();
// Check fee is positive
if (fee.compareTo(BigDecimal.ZERO) <= 0)
if (fee <= 0)
return ValidationResult.NEGATIVE_FEE;
// Total up payment amounts by assetId
Map<Long, BigDecimal> amountsByAssetId = new HashMap<>();
Map<Long, Long> amountsByAssetId = new HashMap<>();
// Add transaction fee to start with
amountsByAssetId.put(Asset.QORT, fee);
@ -55,11 +54,11 @@ public class Payment {
// Check payments, and calculate amount total by assetId
for (PaymentData paymentData : payments) {
// Check amount is zero or positive
if (paymentData.getAmount().compareTo(BigDecimal.ZERO) < 0)
if (paymentData.getAmount() < 0)
return ValidationResult.NEGATIVE_AMOUNT;
// Optional zero-amount check
if (!isZeroAmountValid && paymentData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
if (!isZeroAmountValid && paymentData.getAmount() <= 0)
return ValidationResult.NEGATIVE_AMOUNT;
// Check recipient address is valid
@ -94,64 +93,63 @@ public class Payment {
return ValidationResult.ASSET_DOES_NOT_MATCH_AT;
// Check asset amount is integer if asset is not divisible
if (!assetData.getIsDivisible() && paymentData.getAmount().stripTrailingZeros().scale() > 0)
if (!assetData.getIsDivisible() && paymentData.getAmount() % Asset.MULTIPLIER != 0)
return ValidationResult.INVALID_AMOUNT;
// Set or add amount into amounts-by-asset map
amountsByAssetId.compute(paymentData.getAssetId(), (assetId, amount) -> amount == null ? paymentData.getAmount() : amount.add(paymentData.getAmount()));
amountsByAssetId.compute(paymentData.getAssetId(), (assetId, amount) -> amount == null ? paymentData.getAmount() : amount + paymentData.getAmount());
}
// Check sender has enough of each asset
for (Entry<Long, BigDecimal> pair : amountsByAssetId.entrySet())
if (sender.getConfirmedBalance(pair.getKey()).compareTo(pair.getValue()) < 0)
for (Entry<Long, Long> pair : amountsByAssetId.entrySet())
if (sender.getConfirmedBalance(pair.getKey()) < pair.getValue())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
}
/** Are payments valid? */
public ValidationResult isValid(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee) throws DataException {
public ValidationResult isValid(byte[] senderPublicKey, List<PaymentData> payments, long fee) throws DataException {
return isValid(senderPublicKey, payments, fee, false);
}
/** Is single payment valid? */
public ValidationResult isValid(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, boolean isZeroAmountValid) throws DataException {
public ValidationResult isValid(byte[] senderPublicKey, PaymentData paymentData, long fee, boolean isZeroAmountValid) throws DataException {
return isValid(senderPublicKey, Collections.singletonList(paymentData), fee, isZeroAmountValid);
}
/** Is single payment valid? */
public ValidationResult isValid(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee) throws DataException {
public ValidationResult isValid(byte[] senderPublicKey, PaymentData paymentData, long fee) throws DataException {
return isValid(senderPublicKey, paymentData, fee, false);
}
// isProcessable
/** Are multiple payments processable? */
public ValidationResult isProcessable(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee, boolean isZeroAmountValid) throws DataException {
public ValidationResult isProcessable(byte[] senderPublicKey, List<PaymentData> payments, long fee, boolean isZeroAmountValid) throws DataException {
// Essentially the same as isValid...
return isValid(senderPublicKey, payments, fee, isZeroAmountValid);
}
/** Are multiple payments processable? */
public ValidationResult isProcessable(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee) throws DataException {
public ValidationResult isProcessable(byte[] senderPublicKey, List<PaymentData> payments, long fee) throws DataException {
return isProcessable(senderPublicKey, payments, fee, false);
}
/** Is single payment processable? */
public ValidationResult isProcessable(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, boolean isZeroAmountValid) throws DataException {
public ValidationResult isProcessable(byte[] senderPublicKey, PaymentData paymentData, long fee, boolean isZeroAmountValid) throws DataException {
return isProcessable(senderPublicKey, Collections.singletonList(paymentData), fee, isZeroAmountValid);
}
/** Is single payment processable? */
public ValidationResult isProcessable(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee) throws DataException {
public ValidationResult isProcessable(byte[] senderPublicKey, PaymentData paymentData, long fee) throws DataException {
return isProcessable(senderPublicKey, paymentData, fee, false);
}
// process
/** Multiple payment processing */
public void process(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee, byte[] signature)
throws DataException {
public void process(byte[] senderPublicKey, List<PaymentData> payments, byte[] signature) throws DataException {
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Process all payments
@ -159,31 +157,30 @@ public class Payment {
Account recipient = new Account(this.repository, paymentData.getRecipient());
long assetId = paymentData.getAssetId();
BigDecimal amount = paymentData.getAmount();
long amount = paymentData.getAmount();
// Update sender's balance due to amount
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(amount));
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId) - amount);
// Update recipient's balance
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(amount));
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId) + amount);
}
}
/** Single payment processing */
public void process(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature)
throws DataException {
process(senderPublicKey, Collections.singletonList(paymentData), fee, signature);
public void process(byte[] senderPublicKey, PaymentData paymentData, byte[] signature) throws DataException {
process(senderPublicKey, Collections.singletonList(paymentData), signature);
}
// processReferenceAndFees
/** Multiple payment reference processing */
public void processReferencesAndFees(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee, byte[] signature, boolean alwaysInitializeRecipientReference)
public void processReferencesAndFees(byte[] senderPublicKey, List<PaymentData> payments, long fee, byte[] signature, boolean alwaysInitializeRecipientReference)
throws DataException {
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Update sender's balance due to fee
sender.setConfirmedBalance(Asset.QORT, sender.getConfirmedBalance(Asset.QORT).subtract(fee));
sender.setConfirmedBalance(Asset.QORT, sender.getConfirmedBalance(Asset.QORT) - fee);
// Update sender's reference
sender.setLastReference(signature);
@ -201,42 +198,42 @@ public class Payment {
}
/** Multiple payment reference processing */
public void processReferencesAndFees(byte[] senderPublicKey, PaymentData payment, BigDecimal fee, byte[] signature, boolean alwaysInitializeRecipientReference)
public void processReferencesAndFees(byte[] senderPublicKey, PaymentData payment, long fee, byte[] signature, boolean alwaysInitializeRecipientReference)
throws DataException {
processReferencesAndFees(senderPublicKey, Collections.singletonList(payment), fee, signature, alwaysInitializeRecipientReference);
}
// orphan
public void orphan(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee, byte[] signature, byte[] reference) throws DataException {
public void orphan(byte[] senderPublicKey, List<PaymentData> payments, byte[] signature, byte[] reference) throws DataException {
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Orphan all payments
for (PaymentData paymentData : payments) {
Account recipient = new Account(this.repository, paymentData.getRecipient());
long assetId = paymentData.getAssetId();
BigDecimal amount = paymentData.getAmount();
long amount = paymentData.getAmount();
// Update sender's balance due to amount
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).add(amount));
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId) + amount);
// Update recipient's balance
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).subtract(amount));
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId) - amount);
}
}
public void orphan(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature, byte[] reference) throws DataException {
orphan(senderPublicKey, Collections.singletonList(paymentData), fee, signature, reference);
public void orphan(byte[] senderPublicKey, PaymentData paymentData, byte[] signature, byte[] reference) throws DataException {
orphan(senderPublicKey, Collections.singletonList(paymentData), signature, reference);
}
// orphanReferencesAndFees
public void orphanReferencesAndFees(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee, byte[] signature, byte[] reference,
public void orphanReferencesAndFees(byte[] senderPublicKey, List<PaymentData> payments, long fee, byte[] signature, byte[] reference,
boolean alwaysUninitializeRecipientReference) throws DataException {
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Update sender's balance due to fee
sender.setConfirmedBalance(Asset.QORT, sender.getConfirmedBalance(Asset.QORT).add(fee));
sender.setConfirmedBalance(Asset.QORT, sender.getConfirmedBalance(Asset.QORT) + fee);
// Update sender's reference
sender.setLastReference(reference);
@ -257,7 +254,7 @@ public class Payment {
}
}
public void orphanReferencesAndFees(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature, byte[] reference,
public void orphanReferencesAndFees(byte[] senderPublicKey, PaymentData paymentData, long fee, byte[] signature, byte[] reference,
boolean alwaysUninitializeRecipientReference) throws DataException {
orphanReferencesAndFees(senderPublicKey, Collections.singletonList(paymentData), fee, signature, reference, alwaysUninitializeRecipientReference);
}

View File

@ -1,6 +1,5 @@
package org.qortal.repository;
import java.math.BigDecimal;
import java.util.List;
import org.qortal.data.account.AccountBalanceData;
@ -106,7 +105,7 @@ public interface AccountRepository {
public List<AccountBalanceData> getAssetBalances(List<String> addresses, List<Long> assetIds, BalanceOrdering balanceOrdering, Boolean excludeZero, Integer limit, Integer offset, Boolean reverse) throws DataException;
public void modifyAssetBalance(String address, long assetId, BigDecimal deltaBalance) throws DataException;
public void modifyAssetBalance(String address, long assetId, long deltaBalance) throws DataException;
public void save(AccountBalanceData accountBalanceData) throws DataException;

View File

@ -1,6 +1,5 @@
package org.qortal.repository;
import java.math.BigDecimal;
import java.util.List;
import org.qortal.data.asset.AssetData;
@ -45,7 +44,7 @@ public interface AssetRepository {
return getOpenOrders(haveAssetId, wantAssetId, null, null, null);
}
public List<OrderData> getOpenOrdersForTrading(long haveAssetId, long wantAssetId, BigDecimal minimumPrice) throws DataException;
public List<OrderData> getOpenOrdersForTrading(long haveAssetId, long wantAssetId, Long minimumPrice) throws DataException;
public List<OrderData> getAggregatedOpenOrders(long haveAssetId, long wantAssetId, Integer limit, Integer offset, Boolean reverse) throws DataException;

View File

@ -1,6 +1,5 @@
package org.qortal.repository.hsqldb;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
@ -46,7 +45,9 @@ public class HSQLDBATRepository implements ATRepository {
boolean hadFatalError = resultSet.getBoolean(9);
boolean isFrozen = resultSet.getBoolean(10);
BigDecimal frozenBalance = resultSet.getBigDecimal(11);
Long frozenBalance = resultSet.getLong(11);
if (frozenBalance == 0 && resultSet.wasNull())
frozenBalance = null;
return new ATData(atAddress, creatorPublicKey, creation, version, assetId, codeBytes, isSleeping, sleepUntilHeight, isFinished, hadFatalError,
isFrozen, frozenBalance);
@ -92,7 +93,9 @@ public class HSQLDBATRepository implements ATRepository {
boolean hadFatalError = resultSet.getBoolean(9);
boolean isFrozen = resultSet.getBoolean(10);
BigDecimal frozenBalance = resultSet.getBigDecimal(11);
Long frozenBalance = resultSet.getLong(11);
if (frozenBalance == 0 && resultSet.wasNull())
frozenBalance = null;
ATData atData = new ATData(atAddress, creatorPublicKey, creation, version, assetId, codeBytes, isSleeping, sleepUntilHeight, isFinished,
hadFatalError, isFrozen, frozenBalance);
@ -159,7 +162,7 @@ public class HSQLDBATRepository implements ATRepository {
long creation = resultSet.getTimestamp(1, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
byte[] stateData = resultSet.getBytes(2); // Actually BLOB
byte[] stateHash = resultSet.getBytes(3);
BigDecimal fees = resultSet.getBigDecimal(4);
long fees = resultSet.getLong(4);
return new ATStateData(atAddress, height, creation, stateData, stateHash, fees);
} catch (SQLException e) {
@ -178,7 +181,7 @@ public class HSQLDBATRepository implements ATRepository {
long creation = resultSet.getTimestamp(2, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
byte[] stateData = resultSet.getBytes(3); // Actually BLOB
byte[] stateHash = resultSet.getBytes(4);
BigDecimal fees = resultSet.getBigDecimal(5);
long fees = resultSet.getLong(5);
return new ATStateData(atAddress, height, creation, stateData, stateHash, fees);
} catch (SQLException e) {
@ -199,7 +202,7 @@ public class HSQLDBATRepository implements ATRepository {
do {
String atAddress = resultSet.getString(1);
byte[] stateHash = resultSet.getBytes(2);
BigDecimal fees = resultSet.getBigDecimal(3);
long fees = resultSet.getLong(3);
ATStateData atStateData = new ATStateData(atAddress, height, stateHash, fees);
atStates.add(atStateData);

View File

@ -1,6 +1,5 @@
package org.qortal.repository.hsqldb;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
@ -319,7 +318,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
if (resultSet == null)
return null;
BigDecimal balance = resultSet.getBigDecimal(1).setScale(8);
long balance = resultSet.getLong(1);
return new AccountBalanceData(address, assetId, balance);
} catch (SQLException e) {
@ -342,7 +341,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
do {
String address = resultSet.getString(1);
BigDecimal balance = resultSet.getBigDecimal(2).setScale(8);
long balance = resultSet.getLong(2);
accountBalances.add(new AccountBalanceData(address, assetId, balance));
} while (resultSet.next());
@ -445,7 +444,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
do {
String address = resultSet.getString(1);
long assetId = resultSet.getLong(2);
BigDecimal balance = resultSet.getBigDecimal(3).setScale(8);
long balance = resultSet.getLong(3);
String assetName = resultSet.getString(4);
accountBalances.add(new AccountBalanceData(address, assetId, balance, assetName));
@ -458,13 +457,13 @@ public class HSQLDBAccountRepository implements AccountRepository {
}
@Override
public void modifyAssetBalance(String address, long assetId, BigDecimal deltaBalance) throws DataException {
public void modifyAssetBalance(String address, long assetId, long deltaBalance) throws DataException {
// If deltaBalance is zero then do nothing
if (deltaBalance.signum() == 0)
if (deltaBalance == 0)
return;
// If deltaBalance is negative then we assume AccountBalances & parent Accounts rows exist
if (deltaBalance.signum() < 0) {
if (deltaBalance < 0) {
// Perform actual balance change
String sql = "UPDATE AccountBalances set balance = balance + ? WHERE account = ? AND asset_id = ?";
try {
@ -496,8 +495,8 @@ public class HSQLDBAccountRepository implements AccountRepository {
public void save(AccountBalanceData accountBalanceData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("AccountBalances");
saveHelper.bind("account", accountBalanceData.getAddress()).bind("asset_id", accountBalanceData.getAssetId()).bind("balance",
accountBalanceData.getBalance());
saveHelper.bind("account", accountBalanceData.getAddress()).bind("asset_id", accountBalanceData.getAssetId())
.bind("balance", accountBalanceData.getBalance());
try {
saveHelper.execute(this.repository);
@ -527,7 +526,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
String minter = resultSet.getString(1);
byte[] rewardSharePublicKey = resultSet.getBytes(2);
BigDecimal sharePercent = resultSet.getBigDecimal(3);
int sharePercent = resultSet.getInt(3);
return new RewardShareData(minterPublicKey, minter, recipient, rewardSharePublicKey, sharePercent);
} catch (SQLException e) {
@ -546,7 +545,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
byte[] minterPublicKey = resultSet.getBytes(1);
String minter = resultSet.getString(2);
String recipient = resultSet.getString(3);
BigDecimal sharePercent = resultSet.getBigDecimal(4);
int sharePercent = resultSet.getInt(4);
return new RewardShareData(minterPublicKey, minter, recipient, rewardSharePublicKey, sharePercent);
} catch (SQLException e) {
@ -588,7 +587,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
byte[] minterPublicKey = resultSet.getBytes(1);
String minter = resultSet.getString(2);
String recipient = resultSet.getString(3);
BigDecimal sharePercent = resultSet.getBigDecimal(4);
int sharePercent = resultSet.getInt(4);
byte[] rewardSharePublicKey = resultSet.getBytes(5);
rewardShares.add(new RewardShareData(minterPublicKey, minter, recipient, rewardSharePublicKey, sharePercent));
@ -676,7 +675,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
byte[] minterPublicKey = resultSet.getBytes(1);
String minter = resultSet.getString(2);
String recipient = resultSet.getString(3);
BigDecimal sharePercent = resultSet.getBigDecimal(4);
int sharePercent = resultSet.getInt(4);
byte[] rewardSharePublicKey = resultSet.getBytes(5);
rewardShares.add(new RewardShareData(minterPublicKey, minter, recipient, rewardSharePublicKey, sharePercent));
@ -715,7 +714,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
byte[] minterPublicKey = resultSet.getBytes(1);
String minter = resultSet.getString(2);
String recipient = resultSet.getString(3);
BigDecimal sharePercent = resultSet.getBigDecimal(4);
int sharePercent = resultSet.getInt(4);
byte[] rewardSharePublicKey = resultSet.getBytes(5);
return new RewardShareData(minterPublicKey, minter, recipient, rewardSharePublicKey, sharePercent);
@ -820,7 +819,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
do {
String address = resultSet.getString(1);
BigDecimal balance = resultSet.getBigDecimal(2).setScale(8);
long balance = resultSet.getLong(2);
accountBalances.add(new AccountBalanceData(address, Asset.LEGACY_QORA, balance));
} while (resultSet.next());
@ -839,7 +838,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
if (resultSet == null)
return null;
BigDecimal finalQortFromQora = resultSet.getBigDecimal(1);
long finalQortFromQora = resultSet.getLong(1);
Integer finalBlockHeight = resultSet.getInt(2);
if (finalBlockHeight == 0 && resultSet.wasNull())
finalBlockHeight = null;

View File

@ -1,11 +1,11 @@
package org.qortal.repository.hsqldb;
import java.math.BigDecimal;
import static org.qortal.repository.hsqldb.HSQLDBRepository.getZonedTimestampMilli;
import static org.qortal.repository.hsqldb.HSQLDBRepository.toOffsetDateTime;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
@ -171,8 +171,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
if (assetData.getAssetId() == null) {
// 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)
throw new DataException("Unable to fetch new asset ID from repository");
@ -213,10 +212,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
byte[] creatorPublicKey = resultSet.getBytes(1);
long haveAssetId = resultSet.getLong(2);
long wantAssetId = resultSet.getLong(3);
BigDecimal amount = resultSet.getBigDecimal(4);
BigDecimal fulfilled = resultSet.getBigDecimal(5);
BigDecimal price = resultSet.getBigDecimal(6);
long timestamp = resultSet.getTimestamp(7, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
long amount = resultSet.getLong(4);
long fulfilled = resultSet.getLong(5);
long price = resultSet.getLong(6);
long timestamp = getZonedTimestampMilli(resultSet, 7);
boolean isClosed = resultSet.getBoolean(8);
boolean isFulfilled = resultSet.getBoolean(9);
String haveAssetName = resultSet.getString(10);
@ -263,10 +262,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
do {
byte[] creatorPublicKey = resultSet.getBytes(1);
byte[] orderId = resultSet.getBytes(2);
BigDecimal amount = resultSet.getBigDecimal(3);
BigDecimal fulfilled = resultSet.getBigDecimal(4);
BigDecimal price = resultSet.getBigDecimal(5);
long timestamp = resultSet.getTimestamp(6, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
long amount = resultSet.getLong(3);
long fulfilled = resultSet.getLong(4);
long price = resultSet.getLong(5);
long timestamp = getZonedTimestampMilli(resultSet, 6);
boolean isClosed = false;
boolean isFulfilled = false;
@ -282,7 +281,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
}
@Override
public List<OrderData> getOpenOrdersForTrading(long haveAssetId, long wantAssetId, BigDecimal minimumPrice) throws DataException {
public List<OrderData> getOpenOrdersForTrading(long haveAssetId, long wantAssetId, Long minimumPrice) throws DataException {
List<Object> bindParams = new ArrayList<>(3);
StringBuilder sql = new StringBuilder(512);
@ -317,10 +316,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
do {
byte[] creatorPublicKey = resultSet.getBytes(1);
byte[] orderId = resultSet.getBytes(2);
BigDecimal amount = resultSet.getBigDecimal(3);
BigDecimal fulfilled = resultSet.getBigDecimal(4);
BigDecimal price = resultSet.getBigDecimal(5);
long timestamp = resultSet.getTimestamp(6, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
long amount = resultSet.getLong(3);
long fulfilled = resultSet.getLong(4);
long price = resultSet.getLong(5);
long timestamp = getZonedTimestampMilli(resultSet, 6);
boolean isClosed = false;
boolean isFulfilled = false;
@ -366,11 +365,11 @@ public class HSQLDBAssetRepository implements AssetRepository {
return orders;
do {
BigDecimal price = resultSet.getBigDecimal(1);
BigDecimal totalUnfulfilled = resultSet.getBigDecimal(2);
long price = resultSet.getLong(1);
long totalUnfulfilled = resultSet.getLong(2);
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, 0L,
price, timestamp, false, false, haveAssetData.getName(), wantAssetData.getName());
orders.add(order);
} while (resultSet.next());
@ -417,10 +416,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
byte[] orderId = resultSet.getBytes(1);
long haveAssetId = resultSet.getLong(2);
long wantAssetId = resultSet.getLong(3);
BigDecimal amount = resultSet.getBigDecimal(4);
BigDecimal fulfilled = resultSet.getBigDecimal(5);
BigDecimal price = resultSet.getBigDecimal(6);
long timestamp = resultSet.getTimestamp(7, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
long amount = resultSet.getLong(4);
long fulfilled = resultSet.getLong(5);
long price = resultSet.getLong(6);
long timestamp = getZonedTimestampMilli(resultSet, 7);
boolean isClosed = resultSet.getBoolean(8);
boolean isFulfilled = resultSet.getBoolean(9);
String haveAssetName = resultSet.getString(10);
@ -478,10 +477,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
do {
byte[] orderId = resultSet.getBytes(1);
BigDecimal amount = resultSet.getBigDecimal(2);
BigDecimal fulfilled = resultSet.getBigDecimal(3);
BigDecimal price = resultSet.getBigDecimal(4);
long timestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
long amount = resultSet.getLong(2);
long fulfilled = resultSet.getLong(3);
long price = resultSet.getLong(4);
long timestamp = getZonedTimestampMilli(resultSet, 5);
boolean isClosed = resultSet.getBoolean(6);
boolean isFulfilled = resultSet.getBoolean(7);
@ -503,7 +502,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
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("fulfilled", orderData.getFulfilled())
.bind("price", orderData.getPrice()).bind("ordered", new Timestamp(orderData.getTimestamp()))
.bind("price", orderData.getPrice()).bind("ordered", toOffsetDateTime(orderData.getTimestamp()))
.bind("is_closed", orderData.getIsClosed()).bind("is_fulfilled", orderData.getIsFulfilled());
try {
@ -556,10 +555,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
do {
byte[] initiatingOrderId = resultSet.getBytes(1);
byte[] targetOrderId = resultSet.getBytes(2);
BigDecimal targetAmount = resultSet.getBigDecimal(3);
BigDecimal initiatorAmount = resultSet.getBigDecimal(4);
BigDecimal initiatorSaving = resultSet.getBigDecimal(5);
long timestamp = resultSet.getTimestamp(6, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
long targetAmount = resultSet.getLong(3);
long initiatorAmount = resultSet.getLong(4);
long initiatorSaving = resultSet.getLong(5);
long timestamp = getZonedTimestampMilli(resultSet, 6);
TradeData trade = new TradeData(initiatingOrderId, targetOrderId, targetAmount, initiatorAmount, initiatorSaving,
timestamp, haveAssetId, haveAssetData.getName(), wantAssetId, wantAssetData.getName());
@ -648,9 +647,9 @@ public class HSQLDBAssetRepository implements AssetRepository {
do {
long haveAssetId = resultSet.getLong(1);
long wantAssetId = resultSet.getLong(2);
BigDecimal otherAmount = resultSet.getBigDecimal(3);
BigDecimal amount = resultSet.getBigDecimal(4);
long timestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
long otherAmount = resultSet.getLong(3);
long amount = resultSet.getLong(4);
long timestamp = getZonedTimestampMilli(resultSet, 5);
RecentTradeData recentTrade = new RecentTradeData(haveAssetId, wantAssetId, otherAmount, amount,
timestamp);
@ -689,10 +688,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
do {
byte[] initiatingOrderId = resultSet.getBytes(1);
byte[] targetOrderId = resultSet.getBytes(2);
BigDecimal targetAmount = resultSet.getBigDecimal(3);
BigDecimal initiatorAmount = resultSet.getBigDecimal(4);
BigDecimal initiatorSaving = resultSet.getBigDecimal(5);
long timestamp = resultSet.getTimestamp(6, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
long targetAmount = resultSet.getLong(3);
long initiatorAmount = resultSet.getLong(4);
long initiatorSaving = resultSet.getLong(5);
long timestamp = getZonedTimestampMilli(resultSet, 6);
long haveAssetId = resultSet.getLong(7);
String haveAssetName = resultSet.getString(8);
@ -716,7 +715,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
saveHelper.bind("initiating_order_id", tradeData.getInitiator()).bind("target_order_id", tradeData.getTarget())
.bind("target_amount", tradeData.getTargetAmount()).bind("initiator_amount", tradeData.getInitiatorAmount())
.bind("initiator_saving", tradeData.getInitiatorSaving()).bind("traded", new Timestamp(tradeData.getTimestamp()));
.bind("initiator_saving", tradeData.getInitiatorSaving()).bind("traded", toOffsetDateTime(tradeData.getTimestamp()));
try {
saveHelper.execute(this.repository);

View File

@ -3,7 +3,6 @@ package org.qortal.repository.hsqldb;
import static org.qortal.repository.hsqldb.HSQLDBRepository.getZonedTimestampMilli;
import static org.qortal.repository.hsqldb.HSQLDBRepository.toOffsetDateTime;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
@ -39,14 +38,14 @@ public class HSQLDBBlockRepository implements BlockRepository {
int version = resultSet.getInt(1);
byte[] reference = resultSet.getBytes(2);
int transactionCount = resultSet.getInt(3);
BigDecimal totalFees = resultSet.getBigDecimal(4);
long totalFees = resultSet.getLong(4);
byte[] transactionsSignature = resultSet.getBytes(5);
int height = resultSet.getInt(6);
long timestamp = getZonedTimestampMilli(resultSet, 7);
byte[] minterPublicKey = resultSet.getBytes(8);
byte[] minterSignature = resultSet.getBytes(9);
int atCount = resultSet.getInt(10);
BigDecimal atFees = resultSet.getBigDecimal(11);
long atFees = resultSet.getLong(11);
byte[] encodedOnlineAccounts = resultSet.getBytes(12);
int onlineAccountsCount = resultSet.getInt(13);
Long onlineAccountsTimestamp = getZonedTimestampMilli(resultSet, 14);

View File

@ -99,7 +99,7 @@ public class HSQLDBDatabaseUpdates {
stmt.execute("CREATE TYPE Signature AS VARBINARY(64)");
stmt.execute("CREATE TYPE QortalAddress AS VARCHAR(36)");
stmt.execute("CREATE TYPE QortalPublicKey AS VARBINARY(32)");
stmt.execute("CREATE TYPE QortalAmount AS DECIMAL(27, 8)");
stmt.execute("CREATE TYPE QortalAmount AS BIGINT");
stmt.execute("CREATE TYPE GenericDescription AS VARCHAR(4000)");
stmt.execute("CREATE TYPE RegisteredName AS VARCHAR(128) COLLATE SQL_TEXT_NO_PAD");
stmt.execute("CREATE TYPE NameData AS VARCHAR(4000)");
@ -122,6 +122,7 @@ public class HSQLDBDatabaseUpdates {
stmt.execute("CREATE TYPE GroupID AS INTEGER");
stmt.execute("CREATE TYPE GroupName AS VARCHAR(400) COLLATE SQL_TEXT_UCC_NO_PAD");
stmt.execute("CREATE TYPE GroupReason AS VARCHAR(128) COLLATE SQL_TEXT_UCC_NO_PAD");
stmt.execute("CREATE TYPE RewardSharePercent AS INT");
break;
case 1:
@ -713,10 +714,10 @@ public class HSQLDBDatabaseUpdates {
case 46:
// Proxy forging
// Transaction emitted by forger announcing they are forging on behalf of recipient
stmt.execute("CREATE TABLE ProxyForgingTransactions (signature Signature, forger QortalPublicKey NOT NULL, recipient QortalAddress NOT NULL, proxy_public_key QortalPublicKey NOT NULL, share DECIMAL(5,2) NOT NULL, "
+ "previous_share DECIMAL(5,2), PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
stmt.execute("CREATE TABLE ProxyForgingTransactions (signature Signature, forger QortalPublicKey NOT NULL, recipient QortalAddress NOT NULL, proxy_public_key QortalPublicKey NOT NULL, share RewardSharePercent NOT NULL, "
+ "previous_share RewardSharePercent, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// Table of current shares
stmt.execute("CREATE TABLE ProxyForgers (forger QortalPublicKey NOT NULL, recipient QortalAddress NOT NULL, proxy_public_key QortalPublicKey NOT NULL, share DECIMAL(5,2) NOT NULL, "
stmt.execute("CREATE TABLE ProxyForgers (forger QortalPublicKey NOT NULL, recipient QortalAddress NOT NULL, proxy_public_key QortalPublicKey NOT NULL, share RewardSharePercent NOT NULL, "
+ "PRIMARY KEY (forger, recipient))");
// Proxy-forged blocks will contain proxy public key, which will be used to look up block reward sharing, so create index for those lookups
stmt.execute("CREATE INDEX ProxyForgersProxyPublicKeyIndex ON ProxyForgers (proxy_public_key)");

View File

@ -1,6 +1,8 @@
package org.qortal.repository.hsqldb;
import java.math.BigDecimal;
import static org.qortal.repository.hsqldb.HSQLDBRepository.getZonedTimestampMilli;
import static org.qortal.repository.hsqldb.HSQLDBRepository.toOffsetDateTime;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
@ -38,7 +40,11 @@ public class HSQLDBNameRepository implements NameRepository {
byte[] reference = resultSet.getBytes(5);
boolean isForSale = resultSet.getBoolean(6);
BigDecimal salePrice = resultSet.getBigDecimal(7);
Long salePrice = resultSet.getLong(7);
if (salePrice == 0 && resultSet.wasNull())
salePrice = null;
int creationGroupId = resultSet.getInt(8);
return new NameData(owner, name, data, registered, updated, reference, isForSale, salePrice, creationGroupId);
@ -75,15 +81,15 @@ public class HSQLDBNameRepository implements NameRepository {
String name = resultSet.getString(1);
String data = resultSet.getString(2);
String owner = resultSet.getString(3);
long registered = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
// Special handling for possibly-NULL "updated" column
Timestamp updatedTimestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC));
Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime();
long registered = getZonedTimestampMilli(resultSet, 4);
Long updated = getZonedTimestampMilli(resultSet, 5); // can be null
byte[] reference = resultSet.getBytes(6);
boolean isForSale = resultSet.getBoolean(7);
BigDecimal salePrice = resultSet.getBigDecimal(8);
Long salePrice = resultSet.getLong(8);
if (salePrice == 0 && resultSet.wasNull())
salePrice = null;
int creationGroupId = resultSet.getInt(9);
names.add(new NameData(owner, name, data, registered, updated, reference, isForSale, salePrice, creationGroupId));
@ -114,15 +120,15 @@ public class HSQLDBNameRepository implements NameRepository {
String name = resultSet.getString(1);
String data = resultSet.getString(2);
String owner = resultSet.getString(3);
long registered = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
// Special handling for possibly-NULL "updated" column
Timestamp updatedTimestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC));
Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime();
long registered = getZonedTimestampMilli(resultSet, 4);
Long updated = getZonedTimestampMilli(resultSet, 5); // can be null
byte[] reference = resultSet.getBytes(6);
boolean isForSale = true;
BigDecimal salePrice = resultSet.getBigDecimal(7);
Long salePrice = resultSet.getLong(7);
if (salePrice == 0 && resultSet.wasNull())
salePrice = null;
int creationGroupId = resultSet.getInt(8);
names.add(new NameData(owner, name, data, registered, updated, reference, isForSale, salePrice, creationGroupId));
@ -152,15 +158,15 @@ public class HSQLDBNameRepository implements NameRepository {
do {
String name = resultSet.getString(1);
String data = resultSet.getString(2);
long registered = resultSet.getTimestamp(3, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
// Special handling for possibly-NULL "updated" column
Timestamp updatedTimestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC));
Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime();
long registered = getZonedTimestampMilli(resultSet, 3);
Long updated = getZonedTimestampMilli(resultSet, 4); // can be null
byte[] reference = resultSet.getBytes(5);
boolean isForSale = resultSet.getBoolean(6);
BigDecimal salePrice = resultSet.getBigDecimal(7);
Long salePrice = resultSet.getLong(7);
if (salePrice == 0 && resultSet.wasNull())
salePrice = null;
int creationGroupId = resultSet.getInt(8);
names.add(new NameData(owner, name, data, registered, updated, reference, isForSale, salePrice, creationGroupId));
@ -200,12 +206,9 @@ public class HSQLDBNameRepository implements NameRepository {
public void save(NameData nameData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("Names");
// Special handling for "updated" timestamp
Long updated = nameData.getUpdated();
Timestamp updatedTimestamp = updated == null ? null : new Timestamp(updated);
saveHelper.bind("owner", nameData.getOwner()).bind("name", nameData.getName()).bind("data", nameData.getData())
.bind("registered", new Timestamp(nameData.getRegistered())).bind("updated", updatedTimestamp).bind("reference", nameData.getReference())
.bind("registered", toOffsetDateTime(nameData.getRegistered())).bind("updated", toOffsetDateTime(nameData.getUpdated()))
.bind("reference", nameData.getReference())
.bind("is_for_sale", nameData.getIsForSale()).bind("sale_price", nameData.getSalePrice())
.bind("creation_group_id", nameData.getCreationGroupId());

View File

@ -763,7 +763,7 @@ public class HSQLDBRepository implements Repository {
}
/** Converts milliseconds from epoch to OffsetDateTime needed for TIMESTAMP WITH TIME ZONE columns. */
/* package */ static OffsetDateTime toOffsetDateTime(Long timestamp) {
public static OffsetDateTime toOffsetDateTime(Long timestamp) {
if (timestamp == null)
return null;
@ -771,12 +771,12 @@ public class HSQLDBRepository implements Repository {
}
/** Converts OffsetDateTime from TIMESTAMP WITH TIME ZONE column to milliseconds from epoch. */
/* package */ static long fromOffsetDateTime(OffsetDateTime offsetDateTime) {
public static long fromOffsetDateTime(OffsetDateTime offsetDateTime) {
return offsetDateTime.toInstant().toEpochMilli();
}
/** Returns TIMESTAMP WITH TIME ZONE column value as milliseconds from epoch, or null. */
/* package */ static Long getZonedTimestampMilli(ResultSet resultSet, int columnIndex) throws SQLException {
public static Long getZonedTimestampMilli(ResultSet resultSet, int columnIndex) throws SQLException {
OffsetDateTime offsetDateTime = resultSet.getObject(columnIndex, OffsetDateTime.class);
if (offsetDateTime == null)
return null;

View File

@ -1,6 +1,5 @@
package org.qortal.repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -27,7 +26,9 @@ public class HSQLDBAtTransactionRepository extends HSQLDBTransactionRepository {
String atAddress = resultSet.getString(1);
String recipient = resultSet.getString(2);
BigDecimal amount = resultSet.getBigDecimal(3);
Long amount = resultSet.getLong(3);
if (amount == 0 && resultSet.wasNull())
amount = null;
Long assetId = resultSet.getLong(4);
if (assetId == 0 && resultSet.wasNull())

View File

@ -1,6 +1,5 @@
package org.qortal.repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -25,7 +24,7 @@ public class HSQLDBBuyNameTransactionRepository extends HSQLDBTransactionReposit
return null;
String name = resultSet.getString(1);
BigDecimal amount = resultSet.getBigDecimal(2);
long amount = resultSet.getLong(2);
String seller = resultSet.getString(3);
byte[] nameReference = resultSet.getBytes(4);

View File

@ -1,6 +1,5 @@
package org.qortal.repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -30,9 +29,9 @@ public class HSQLDBCreateAssetOrderTransactionRepository extends HSQLDBTransacti
return null;
long haveAssetId = resultSet.getLong(1);
BigDecimal amount = resultSet.getBigDecimal(2);
long amount = resultSet.getLong(2);
long wantAssetId = resultSet.getLong(3);
BigDecimal price = resultSet.getBigDecimal(4);
long price = resultSet.getLong(4);
String haveAssetName = resultSet.getString(5);
String wantAssetName = resultSet.getString(6);

View File

@ -1,6 +1,5 @@
package org.qortal.repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -29,7 +28,7 @@ public class HSQLDBDeployAtTransactionRepository extends HSQLDBTransactionReposi
String atType = resultSet.getString(3);
String tags = resultSet.getString(4);
byte[] creationBytes = resultSet.getBytes(5);
BigDecimal amount = resultSet.getBigDecimal(6).setScale(8);
long amount = resultSet.getLong(6);
long assetId = resultSet.getLong(7);
// Special null-checking for AT address

View File

@ -1,6 +1,5 @@
package org.qortal.repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -25,7 +24,7 @@ public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionReposit
return null;
String recipient = resultSet.getString(1);
BigDecimal amount = resultSet.getBigDecimal(2).setScale(8);
long amount = resultSet.getLong(2);
long assetId = resultSet.getLong(3);
return new GenesisTransactionData(baseTransactionData, recipient, amount, assetId);

View File

@ -1,6 +1,5 @@
package org.qortal.repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -28,7 +27,7 @@ public class HSQLDBMessageTransactionRepository extends HSQLDBTransactionReposit
String recipient = resultSet.getString(2);
boolean isText = resultSet.getBoolean(3);
boolean isEncrypted = resultSet.getBoolean(4);
BigDecimal amount = resultSet.getBigDecimal(5);
long amount = resultSet.getLong(5);
// Special null-checking for asset ID
Long assetId = resultSet.getLong(6);

View File

@ -1,6 +1,5 @@
package org.qortal.repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -25,7 +24,7 @@ public class HSQLDBPaymentTransactionRepository extends HSQLDBTransactionReposit
return null;
String recipient = resultSet.getString(1);
BigDecimal amount = resultSet.getBigDecimal(2);
long amount = resultSet.getLong(2);
return new PaymentTransactionData(baseTransactionData, recipient, amount);
} catch (SQLException e) {

View File

@ -1,6 +1,5 @@
package org.qortal.repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -26,8 +25,11 @@ public class HSQLDBRewardShareTransactionRepository extends HSQLDBTransactionRep
String recipient = resultSet.getString(1);
byte[] rewardSharePublicKey = resultSet.getBytes(2);
BigDecimal sharePercent = resultSet.getBigDecimal(3);
BigDecimal previousSharePercent = resultSet.getBigDecimal(4);
int sharePercent = resultSet.getInt(3);
Integer previousSharePercent = resultSet.getInt(4);
if (previousSharePercent == 0 && resultSet.wasNull())
previousSharePercent = null;
return new RewardShareTransactionData(baseTransactionData, recipient, rewardSharePublicKey, sharePercent, previousSharePercent);
} catch (SQLException e) {

View File

@ -1,6 +1,5 @@
package org.qortal.repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -25,7 +24,7 @@ public class HSQLDBSellNameTransactionRepository extends HSQLDBTransactionReposi
return null;
String name = resultSet.getString(1);
BigDecimal amount = resultSet.getBigDecimal(2);
long amount = resultSet.getLong(2);
return new SellNameTransactionData(baseTransactionData, name, amount);
} catch (SQLException e) {

View File

@ -1,16 +1,16 @@
package org.qortal.repository.hsqldb.transaction;
import static org.qortal.repository.hsqldb.HSQLDBRepository.getZonedTimestampMilli;
import static org.qortal.repository.hsqldb.HSQLDBRepository.toOffsetDateTime;
import static org.qortal.transaction.Transaction.TransactionType.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
@ -135,8 +135,12 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
byte[] reference = resultSet.getBytes(2);
byte[] creatorPublicKey = resultSet.getBytes(3);
long timestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
BigDecimal fee = resultSet.getBigDecimal(5).setScale(8);
long timestamp = getZonedTimestampMilli(resultSet, 4);
Long fee = resultSet.getLong(5);
if (fee == 0 && resultSet.wasNull())
fee = null;
int txGroupId = resultSet.getInt(6);
Integer blockHeight = resultSet.getInt(7);
@ -168,8 +172,12 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
byte[] signature = resultSet.getBytes(2);
byte[] creatorPublicKey = resultSet.getBytes(3);
long timestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
BigDecimal fee = resultSet.getBigDecimal(5).setScale(8);
long timestamp = getZonedTimestampMilli(resultSet, 4);
Long fee = resultSet.getLong(5);
if (fee == 0 && resultSet.wasNull())
fee = null;
int txGroupId = resultSet.getInt(6);
Integer blockHeight = resultSet.getInt(7);
@ -244,7 +252,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
// NOTE: do-while because checkedExecute() above has already called rs.next() for us
do {
String recipient = resultSet.getString(1);
BigDecimal amount = resultSet.getBigDecimal(2);
long amount = resultSet.getLong(2);
long assetId = resultSet.getLong(3);
payments.add(new PaymentData(recipient, assetId, amount));
@ -673,10 +681,10 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
return assetTransfers;
do {
long timestamp = resultSet.getTimestamp(1, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
long timestamp = getZonedTimestampMilli(resultSet, 1);
int txGroupId = resultSet.getInt(2);
byte[] reference = resultSet.getBytes(3);
BigDecimal fee = resultSet.getBigDecimal(4).setScale(8);
long fee = resultSet.getLong(4);
byte[] signature = resultSet.getBytes(5);
byte[] creatorPublicKey = resultSet.getBytes(6);
@ -693,7 +701,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, creatorPublicKey, fee, approvalStatus, blockHeight, approvalHeight, signature);
String recipient = resultSet.getString(10);
BigDecimal amount = resultSet.getBigDecimal(11);
long amount = resultSet.getLong(11);
String assetName = resultSet.getString(12);
assetTransfers.add(new TransferAssetTransactionData(baseTransactionData, recipient, amount, assetId, assetName));
@ -1027,7 +1035,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
public void unconfirmTransaction(TransactionData transactionData) throws DataException {
HSQLDBSaver saver = new HSQLDBSaver("UnconfirmedTransactions");
saver.bind("signature", transactionData.getSignature()).bind("creation", new Timestamp(transactionData.getTimestamp()));
saver.bind("signature", transactionData.getSignature()).bind("creation", toOffsetDateTime(transactionData.getTimestamp()));
try {
saver.execute(repository);
@ -1044,7 +1052,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
saver.bind("signature", transactionData.getSignature()).bind("reference", transactionData.getReference())
.bind("type", transactionData.getType().value)
.bind("creator", transactionData.getCreatorPublicKey()).bind("creation", new Timestamp(transactionData.getTimestamp()))
.bind("creator", transactionData.getCreatorPublicKey()).bind("creation", toOffsetDateTime(transactionData.getTimestamp()))
.bind("fee", transactionData.getFee()).bind("milestone_block", null).bind("tx_group_id", transactionData.getTxGroupId())
.bind("approval_status", transactionData.getApprovalStatus().value);

View File

@ -1,6 +1,5 @@
package org.qortal.repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -27,7 +26,7 @@ public class HSQLDBTransferAssetTransactionRepository extends HSQLDBTransactionR
String recipient = resultSet.getString(1);
long assetId = resultSet.getLong(2);
BigDecimal amount = resultSet.getBigDecimal(3);
long amount = resultSet.getLong(3);
String assetName = resultSet.getString(4);
return new TransferAssetTransactionData(baseTransactionData, recipient, amount, assetId, assetName);

View File

@ -1,12 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.NullAccount;
import org.qortal.asset.Asset;
import org.qortal.data.transaction.AccountFlagsTransactionData;
import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException;
@ -15,7 +12,9 @@ import org.qortal.repository.Repository;
public class AccountFlagsTransaction extends Transaction {
// Properties
private AccountFlagsTransactionData accountFlagsTransactionData;
private Account targetAccount = null;
// Constructors
@ -28,78 +27,46 @@ public class AccountFlagsTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.emptyList();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getCreator().getAddress()))
return true;
if (address.equals(this.getTarget().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.getCreator().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.accountFlagsTransactionData.getTarget());
}
// Navigation
public Account getTarget() {
return new Account(this.repository, this.accountFlagsTransactionData.getTarget());
if (this.targetAccount == null)
this.targetAccount = new Account(this.repository, this.accountFlagsTransactionData.getTarget());
return this.targetAccount;
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
Account creator = getCreator();
// Only null account can modify flags
if (!creator.getAddress().equals(NullAccount.ADDRESS))
return ValidationResult.NO_FLAG_PERMISSION;
// Check fee is zero or positive
if (accountFlagsTransactionData.getFee().compareTo(BigDecimal.ZERO) < 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORT).compareTo(accountFlagsTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
// Invalid outside of genesis block
return ValidationResult.NO_FLAG_PERMISSION;
}
@Override
public void process() throws DataException {
Account target = getTarget();
Account target = this.getTarget();
Integer previousFlags = target.getFlags();
accountFlagsTransactionData.setPreviousFlags(previousFlags);
this.accountFlagsTransactionData.setPreviousFlags(previousFlags);
// Save this transaction with target account's previous flags value
this.repository.getTransactionRepository().save(accountFlagsTransactionData);
this.repository.getTransactionRepository().save(this.accountFlagsTransactionData);
// If account doesn't have entry in database yet (e.g. genesis block) then flags are zero
if (previousFlags == null)
previousFlags = 0;
// Set account's new flags
int newFlags = previousFlags & accountFlagsTransactionData.getAndMask()
| accountFlagsTransactionData.getOrMask() ^ accountFlagsTransactionData.getXorMask();
int newFlags = previousFlags
& this.accountFlagsTransactionData.getAndMask()
| this.accountFlagsTransactionData.getOrMask()
^ this.accountFlagsTransactionData.getXorMask();
target.setFlags(newFlags);
}
@ -107,15 +74,14 @@ public class AccountFlagsTransaction extends Transaction {
@Override
public void processReferencesAndFees() throws DataException {
// Set account's reference
getTarget().setLastReference(this.accountFlagsTransactionData.getSignature());
this.getTarget().setLastReference(this.accountFlagsTransactionData.getSignature());
}
@Override
public void orphan() throws DataException {
// Revert
Account target = getTarget();
Integer previousFlags = accountFlagsTransactionData.getPreviousFlags();
Integer previousFlags = this.accountFlagsTransactionData.getPreviousFlags();
// If previousFlags are null then account didn't exist before this transaction
if (previousFlags == null)
@ -124,7 +90,7 @@ public class AccountFlagsTransaction extends Transaction {
target.setFlags(previousFlags);
// Remove previous flags from transaction itself
accountFlagsTransactionData.setPreviousFlags(null);
this.accountFlagsTransactionData.setPreviousFlags(null);
this.repository.getTransactionRepository().save(accountFlagsTransactionData);
}

View File

@ -1,12 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.NullAccount;
import org.qortal.asset.Asset;
import org.qortal.block.BlockChain;
import org.qortal.data.transaction.AccountLevelTransactionData;
import org.qortal.data.transaction.TransactionData;
@ -16,7 +13,9 @@ import org.qortal.repository.Repository;
public class AccountLevelTransaction extends Transaction {
// Properties
private AccountLevelTransactionData accountLevelTransactionData;
private Account targetAccount = null;
// Constructors
@ -29,59 +28,25 @@ public class AccountLevelTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.emptyList();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getCreator().getAddress()))
return true;
if (address.equals(this.getTarget().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.getCreator().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.accountLevelTransactionData.getTarget());
}
// Navigation
public Account getTarget() {
return new Account(this.repository, this.accountLevelTransactionData.getTarget());
if (this.targetAccount == null)
this.targetAccount = new Account(this.repository, this.accountLevelTransactionData.getTarget());
return this.targetAccount;
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
Account creator = getCreator();
// Only genesis account can modify level
if (!creator.getAddress().equals(new NullAccount(repository).getAddress()))
return ValidationResult.NO_FLAG_PERMISSION;
// Check fee is zero or positive
if (accountLevelTransactionData.getFee().compareTo(BigDecimal.ZERO) < 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORT).compareTo(accountLevelTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
// Invalid outside of genesis block
return ValidationResult.NO_FLAG_PERMISSION;
}
@Override
@ -89,13 +54,13 @@ public class AccountLevelTransaction extends Transaction {
Account target = getTarget();
// Save this transaction
this.repository.getTransactionRepository().save(accountLevelTransactionData);
this.repository.getTransactionRepository().save(this.accountLevelTransactionData);
// Set account's initial level
target.setLevel(this.accountLevelTransactionData.getLevel());
// Set account's blocks minted adjustment
final List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
List<Integer> cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel();
int blocksMintedAdjustment = cumulativeBlocksByLevel.get(this.accountLevelTransactionData.getLevel());
target.setBlocksMintedAdjustment(blocksMintedAdjustment);
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.data.transaction.AddGroupAdminTransactionData;
@ -17,7 +15,9 @@ import org.qortal.repository.Repository;
public class AddGroupAdminTransaction extends Transaction {
// Properties
private AddGroupAdminTransactionData addGroupAdminTransactionData;
Account memberAccount = null;
// Constructors
@ -30,79 +30,55 @@ public class AddGroupAdminTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.emptyList();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getOwner().getAddress()))
return true;
if (address.equals(this.getMember().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;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.addGroupAdminTransactionData.getMember());
}
// Navigation
public Account getOwner() throws DataException {
return new PublicKeyAccount(this.repository, this.addGroupAdminTransactionData.getOwnerPublicKey());
public Account getOwner() {
return this.getCreator();
}
public Account getMember() throws DataException {
return new Account(this.repository, this.addGroupAdminTransactionData.getMember());
public Account getMember() {
if (this.memberAccount == null)
this.memberAccount = new Account(this.repository, this.addGroupAdminTransactionData.getMember());
return this.memberAccount;
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
int groupId = this.addGroupAdminTransactionData.getGroupId();
String memberAddress = this.addGroupAdminTransactionData.getMember();
// Check member address is valid
if (!Crypto.isValidAddress(addGroupAdminTransactionData.getMember()))
if (!Crypto.isValidAddress(memberAddress))
return ValidationResult.INVALID_ADDRESS;
// Check group exists
if (!this.repository.getGroupRepository().groupExists(addGroupAdminTransactionData.getGroupId()))
if (!this.repository.getGroupRepository().groupExists(groupId))
return ValidationResult.GROUP_DOES_NOT_EXIST;
// Check fee is positive
if (addGroupAdminTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
Account owner = getOwner();
String groupOwner = this.repository.getGroupRepository().getOwner(addGroupAdminTransactionData.getGroupId());
String groupOwner = this.repository.getGroupRepository().getOwner(groupId);
// Check transaction's public key matches group's current owner
if (!owner.getAddress().equals(groupOwner))
return ValidationResult.INVALID_GROUP_OWNER;
Account member = getMember();
// Check address is a member
if (!this.repository.getGroupRepository().memberExists(addGroupAdminTransactionData.getGroupId(), member.getAddress()))
// Check address is a group member
if (!this.repository.getGroupRepository().memberExists(groupId, memberAddress))
return ValidationResult.NOT_GROUP_MEMBER;
// Check member is not already an admin
if (this.repository.getGroupRepository().adminExists(addGroupAdminTransactionData.getGroupId(), member.getAddress()))
// Check group member is not already an admin
if (this.repository.getGroupRepository().adminExists(groupId, memberAddress))
return ValidationResult.ALREADY_GROUP_ADMIN;
// Check group owner has enough funds
if (owner.getConfirmedBalance(Asset.QORT).compareTo(addGroupAdminTransactionData.getFee()) < 0)
if (owner.getConfirmedBalance(Asset.QORT) < this.addGroupAdminTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -111,15 +87,15 @@ public class AddGroupAdminTransaction extends Transaction {
@Override
public void process() throws DataException {
// Update Group adminship
Group group = new Group(this.repository, addGroupAdminTransactionData.getGroupId());
group.promoteToAdmin(addGroupAdminTransactionData);
Group group = new Group(this.repository, this.addGroupAdminTransactionData.getGroupId());
group.promoteToAdmin(this.addGroupAdminTransactionData);
}
@Override
public void orphan() throws DataException {
// Revert group adminship
Group group = new Group(this.repository, addGroupAdminTransactionData.getGroupId());
group.unpromoteToAdmin(addGroupAdminTransactionData);
Group group = new Group(this.repository, this.addGroupAdminTransactionData.getGroupId());
group.unpromoteToAdmin(this.addGroupAdminTransactionData);
}
}

View File

@ -1,12 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.PaymentData;
import org.qortal.data.transaction.ArbitraryTransactionData;
import org.qortal.data.transaction.TransactionData;
@ -33,57 +30,14 @@ public class ArbitraryTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
List<Account> recipients = new ArrayList<>();
if (arbitraryTransactionData.getVersion() != 1)
for (PaymentData paymentData : arbitraryTransactionData.getPayments())
recipients.add(new Account(this.repository, paymentData.getRecipient()));
return recipients;
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getSender().getAddress()))
return true;
if (arbitraryTransactionData.getVersion() != 1)
for (PaymentData paymentData : arbitraryTransactionData.getPayments())
if (address.equals(paymentData.getRecipient()))
return true;
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
String senderAddress = this.getSender().getAddress();
if (address.equals(senderAddress))
amount = amount.subtract(this.transactionData.getFee());
if (arbitraryTransactionData.getVersion() != 1)
for (PaymentData paymentData : arbitraryTransactionData.getPayments())
// We're only interested in QORT
if (paymentData.getAssetId() == Asset.QORT) {
if (address.equals(paymentData.getRecipient()))
amount = amount.add(paymentData.getAmount());
else if (address.equals(senderAddress))
amount = amount.subtract(paymentData.getAmount());
}
return amount;
public List<String> getRecipientAddresses() throws DataException {
return this.arbitraryTransactionData.getPayments().stream().map(PaymentData::getRecipient).collect(Collectors.toList());
}
// Navigation
public Account getSender() throws DataException {
return new PublicKeyAccount(this.repository, this.arbitraryTransactionData.getSenderPublicKey());
public Account getSender() {
return this.getCreator();
}
// Processing
@ -110,7 +64,7 @@ public class ArbitraryTransaction extends Transaction {
public void process() throws DataException {
// Wrap and delegate payment processing to Payment class.
new Payment(this.repository).process(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(),
arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature());
arbitraryTransactionData.getSignature());
}
@Override
@ -124,7 +78,7 @@ public class ArbitraryTransaction extends Transaction {
public void orphan() throws DataException {
// Wrap and delegate payment processing to Payment class.
new Payment(this.repository).orphan(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(),
arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature(), arbitraryTransactionData.getReference());
arbitraryTransactionData.getSignature(), arbitraryTransactionData.getReference());
}
@Override

View File

@ -1,9 +1,6 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
@ -22,7 +19,10 @@ import com.google.common.primitives.Bytes;
public class AtTransaction extends Transaction {
// Properties
private ATTransactionData atTransactionData;
private Account atAccount = null;
private Account recipientAccount = null;
// Other useful constants
public static final int MAX_DATA_SIZE = 256;
@ -50,97 +50,62 @@ public class AtTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(new Account(this.repository, this.atTransactionData.getRecipient()));
}
/** For AT-Transactions, the use the AT address instead of transaction creator (which is genesis account) */
@Override
public List<Account> getInvolvedAccounts() throws DataException {
List<Account> participants = new ArrayList<>(getRecipientAccounts());
participants.add(getATAccount());
return participants;
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.atTransactionData.getATAddress()))
return true;
if (address.equals(this.atTransactionData.getRecipient()))
return true;
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
String atAddress = this.atTransactionData.getATAddress();
if (address.equals(atAddress)) {
amount = amount.subtract(this.atTransactionData.getFee());
if (this.atTransactionData.getAmount() != null && this.atTransactionData.getAssetId() == Asset.QORT)
amount = amount.subtract(this.atTransactionData.getAmount());
}
if (address.equals(this.atTransactionData.getRecipient()) && this.atTransactionData.getAmount() != null
&& this.atTransactionData.getAssetId() == Asset.QORT)
amount = amount.add(this.atTransactionData.getAmount());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Arrays.asList(this.atTransactionData.getATAddress(), this.atTransactionData.getRecipient());
}
// Navigation
public Account getATAccount() throws DataException {
return new Account(this.repository, this.atTransactionData.getATAddress());
public Account getATAccount() {
if (this.atAccount == null)
this.atAccount = new Account(this.repository, this.atTransactionData.getATAddress());
return this.atAccount;
}
public Account getRecipient() throws DataException {
return new Account(this.repository, this.atTransactionData.getRecipient());
public Account getRecipient() {
if (this.recipientAccount == null)
this.recipientAccount = new Account(this.repository, this.atTransactionData.getRecipient());
return this.recipientAccount;
}
// Processing
@Override
public boolean hasValidReference() throws DataException {
// Check reference is correct
// Check reference is correct, using AT account, not transaction creator which is null account
Account atAccount = getATAccount();
return Arrays.equals(atAccount.getLastReference(), atTransactionData.getReference());
}
@Override
public ValidationResult isValid() throws DataException {
if (this.atTransactionData.getMessage().length > MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH;
BigDecimal amount = this.atTransactionData.getAmount();
byte[] message = this.atTransactionData.getMessage();
// We can only have either message or amount
boolean amountIsZero = amount.compareTo(BigDecimal.ZERO.setScale(8)) == 0;
boolean messageIsEmpty = message.length == 0;
if ((messageIsEmpty && amountIsZero) || (!messageIsEmpty && !amountIsZero))
return ValidationResult.INVALID_AT_TRANSACTION;
// If we have no payment then we're done
if (amountIsZero)
return ValidationResult.OK;
// Check amount is zero or positive
if (amount.compareTo(BigDecimal.ZERO) < 0)
return ValidationResult.NEGATIVE_AMOUNT;
// Check recipient address is valid
if (!Crypto.isValidAddress(this.atTransactionData.getRecipient()))
return ValidationResult.INVALID_ADDRESS;
Long amount = this.atTransactionData.getAmount();
byte[] message = this.atTransactionData.getMessage();
// We can only have either message or amount
boolean amountIsNull = amount == null;
boolean messageIsEmpty = message == null || message.length == 0;
if ((messageIsEmpty && amountIsNull) || (!messageIsEmpty && !amountIsNull))
return ValidationResult.INVALID_AT_TRANSACTION;
if (!messageIsEmpty && message.length > MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH;
// If we have no payment then we're done
if (amountIsNull)
return ValidationResult.OK;
// Check amount is zero or positive
if (amount < 0)
return ValidationResult.NEGATIVE_AMOUNT;
long assetId = this.atTransactionData.getAssetId();
AssetData assetData = this.repository.getAssetRepository().fromAssetId(assetId);
// Check asset even exists
@ -148,12 +113,12 @@ public class AtTransaction extends Transaction {
return ValidationResult.ASSET_DOES_NOT_EXIST;
// Check asset amount is integer if asset is not divisible
if (!assetData.getIsDivisible() && amount.stripTrailingZeros().scale() > 0)
if (!assetData.getIsDivisible() && amount % Asset.MULTIPLIER != 0)
return ValidationResult.INVALID_AMOUNT;
Account sender = getATAccount();
// Check sender has enough of asset
if (sender.getConfirmedBalance(assetId).compareTo(amount) < 0)
if (sender.getConfirmedBalance(assetId) < amount)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -161,18 +126,19 @@ public class AtTransaction extends Transaction {
@Override
public void process() throws DataException {
if (this.atTransactionData.getAmount() != null) {
Long amount = this.atTransactionData.getAmount();
if (amount != null) {
Account sender = getATAccount();
Account recipient = getRecipient();
long assetId = this.atTransactionData.getAssetId();
BigDecimal amount = this.atTransactionData.getAmount();
// Update sender's balance due to amount
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(amount));
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId) - amount);
// Update recipient's balance
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(amount));
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId) + amount);
}
}
@ -192,18 +158,19 @@ public class AtTransaction extends Transaction {
@Override
public void orphan() throws DataException {
if (this.atTransactionData.getAmount() != null) {
Long amount = this.atTransactionData.getAmount();
if (amount != null) {
Account sender = getATAccount();
Account recipient = getRecipient();
long assetId = this.atTransactionData.getAssetId();
BigDecimal amount = this.atTransactionData.getAmount();
// Update sender's balance due to amount
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).add(amount));
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId) + amount);
// Update recipient's balance
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).subtract(amount));
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId) - amount);
}
// As AT_TRANSACTIONs are really part of a block, the caller (Block) will probably delete this transaction after orphaning

View File

@ -1,12 +1,11 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.data.naming.NameData;
import org.qortal.data.transaction.BuyNameTransactionData;
import org.qortal.data.transaction.TransactionData;
@ -19,6 +18,7 @@ import com.google.common.base.Utf8;
public class BuyNameTransaction extends Transaction {
// Properties
private BuyNameTransactionData buyNameTransactionData;
// Constructors
@ -32,57 +32,36 @@ public class BuyNameTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(new Account(this.repository, this.buyNameTransactionData.getSeller()));
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getBuyer().getAddress()))
return true;
if (address.equals(this.buyNameTransactionData.getSeller()))
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.getBuyer().getAddress()))
amount = amount.subtract(this.transactionData.getFee()).subtract(this.buyNameTransactionData.getAmount());
if (address.equals(this.buyNameTransactionData.getSeller()))
amount = amount.add(this.buyNameTransactionData.getAmount());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.buyNameTransactionData.getSeller());
}
// Navigation
public Account getBuyer() throws DataException {
return new PublicKeyAccount(this.repository, this.buyNameTransactionData.getBuyerPublicKey());
public Account getBuyer() {
return this.getCreator();
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
String name = this.buyNameTransactionData.getName();
// Check seller address is valid
if (!Crypto.isValidAddress(this.buyNameTransactionData.getSeller()))
return ValidationResult.INVALID_ADDRESS;
// Check name size bounds
int nameLength = Utf8.encodedLength(buyNameTransactionData.getName());
int nameLength = Utf8.encodedLength(name);
if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH;
// Check name is lowercase
if (!buyNameTransactionData.getName().equals(buyNameTransactionData.getName().toLowerCase()))
if (!name.equals(name.toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE;
NameData nameData = this.repository.getNameRepository().fromName(buyNameTransactionData.getName());
NameData nameData = this.repository.getNameRepository().fromName(name);
// Check name exists
if (nameData == null)
@ -98,19 +77,15 @@ public class BuyNameTransaction extends Transaction {
return ValidationResult.BUYER_ALREADY_OWNER;
// Check expected seller currently owns name
if (!buyNameTransactionData.getSeller().equals(nameData.getOwner()))
if (!this.buyNameTransactionData.getSeller().equals(nameData.getOwner()))
return ValidationResult.INVALID_SELLER;
// Check amounts agree
if (buyNameTransactionData.getAmount().compareTo(nameData.getSalePrice()) != 0)
if (this.buyNameTransactionData.getAmount() != nameData.getSalePrice())
return ValidationResult.INVALID_AMOUNT;
// Check fee is positive
if (buyNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check issuer has enough funds
if (buyer.getConfirmedBalance(Asset.QORT).compareTo(buyNameTransactionData.getFee()) < 0)
if (buyer.getConfirmedBalance(Asset.QORT) < this.buyNameTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -119,21 +94,21 @@ public class BuyNameTransaction extends Transaction {
@Override
public void process() throws DataException {
// Update Name
Name name = new Name(this.repository, buyNameTransactionData.getName());
name.buy(buyNameTransactionData);
Name name = new Name(this.repository, this.buyNameTransactionData.getName());
name.buy(this.buyNameTransactionData);
// Save transaction with updated "name reference" pointing to previous transaction that updated name
this.repository.getTransactionRepository().save(buyNameTransactionData);
this.repository.getTransactionRepository().save(this.buyNameTransactionData);
}
@Override
public void orphan() throws DataException {
// Revert name
Name name = new Name(this.repository, buyNameTransactionData.getName());
name.unbuy(buyNameTransactionData);
Name name = new Name(this.repository, this.buyNameTransactionData.getName());
name.unbuy(this.buyNameTransactionData);
// Save this transaction, with removed "name reference"
this.repository.getTransactionRepository().save(buyNameTransactionData);
this.repository.getTransactionRepository().save(this.buyNameTransactionData);
}
}

View File

@ -1,14 +1,12 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.asset.Order;
import org.qortal.crypto.Crypto;
import org.qortal.data.asset.OrderData;
import org.qortal.data.transaction.CancelAssetOrderTransactionData;
import org.qortal.data.transaction.TransactionData;
@ -32,30 +30,8 @@ public class CancelAssetOrderTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() {
return new ArrayList<>();
}
@Override
public boolean isInvolved(Account account) throws DataException {
return account.getAddress().equals(this.getCreator().getAddress());
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
BigDecimal amount = BigDecimal.ZERO.setScale(8);
if (account.getAddress().equals(this.getCreator().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
}
// Navigation
@Override
public PublicKeyAccount getCreator() throws DataException {
return new PublicKeyAccount(this.repository, cancelOrderTransactionData.getCreatorPublicKey());
public List<String> getRecipientAddresses() {
return Collections.emptyList();
}
// Processing
@ -64,12 +40,8 @@ public class CancelAssetOrderTransaction extends Transaction {
public ValidationResult isValid() throws DataException {
AssetRepository assetRepository = this.repository.getAssetRepository();
// Check fee is positive
if (cancelOrderTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check order even exists
OrderData orderData = assetRepository.fromOrderId(cancelOrderTransactionData.getOrderId());
OrderData orderData = assetRepository.fromOrderId(this.cancelOrderTransactionData.getOrderId());
if (orderData == null)
return ValidationResult.ORDER_DOES_NOT_EXIST;
@ -77,19 +49,14 @@ public class CancelAssetOrderTransaction extends Transaction {
if (orderData.getIsClosed())
return ValidationResult.ORDER_ALREADY_CLOSED;
Account creator = getCreator();
// Check creator's public key results in valid address
if (!Crypto.isValidAddress(creator.getAddress()))
return ValidationResult.INVALID_ADDRESS;
// Check creator's public key matches order's creator's public key
Account orderCreator = new PublicKeyAccount(this.repository, orderData.getCreatorPublicKey());
if (!orderCreator.getAddress().equals(creator.getAddress()))
// Check transaction creator matches order creator
if (!Arrays.equals(this.transactionData.getCreatorPublicKey(), orderData.getCreatorPublicKey()))
return ValidationResult.INVALID_ORDER_CREATOR;
Account creator = getCreator();
// Check creator has enough QORT for fee
if (creator.getConfirmedBalance(Asset.QORT).compareTo(cancelOrderTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT) < this.cancelOrderTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -98,7 +65,7 @@ public class CancelAssetOrderTransaction extends Transaction {
@Override
public void process() throws DataException {
// Mark Order as completed so no more trades can happen
OrderData orderData = this.repository.getAssetRepository().fromOrderId(cancelOrderTransactionData.getOrderId());
OrderData orderData = this.repository.getAssetRepository().fromOrderId(this.cancelOrderTransactionData.getOrderId());
Order order = new Order(this.repository, orderData);
order.cancel();
}
@ -106,7 +73,7 @@ public class CancelAssetOrderTransaction extends Transaction {
@Override
public void orphan() throws DataException {
// Unmark Order as completed so trades can happen again
OrderData orderData = this.repository.getAssetRepository().fromOrderId(cancelOrderTransactionData.getOrderId());
OrderData orderData = this.repository.getAssetRepository().fromOrderId(this.cancelOrderTransactionData.getOrderId());
Order order = new Order(this.repository, orderData);
order.reopen();
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.data.group.GroupData;
@ -18,7 +16,9 @@ import org.qortal.repository.Repository;
public class CancelGroupBanTransaction extends Transaction {
// Properties
private CancelGroupBanTransactionData groupUnbanTransactionData;
private Account memberAccount = null;
// Constructors
@ -31,53 +31,34 @@ public class CancelGroupBanTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.emptyList();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getAdmin().getAddress()))
return true;
if (address.equals(this.getMember().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.getAdmin().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.groupUnbanTransactionData.getMember());
}
// Navigation
public Account getAdmin() throws DataException {
return new PublicKeyAccount(this.repository, this.groupUnbanTransactionData.getAdminPublicKey());
public Account getAdmin() {
return this.getCreator();
}
public Account getMember() throws DataException {
return new Account(this.repository, this.groupUnbanTransactionData.getMember());
public Account getMember() {
if (this.memberAccount == null)
this.memberAccount = new Account(this.repository, this.groupUnbanTransactionData.getMember());
return this.memberAccount;
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
int groupId = this.groupUnbanTransactionData.getGroupId();
// Check member address is valid
if (!Crypto.isValidAddress(groupUnbanTransactionData.getMember()))
if (!Crypto.isValidAddress(this.groupUnbanTransactionData.getMember()))
return ValidationResult.INVALID_ADDRESS;
GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupUnbanTransactionData.getGroupId());
GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupId);
// Check group exists
if (groupData == null)
@ -86,21 +67,17 @@ public class CancelGroupBanTransaction extends Transaction {
Account admin = getAdmin();
// Can't unban if not an admin
if (!this.repository.getGroupRepository().adminExists(groupUnbanTransactionData.getGroupId(), admin.getAddress()))
if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress()))
return ValidationResult.NOT_GROUP_ADMIN;
Account member = getMember();
// Check ban actually exists
if (!this.repository.getGroupRepository().banExists(groupUnbanTransactionData.getGroupId(), member.getAddress()))
if (!this.repository.getGroupRepository().banExists(groupId, member.getAddress()))
return ValidationResult.BAN_UNKNOWN;
// Check fee is positive
if (groupUnbanTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (admin.getConfirmedBalance(Asset.QORT).compareTo(groupUnbanTransactionData.getFee()) < 0)
// Check admin has enough funds
if (admin.getConfirmedBalance(Asset.QORT) < this.groupUnbanTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -109,21 +86,21 @@ public class CancelGroupBanTransaction extends Transaction {
@Override
public void process() throws DataException {
// Update Group Membership
Group group = new Group(this.repository, groupUnbanTransactionData.getGroupId());
group.cancelBan(groupUnbanTransactionData);
Group group = new Group(this.repository, this.groupUnbanTransactionData.getGroupId());
group.cancelBan(this.groupUnbanTransactionData);
// Save this transaction with updated member/admin references to transactions that can help restore state
this.repository.getTransactionRepository().save(groupUnbanTransactionData);
this.repository.getTransactionRepository().save(this.groupUnbanTransactionData);
}
@Override
public void orphan() throws DataException {
// Revert group membership
Group group = new Group(this.repository, groupUnbanTransactionData.getGroupId());
group.uncancelBan(groupUnbanTransactionData);
Group group = new Group(this.repository, this.groupUnbanTransactionData.getGroupId());
group.uncancelBan(this.groupUnbanTransactionData);
// Save this transaction with removed member/admin references
this.repository.getTransactionRepository().save(groupUnbanTransactionData);
this.repository.getTransactionRepository().save(this.groupUnbanTransactionData);
}
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.data.group.GroupData;
@ -18,7 +16,9 @@ import org.qortal.repository.Repository;
public class CancelGroupInviteTransaction extends Transaction {
// Properties
private CancelGroupInviteTransactionData cancelGroupInviteTransactionData;
private Account inviteeAccount = null;
// Constructors
@ -31,53 +31,34 @@ public class CancelGroupInviteTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.emptyList();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getAdmin().getAddress()))
return true;
if (address.equals(this.getInvitee().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.getAdmin().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.cancelGroupInviteTransactionData.getInvitee());
}
// Navigation
public Account getAdmin() throws DataException {
return new PublicKeyAccount(this.repository, this.cancelGroupInviteTransactionData.getAdminPublicKey());
public Account getAdmin() {
return this.getCreator();
}
public Account getInvitee() throws DataException {
return new Account(this.repository, this.cancelGroupInviteTransactionData.getInvitee());
public Account getInvitee() {
if (this.inviteeAccount == null)
this.inviteeAccount = new Account(this.repository, this.cancelGroupInviteTransactionData.getInvitee());
return this.inviteeAccount;
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
int groupId = this.cancelGroupInviteTransactionData.getGroupId();
// Check invitee address is valid
if (!Crypto.isValidAddress(cancelGroupInviteTransactionData.getInvitee()))
if (!Crypto.isValidAddress(this.cancelGroupInviteTransactionData.getInvitee()))
return ValidationResult.INVALID_ADDRESS;
GroupData groupData = this.repository.getGroupRepository().fromGroupId(cancelGroupInviteTransactionData.getGroupId());
GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupId);
// Check group exists
if (groupData == null)
@ -86,21 +67,17 @@ public class CancelGroupInviteTransaction extends Transaction {
Account admin = getAdmin();
// Check admin is actually an admin
if (!this.repository.getGroupRepository().adminExists(cancelGroupInviteTransactionData.getGroupId(), admin.getAddress()))
if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress()))
return ValidationResult.NOT_GROUP_ADMIN;
Account invitee = getInvitee();
// Check invite exists
if (!this.repository.getGroupRepository().inviteExists(cancelGroupInviteTransactionData.getGroupId(), invitee.getAddress()))
if (!this.repository.getGroupRepository().inviteExists(groupId, invitee.getAddress()))
return ValidationResult.INVITE_UNKNOWN;
// Check fee is positive
if (cancelGroupInviteTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (admin.getConfirmedBalance(Asset.QORT).compareTo(cancelGroupInviteTransactionData.getFee()) < 0)
if (admin.getConfirmedBalance(Asset.QORT) < this.cancelGroupInviteTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -109,21 +86,21 @@ public class CancelGroupInviteTransaction extends Transaction {
@Override
public void process() throws DataException {
// Update Group Membership
Group group = new Group(this.repository, cancelGroupInviteTransactionData.getGroupId());
group.cancelInvite(cancelGroupInviteTransactionData);
Group group = new Group(this.repository, this.cancelGroupInviteTransactionData.getGroupId());
group.cancelInvite(this.cancelGroupInviteTransactionData);
// Save this transaction with updated member/admin references to transactions that can help restore state
this.repository.getTransactionRepository().save(cancelGroupInviteTransactionData);
this.repository.getTransactionRepository().save(this.cancelGroupInviteTransactionData);
}
@Override
public void orphan() throws DataException {
// Revert group membership
Group group = new Group(this.repository, cancelGroupInviteTransactionData.getGroupId());
group.uncancelInvite(cancelGroupInviteTransactionData);
Group group = new Group(this.repository, this.cancelGroupInviteTransactionData.getGroupId());
group.uncancelInvite(this.cancelGroupInviteTransactionData);
// Save this transaction with removed member/admin references
this.repository.getTransactionRepository().save(cancelGroupInviteTransactionData);
this.repository.getTransactionRepository().save(this.cancelGroupInviteTransactionData);
}
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.naming.NameData;
import org.qortal.data.transaction.CancelSellNameTransactionData;
@ -32,51 +30,32 @@ public class CancelSellNameTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() {
return new ArrayList<>();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getOwner().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;
public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList();
}
// Navigation
public Account getOwner() throws DataException {
return new PublicKeyAccount(this.repository, this.cancelSellNameTransactionData.getOwnerPublicKey());
public Account getOwner() {
return this.getCreator();
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
String name = this.cancelSellNameTransactionData.getName();
// Check name size bounds
int nameLength = Utf8.encodedLength(cancelSellNameTransactionData.getName());
int nameLength = Utf8.encodedLength(name);
if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH;
// Check name is lowercase
if (!cancelSellNameTransactionData.getName().equals(cancelSellNameTransactionData.getName().toLowerCase()))
if (!name.equals(name.toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE;
NameData nameData = this.repository.getNameRepository().fromName(cancelSellNameTransactionData.getName());
NameData nameData = this.repository.getNameRepository().fromName(name);
// Check name exists
if (nameData == null)
@ -86,17 +65,13 @@ public class CancelSellNameTransaction extends Transaction {
if (!nameData.getIsForSale())
return ValidationResult.NAME_NOT_FOR_SALE;
// Check transaction's public key matches name's current owner
// Check transaction creator matches name's current owner
Account owner = getOwner();
if (!owner.getAddress().equals(nameData.getOwner()))
return ValidationResult.INVALID_NAME_OWNER;
// Check fee is positive
if (cancelSellNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check issuer has enough funds
if (owner.getConfirmedBalance(Asset.QORT).compareTo(cancelSellNameTransactionData.getFee()) < 0)
if (owner.getConfirmedBalance(Asset.QORT) < cancelSellNameTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.asset.Order;
import org.qortal.data.asset.AssetData;
@ -32,32 +30,12 @@ public class CreateAssetOrderTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() {
return new ArrayList<>();
}
@Override
public boolean isInvolved(Account account) throws DataException {
return account.getAddress().equals(this.getCreator().getAddress());
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
BigDecimal amount = BigDecimal.ZERO.setScale(8);
if (account.getAddress().equals(this.getCreator().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList();
}
// Navigation
@Override
public PublicKeyAccount getCreator() throws DataException {
return new PublicKeyAccount(this.repository, createOrderTransactionData.getCreatorPublicKey());
}
public Order getOrder() throws DataException {
// orderId is the transaction signature
OrderData orderData = this.repository.getAssetRepository().fromOrderId(this.createOrderTransactionData.getSignature());
@ -68,25 +46,21 @@ public class CreateAssetOrderTransaction extends Transaction {
@Override
public ValidationResult isValid() throws DataException {
long haveAssetId = createOrderTransactionData.getHaveAssetId();
long wantAssetId = createOrderTransactionData.getWantAssetId();
long haveAssetId = this.createOrderTransactionData.getHaveAssetId();
long wantAssetId = this.createOrderTransactionData.getWantAssetId();
// Check have/want assets are not the same
if (haveAssetId == wantAssetId)
return ValidationResult.HAVE_EQUALS_WANT;
// Check amount is positive
if (createOrderTransactionData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
if (this.createOrderTransactionData.getAmount() <= 0)
return ValidationResult.NEGATIVE_AMOUNT;
// Check price is positive
if (createOrderTransactionData.getPrice().compareTo(BigDecimal.ZERO) <= 0)
if (this.createOrderTransactionData.getPrice() <= 0)
return ValidationResult.NEGATIVE_PRICE;
// Check fee is positive
if (createOrderTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
AssetRepository assetRepository = this.repository.getAssetRepository();
// Check "have" asset exists
@ -105,8 +79,8 @@ public class CreateAssetOrderTransaction extends Transaction {
Account creator = getCreator();
BigDecimal committedCost;
BigDecimal maxOtherAmount;
long committedCost;
long maxOtherAmount;
/*
* "amount" might be either have-asset or want-asset, whichever has the highest assetID.
@ -122,35 +96,35 @@ public class CreateAssetOrderTransaction extends Transaction {
if (isAmountWantAsset) {
// have/commit 49200 QORT, want/return 123 GOLD
committedCost = createOrderTransactionData.getAmount().multiply(createOrderTransactionData.getPrice());
maxOtherAmount = createOrderTransactionData.getAmount();
committedCost = this.createOrderTransactionData.getAmount() * this.createOrderTransactionData.getPrice();
maxOtherAmount = this.createOrderTransactionData.getAmount();
} else {
// have/commit 123 GOLD, want/return 49200 QORT
committedCost = createOrderTransactionData.getAmount();
maxOtherAmount = createOrderTransactionData.getAmount().multiply(createOrderTransactionData.getPrice());
committedCost = this.createOrderTransactionData.getAmount();
maxOtherAmount = this.createOrderTransactionData.getAmount() * this.createOrderTransactionData.getPrice();
}
// Check amount is integer if amount's asset is not divisible
if (!haveAssetData.getIsDivisible() && committedCost.stripTrailingZeros().scale() > 0)
if (!haveAssetData.getIsDivisible() && committedCost % Asset.MULTIPLIER != 0)
return ValidationResult.INVALID_AMOUNT;
// Check total return from fulfilled order would be integer if return's asset is not divisible
if (!wantAssetData.getIsDivisible() && maxOtherAmount.stripTrailingZeros().scale() > 0)
if (!wantAssetData.getIsDivisible() && maxOtherAmount % Asset.MULTIPLIER != 0)
return ValidationResult.INVALID_RETURN;
// Check order creator has enough asset balance AFTER removing fee, in case asset is QORT
// If asset is QORT then we need to check amount + fee in one go
if (haveAssetId == Asset.QORT) {
// Check creator has enough funds for amount + fee in QORT
if (creator.getConfirmedBalance(Asset.QORT).compareTo(committedCost.add(createOrderTransactionData.getFee())) < 0)
if (creator.getConfirmedBalance(Asset.QORT) < committedCost + this.createOrderTransactionData.getFee())
return ValidationResult.NO_BALANCE;
} else {
// Check creator has enough funds for amount in whatever asset
if (creator.getConfirmedBalance(haveAssetId).compareTo(committedCost) < 0)
if (creator.getConfirmedBalance(haveAssetId) < committedCost)
return ValidationResult.NO_BALANCE;
// Check creator has enough funds for fee in QORT
if (creator.getConfirmedBalance(Asset.QORT).compareTo(createOrderTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT) < this.createOrderTransactionData.getFee())
return ValidationResult.NO_BALANCE;
}
@ -160,12 +134,13 @@ public class CreateAssetOrderTransaction extends Transaction {
@Override
public void process() throws DataException {
// Order Id is transaction's signature
byte[] orderId = createOrderTransactionData.getSignature();
byte[] orderId = this.createOrderTransactionData.getSignature();
// Process the order itself
OrderData orderData = new OrderData(orderId, createOrderTransactionData.getCreatorPublicKey(), createOrderTransactionData.getHaveAssetId(),
createOrderTransactionData.getWantAssetId(), createOrderTransactionData.getAmount(), createOrderTransactionData.getPrice(),
createOrderTransactionData.getTimestamp());
OrderData orderData = new OrderData(orderId, this.createOrderTransactionData.getCreatorPublicKey(),
this.createOrderTransactionData.getHaveAssetId(), this.createOrderTransactionData.getWantAssetId(),
this.createOrderTransactionData.getAmount(), this.createOrderTransactionData.getPrice(),
this.createOrderTransactionData.getTimestamp());
new Order(this.repository, orderData).process();
}
@ -173,7 +148,7 @@ public class CreateAssetOrderTransaction extends Transaction {
@Override
public void orphan() throws DataException {
// Order Id is transaction's signature
byte[] orderId = createOrderTransactionData.getSignature();
byte[] orderId = this.createOrderTransactionData.getSignature();
// Orphan the order itself
OrderData orderData = this.repository.getAssetRepository().fromOrderId(orderId);

View File

@ -1,6 +1,5 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
@ -31,38 +30,14 @@ public class CreateGroupTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(getOwner());
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getCreator().getAddress()))
return true;
if (address.equals(this.getOwner().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.getCreator().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.createGroupTransactionData.getOwner());
}
// Navigation
public Account getOwner() throws DataException {
return new Account(this.repository, this.createGroupTransactionData.getOwner());
public Account getOwner() {
return this.getCreator();
}
// Processing
@ -70,45 +45,41 @@ public class CreateGroupTransaction extends Transaction {
@Override
public ValidationResult isValid() throws DataException {
// Check owner address is valid
if (!Crypto.isValidAddress(createGroupTransactionData.getOwner()))
if (!Crypto.isValidAddress(this.createGroupTransactionData.getOwner()))
return ValidationResult.INVALID_ADDRESS;
// Check approval threshold is valid
if (createGroupTransactionData.getApprovalThreshold() == null)
if (this.createGroupTransactionData.getApprovalThreshold() == null)
return ValidationResult.INVALID_GROUP_APPROVAL_THRESHOLD;
// Check min/max block delay values
if (createGroupTransactionData.getMinimumBlockDelay() < 0)
if (this.createGroupTransactionData.getMinimumBlockDelay() < 0)
return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
if (createGroupTransactionData.getMaximumBlockDelay() < 1)
if (this.createGroupTransactionData.getMaximumBlockDelay() < 1)
return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
if (createGroupTransactionData.getMaximumBlockDelay() < createGroupTransactionData.getMinimumBlockDelay())
if (this.createGroupTransactionData.getMaximumBlockDelay() < this.createGroupTransactionData.getMinimumBlockDelay())
return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
// Check group name size bounds
int groupNameLength = Utf8.encodedLength(createGroupTransactionData.getGroupName());
int groupNameLength = Utf8.encodedLength(this.createGroupTransactionData.getGroupName());
if (groupNameLength < 1 || groupNameLength > Group.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH;
// Check description size bounds
int descriptionLength = Utf8.encodedLength(createGroupTransactionData.getDescription());
int descriptionLength = Utf8.encodedLength(this.createGroupTransactionData.getDescription());
if (descriptionLength < 1 || descriptionLength > Group.MAX_DESCRIPTION_SIZE)
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
// Check group name is lowercase
if (!createGroupTransactionData.getGroupName().equals(createGroupTransactionData.getGroupName().toLowerCase()))
if (!this.createGroupTransactionData.getGroupName().equals(this.createGroupTransactionData.getGroupName().toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE;
// Check fee is positive
if (createGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
Account creator = getCreator();
// Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORT).compareTo(createGroupTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT) < this.createGroupTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -117,7 +88,7 @@ public class CreateGroupTransaction extends Transaction {
@Override
public ValidationResult isProcessable() throws DataException {
// Check the group name isn't already taken
if (this.repository.getGroupRepository().groupExists(createGroupTransactionData.getGroupName()))
if (this.repository.getGroupRepository().groupExists(this.createGroupTransactionData.getGroupName()))
return ValidationResult.GROUP_ALREADY_EXISTS;
return ValidationResult.OK;
@ -126,27 +97,27 @@ public class CreateGroupTransaction extends Transaction {
@Override
public void process() throws DataException {
// Create Group
Group group = new Group(this.repository, createGroupTransactionData);
group.create(createGroupTransactionData);
Group group = new Group(this.repository, this.createGroupTransactionData);
group.create(this.createGroupTransactionData);
// Note newly assigned group ID in our transaction record
createGroupTransactionData.setGroupId(group.getGroupData().getGroupId());
this.createGroupTransactionData.setGroupId(group.getGroupData().getGroupId());
// Save this transaction with newly assigned group ID
this.repository.getTransactionRepository().save(createGroupTransactionData);
this.repository.getTransactionRepository().save(this.createGroupTransactionData);
}
@Override
public void orphan() throws DataException {
// Uncreate group
Group group = new Group(this.repository, createGroupTransactionData.getGroupId());
Group group = new Group(this.repository, this.createGroupTransactionData.getGroupId());
group.uncreate();
// Remove assigned group ID from transaction record
createGroupTransactionData.setGroupId(null);
this.createGroupTransactionData.setGroupId(null);
// Save this transaction with removed group ID
this.repository.getTransactionRepository().save(createGroupTransactionData);
this.repository.getTransactionRepository().save(this.createGroupTransactionData);
}
}

View File

@ -1,12 +1,10 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.data.transaction.CreatePollTransactionData;
@ -34,42 +32,13 @@ public class CreatePollTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(getOwner());
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getCreator().getAddress()))
return true;
if (address.equals(this.getOwner().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.getCreator().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.createPollTransactionData.getOwner());
}
// Navigation
@Override
public PublicKeyAccount getCreator() throws DataException {
return new PublicKeyAccount(this.repository, this.createPollTransactionData.getCreatorPublicKey());
}
public Account getOwner() throws DataException {
public Account getOwner() {
return new Account(this.repository, this.createPollTransactionData.getOwner());
}
@ -78,33 +47,31 @@ public class CreatePollTransaction extends Transaction {
@Override
public ValidationResult isValid() throws DataException {
// Check owner address is valid
if (!Crypto.isValidAddress(createPollTransactionData.getOwner()))
if (!Crypto.isValidAddress(this.createPollTransactionData.getOwner()))
return ValidationResult.INVALID_ADDRESS;
// Check name size bounds
int pollNameLength = Utf8.encodedLength(createPollTransactionData.getPollName());
int pollNameLength = Utf8.encodedLength(this.createPollTransactionData.getPollName());
if (pollNameLength < 1 || pollNameLength > Poll.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH;
// Check description size bounds
int pollDescriptionLength = Utf8.encodedLength(createPollTransactionData.getDescription());
int pollDescriptionLength = Utf8.encodedLength(this.createPollTransactionData.getDescription());
if (pollDescriptionLength < 1 || pollDescriptionLength > Poll.MAX_DESCRIPTION_SIZE)
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
// Check poll name is lowercase
if (!createPollTransactionData.getPollName().equals(createPollTransactionData.getPollName().toLowerCase()))
if (!this.createPollTransactionData.getPollName().equals(this.createPollTransactionData.getPollName().toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE;
// In gen1 we tested for presence of existing votes but how could there be any if poll doesn't exist?
// Check number of options
List<PollOptionData> pollOptions = createPollTransactionData.getPollOptions();
List<PollOptionData> pollOptions = this.createPollTransactionData.getPollOptions();
int pollOptionsCount = pollOptions.size();
if (pollOptionsCount < 1 || pollOptionsCount > Poll.MAX_OPTIONS)
return ValidationResult.INVALID_OPTIONS_COUNT;
// Check each option
List<String> optionNames = new ArrayList<String>();
List<String> optionNames = new ArrayList<>();
for (PollOptionData pollOptionData : pollOptions) {
// Check option length
int optionNameLength = Utf8.encodedLength(pollOptionData.getOptionName());
@ -119,15 +86,10 @@ public class CreatePollTransaction extends Transaction {
optionNames.add(pollOptionData.getOptionName());
}
// Check fee is positive
if (createPollTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check reference is correct
Account creator = getCreator();
// Check issuer has enough funds
if (creator.getConfirmedBalance(Asset.QORT).compareTo(createPollTransactionData.getFee()) < 0)
// Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORT) < this.createPollTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -136,7 +98,7 @@ public class CreatePollTransaction extends Transaction {
@Override
public ValidationResult isProcessable() throws DataException {
// Check the poll name isn't already taken
if (this.repository.getVotingRepository().pollExists(createPollTransactionData.getPollName()))
if (this.repository.getVotingRepository().pollExists(this.createPollTransactionData.getPollName()))
return ValidationResult.POLL_ALREADY_EXISTS;
return ValidationResult.OK;
@ -145,14 +107,14 @@ public class CreatePollTransaction extends Transaction {
@Override
public void process() throws DataException {
// Publish poll to allow voting
Poll poll = new Poll(this.repository, createPollTransactionData);
Poll poll = new Poll(this.repository, this.createPollTransactionData);
poll.publish();
}
@Override
public void orphan() throws DataException {
// Unpublish poll
Poll poll = new Poll(this.repository, createPollTransactionData.getPollName());
Poll poll = new Poll(this.repository, this.createPollTransactionData.getPollName());
poll.unpublish();
}

View File

@ -1,10 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.ciyam.at.MachineState;
@ -44,35 +43,8 @@ public class DeployAtTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return new ArrayList<>();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getCreator().getAddress()))
return true;
if (address.equals(this.getATAccount().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.getCreator().getAddress()))
amount = amount.subtract(this.deployATTransactionData.getAmount()).subtract(this.transactionData.getFee());
if (address.equals(this.getATAccount().getAddress()))
amount = amount.add(this.deployATTransactionData.getAmount());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.deployATTransactionData.getAtAddress());
}
/** Returns AT version from the header bytes */
@ -124,30 +96,30 @@ public class DeployAtTransaction extends Transaction {
@Override
public ValidationResult isValid() throws DataException {
// Check name size bounds
int nameLength = Utf8.encodedLength(deployATTransactionData.getName());
int nameLength = Utf8.encodedLength(this.deployATTransactionData.getName());
if (nameLength < 1 || nameLength > MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH;
// Check description size bounds
int descriptionlength = Utf8.encodedLength(deployATTransactionData.getDescription());
int descriptionlength = Utf8.encodedLength(this.deployATTransactionData.getDescription());
if (descriptionlength < 1 || descriptionlength > MAX_DESCRIPTION_SIZE)
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
// Check AT-type size bounds
int ATTypeLength = Utf8.encodedLength(deployATTransactionData.getAtType());
if (ATTypeLength < 1 || ATTypeLength > MAX_AT_TYPE_SIZE)
int atTypeLength = Utf8.encodedLength(this.deployATTransactionData.getAtType());
if (atTypeLength < 1 || atTypeLength > MAX_AT_TYPE_SIZE)
return ValidationResult.INVALID_AT_TYPE_LENGTH;
// Check tags size bounds
int tagsLength = Utf8.encodedLength(deployATTransactionData.getTags());
int tagsLength = Utf8.encodedLength(this.deployATTransactionData.getTags());
if (tagsLength < 1 || tagsLength > MAX_TAGS_SIZE)
return ValidationResult.INVALID_TAGS_LENGTH;
// Check amount is positive
if (deployATTransactionData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
if (this.deployATTransactionData.getAmount() <= 0)
return ValidationResult.NEGATIVE_AMOUNT;
long assetId = deployATTransactionData.getAssetId();
long assetId = this.deployATTransactionData.getAssetId();
AssetData assetData = this.repository.getAssetRepository().fromAssetId(assetId);
// Check asset even exists
if (assetData == null)
@ -158,27 +130,23 @@ public class DeployAtTransaction extends Transaction {
return ValidationResult.ASSET_NOT_SPENDABLE;
// Check asset amount is integer if asset is not divisible
if (!assetData.getIsDivisible() && deployATTransactionData.getAmount().stripTrailingZeros().scale() > 0)
if (!assetData.getIsDivisible() && this.deployATTransactionData.getAmount() % Asset.MULTIPLIER != 0)
return ValidationResult.INVALID_AMOUNT;
// Check fee is positive
if (deployATTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
Account creator = getCreator();
Account creator = this.getCreator();
// Check creator has enough funds
if (assetId == Asset.QORT) {
// Simple case: amount and fee both in QORT
BigDecimal minimumBalance = deployATTransactionData.getFee().add(deployATTransactionData.getAmount());
long minimumBalance = this.deployATTransactionData.getFee() + this.deployATTransactionData.getAmount();
if (creator.getConfirmedBalance(Asset.QORT).compareTo(minimumBalance) < 0)
if (creator.getConfirmedBalance(Asset.QORT) < minimumBalance)
return ValidationResult.NO_BALANCE;
} else {
if (creator.getConfirmedBalance(Asset.QORT).compareTo(deployATTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT) < this.deployATTransactionData.getFee())
return ValidationResult.NO_BALANCE;
if (creator.getConfirmedBalance(assetId).compareTo(deployATTransactionData.getAmount()) < 0)
if (creator.getConfirmedBalance(assetId) < this.deployATTransactionData.getAmount())
return ValidationResult.NO_BALANCE;
}
@ -186,7 +154,7 @@ public class DeployAtTransaction extends Transaction {
if (this.getVersion() >= 2) {
// Do actual validation
try {
new MachineState(deployATTransactionData.getCreationBytes());
new MachineState(this.deployATTransactionData.getCreationBytes());
} catch (IllegalArgumentException e) {
// Not valid
return ValidationResult.INVALID_CREATION_BYTES;
@ -201,25 +169,25 @@ public class DeployAtTransaction extends Transaction {
@Override
public ValidationResult isProcessable() throws DataException {
Account creator = getCreator();
long assetId = deployATTransactionData.getAssetId();
long assetId = this.deployATTransactionData.getAssetId();
// Check creator has enough funds
if (assetId == Asset.QORT) {
// Simple case: amount and fee both in QORT
BigDecimal minimumBalance = deployATTransactionData.getFee().add(deployATTransactionData.getAmount());
long minimumBalance = this.deployATTransactionData.getFee() + this.deployATTransactionData.getAmount();
if (creator.getConfirmedBalance(Asset.QORT).compareTo(minimumBalance) < 0)
if (creator.getConfirmedBalance(Asset.QORT) < minimumBalance)
return ValidationResult.NO_BALANCE;
} else {
if (creator.getConfirmedBalance(Asset.QORT).compareTo(deployATTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT) < this.deployATTransactionData.getFee())
return ValidationResult.NO_BALANCE;
if (creator.getConfirmedBalance(assetId).compareTo(deployATTransactionData.getAmount()) < 0)
if (creator.getConfirmedBalance(assetId) < this.deployATTransactionData.getAmount())
return ValidationResult.NO_BALANCE;
}
// Check AT doesn't already exist
if (this.repository.getATRepository().exists(deployATTransactionData.getAtAddress()))
if (this.repository.getATRepository().exists(this.deployATTransactionData.getAtAddress()))
return ValidationResult.AT_ALREADY_EXISTS;
return ValidationResult.OK;
@ -227,24 +195,24 @@ public class DeployAtTransaction extends Transaction {
@Override
public void process() throws DataException {
ensureATAddress();
this.ensureATAddress();
// Deploy AT, saving into repository
AT at = new AT(this.repository, this.deployATTransactionData);
at.deploy();
long assetId = deployATTransactionData.getAssetId();
long assetId = this.deployATTransactionData.getAssetId();
// Update creator's balance regarding initial payment to AT
Account creator = getCreator();
creator.setConfirmedBalance(assetId, creator.getConfirmedBalance(assetId).subtract(deployATTransactionData.getAmount()));
creator.setConfirmedBalance(assetId, creator.getConfirmedBalance(assetId) - this.deployATTransactionData.getAmount());
// Update AT's reference, which also creates AT account
Account atAccount = this.getATAccount();
atAccount.setLastReference(deployATTransactionData.getSignature());
atAccount.setLastReference(this.deployATTransactionData.getSignature());
// Update AT's balance
atAccount.setConfirmedBalance(assetId, deployATTransactionData.getAmount());
atAccount.setConfirmedBalance(assetId, this.deployATTransactionData.getAmount());
}
@Override
@ -253,11 +221,11 @@ public class DeployAtTransaction extends Transaction {
AT at = new AT(this.repository, this.deployATTransactionData);
at.undeploy();
long assetId = deployATTransactionData.getAssetId();
long assetId = this.deployATTransactionData.getAssetId();
// Update creator's balance regarding initial payment to AT
Account creator = getCreator();
creator.setConfirmedBalance(assetId, creator.getConfirmedBalance(assetId).add(deployATTransactionData.getAmount()));
creator.setConfirmedBalance(assetId, creator.getConfirmedBalance(assetId) + this.deployATTransactionData.getAmount());
// Delete AT's account (and hence its balance)
this.repository.getAccountRepository().delete(this.deployATTransactionData.getAtAddress());

View File

@ -1,6 +1,5 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -36,40 +35,8 @@ public class GenesisTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(new Account(this.repository, genesisTransactionData.getRecipient()));
}
/** For Genesis Transactions, do not include transaction creator (which is genesis account) */
@Override
public List<Account> getInvolvedAccounts() throws DataException {
return getRecipientAccounts();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getCreator().getAddress()))
return true;
if (address.equals(genesisTransactionData.getRecipient()))
return true;
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
// NOTE: genesis transactions have no fee, so no need to test against creator as sender
if (address.equals(genesisTransactionData.getRecipient()))
amount = amount.add(genesisTransactionData.getAmount());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.genesisTransactionData.getRecipient());
}
// Processing
@ -123,11 +90,11 @@ public class GenesisTransaction extends Transaction {
@Override
public ValidationResult isValid() {
// Check amount is zero or positive
if (genesisTransactionData.getAmount().compareTo(BigDecimal.ZERO) < 0)
if (this.genesisTransactionData.getAmount() < 0)
return ValidationResult.NEGATIVE_AMOUNT;
// Check recipient address is valid
if (!Crypto.isValidAddress(genesisTransactionData.getRecipient()))
if (!Crypto.isValidAddress(this.genesisTransactionData.getRecipient()))
return ValidationResult.INVALID_ADDRESS;
return ValidationResult.OK;
@ -135,26 +102,26 @@ public class GenesisTransaction extends Transaction {
@Override
public void process() throws DataException {
Account recipient = new Account(repository, genesisTransactionData.getRecipient());
Account recipient = new Account(repository, this.genesisTransactionData.getRecipient());
// Update recipient's balance
recipient.setConfirmedBalance(genesisTransactionData.getAssetId(), genesisTransactionData.getAmount());
recipient.setConfirmedBalance(this.genesisTransactionData.getAssetId(), this.genesisTransactionData.getAmount());
}
@Override
public void processReferencesAndFees() throws DataException {
// Do not attempt to update non-existent genesis account's reference!
Account recipient = new Account(repository, genesisTransactionData.getRecipient());
Account recipient = new Account(repository, this.genesisTransactionData.getRecipient());
// Set recipient's starting reference (also creates account)
recipient.setLastReference(genesisTransactionData.getSignature());
recipient.setLastReference(this.genesisTransactionData.getSignature());
}
@Override
public void orphan() throws DataException {
// Delete recipient's account (and balance)
this.repository.getAccountRepository().delete(genesisTransactionData.getRecipient());
this.repository.getAccountRepository().delete(this.genesisTransactionData.getRecipient());
}
@Override

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.transaction.GroupApprovalTransactionData;
import org.qortal.data.transaction.TransactionData;
@ -28,35 +26,14 @@ public class GroupApprovalTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getAdmin().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.getAdmin().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
}
// Navigation
public Account getAdmin() throws DataException {
return new PublicKeyAccount(this.repository, this.groupApprovalTransactionData.getAdminPublicKey());
public Account getAdmin() {
return this.getCreator();
}
// Processing
@ -64,7 +41,7 @@ public class GroupApprovalTransaction extends Transaction {
@Override
public ValidationResult isValid() throws DataException {
// Grab pending transaction's data
TransactionData pendingTransactionData = this.repository.getTransactionRepository().fromSignature(groupApprovalTransactionData.getPendingSignature());
TransactionData pendingTransactionData = this.repository.getTransactionRepository().fromSignature(this.groupApprovalTransactionData.getPendingSignature());
if (pendingTransactionData == null)
return ValidationResult.TRANSACTION_UNKNOWN;
@ -82,12 +59,8 @@ public class GroupApprovalTransaction extends Transaction {
if (!this.repository.getGroupRepository().adminExists(pendingTransactionData.getTxGroupId(), admin.getAddress()))
return ValidationResult.NOT_GROUP_ADMIN;
// Check fee is positive
if (groupApprovalTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (admin.getConfirmedBalance(Asset.QORT).compareTo(groupApprovalTransactionData.getFee()) < 0)
if (admin.getConfirmedBalance(Asset.QORT) < this.groupApprovalTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -96,20 +69,20 @@ public class GroupApprovalTransaction extends Transaction {
@Override
public void process() throws DataException {
// Find previous approval decision (if any) by this admin for pending transaction
GroupApprovalTransactionData previousApproval = this.repository.getTransactionRepository().getLatestApproval(groupApprovalTransactionData.getPendingSignature(), groupApprovalTransactionData.getAdminPublicKey());
GroupApprovalTransactionData previousApproval = this.repository.getTransactionRepository().getLatestApproval(this.groupApprovalTransactionData.getPendingSignature(), this.groupApprovalTransactionData.getAdminPublicKey());
if (previousApproval != null)
groupApprovalTransactionData.setPriorReference(previousApproval.getSignature());
this.groupApprovalTransactionData.setPriorReference(previousApproval.getSignature());
// Save this transaction with updated prior reference to transaction that can help restore state
this.repository.getTransactionRepository().save(groupApprovalTransactionData);
this.repository.getTransactionRepository().save(this.groupApprovalTransactionData);
}
@Override
public void orphan() throws DataException {
// Save this transaction with removed prior reference
groupApprovalTransactionData.setPriorReference(null);
this.repository.getTransactionRepository().save(groupApprovalTransactionData);
this.groupApprovalTransactionData.setPriorReference(null);
this.repository.getTransactionRepository().save(this.groupApprovalTransactionData);
}
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.data.group.GroupData;
@ -18,7 +16,9 @@ import org.qortal.repository.Repository;
public class GroupBanTransaction extends Transaction {
// Properties
private GroupBanTransactionData groupBanTransactionData;
private Account offenderAccount = null;
// Constructors
@ -31,53 +31,34 @@ public class GroupBanTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.emptyList();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getAdmin().getAddress()))
return true;
if (address.equals(this.getOffender().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.getAdmin().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.groupBanTransactionData.getOffender());
}
// Navigation
public Account getAdmin() throws DataException {
return new PublicKeyAccount(this.repository, this.groupBanTransactionData.getAdminPublicKey());
public Account getAdmin() {
return this.getCreator();
}
public Account getOffender() throws DataException {
return new Account(this.repository, this.groupBanTransactionData.getOffender());
public Account getOffender() {
if (this.offenderAccount == null)
this.offenderAccount = new Account(this.repository, this.groupBanTransactionData.getOffender());
return this.offenderAccount;
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
int groupId = this.groupBanTransactionData.getGroupId();
// Check offender address is valid
if (!Crypto.isValidAddress(groupBanTransactionData.getOffender()))
if (!Crypto.isValidAddress(this.groupBanTransactionData.getOffender()))
return ValidationResult.INVALID_ADDRESS;
GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupBanTransactionData.getGroupId());
GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupId);
// Check group exists
if (groupData == null)
@ -86,22 +67,17 @@ public class GroupBanTransaction extends Transaction {
Account admin = getAdmin();
// Can't ban if not an admin
if (!this.repository.getGroupRepository().adminExists(groupBanTransactionData.getGroupId(), admin.getAddress()))
if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress()))
return ValidationResult.NOT_GROUP_ADMIN;
Account offender = getOffender();
// Can't ban another admin unless the group owner
if (!admin.getAddress().equals(groupData.getOwner())
&& this.repository.getGroupRepository().adminExists(groupBanTransactionData.getGroupId(), offender.getAddress()))
if (!admin.getAddress().equals(groupData.getOwner()) && this.repository.getGroupRepository().adminExists(groupId, offender.getAddress()))
return ValidationResult.INVALID_GROUP_OWNER;
// Check fee is positive
if (groupBanTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check admin has enough funds
if (admin.getConfirmedBalance(Asset.QORT).compareTo(groupBanTransactionData.getFee()) < 0)
if (admin.getConfirmedBalance(Asset.QORT) < this.groupBanTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -110,21 +86,21 @@ public class GroupBanTransaction extends Transaction {
@Override
public void process() throws DataException {
// Update Group Membership
Group group = new Group(this.repository, groupBanTransactionData.getGroupId());
group.ban(groupBanTransactionData);
Group group = new Group(this.repository, this.groupBanTransactionData.getGroupId());
group.ban(this.groupBanTransactionData);
// Save this transaction with updated member/admin references to transactions that can help restore state
this.repository.getTransactionRepository().save(groupBanTransactionData);
this.repository.getTransactionRepository().save(this.groupBanTransactionData);
}
@Override
public void orphan() throws DataException {
// Revert group membership
Group group = new Group(this.repository, groupBanTransactionData.getGroupId());
group.unban(groupBanTransactionData);
Group group = new Group(this.repository, this.groupBanTransactionData.getGroupId());
group.unban(this.groupBanTransactionData);
// Save this transaction with removed member/admin references
this.repository.getTransactionRepository().save(groupBanTransactionData);
this.repository.getTransactionRepository().save(this.groupBanTransactionData);
}
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.data.transaction.GroupInviteTransactionData;
@ -17,7 +15,9 @@ import org.qortal.repository.Repository;
public class GroupInviteTransaction extends Transaction {
// Properties
private GroupInviteTransactionData groupInviteTransactionData;
private Account inviteeAccount = null;
// Constructors
@ -30,56 +30,35 @@ public class GroupInviteTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.emptyList();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getAdmin().getAddress()))
return true;
if (address.equals(this.getInvitee().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.getAdmin().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.groupInviteTransactionData.getInvitee());
}
// Navigation
public Account getAdmin() throws DataException {
return new PublicKeyAccount(this.repository, this.groupInviteTransactionData.getAdminPublicKey());
public Account getAdmin() {
return this.getCreator();
}
public Account getInvitee() throws DataException {
return new Account(this.repository, this.groupInviteTransactionData.getInvitee());
public Account getInvitee() {
if (this.inviteeAccount == null)
this.inviteeAccount = new Account(this.repository, this.groupInviteTransactionData.getInvitee());
return this.inviteeAccount;
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
int groupId = groupInviteTransactionData.getGroupId();
int groupId = this.groupInviteTransactionData.getGroupId();
// Check time to live zero (infinite) or positive
if (groupInviteTransactionData.getTimeToLive() < 0)
if (this.groupInviteTransactionData.getTimeToLive() < 0)
return ValidationResult.INVALID_LIFETIME;
// Check member address is valid
if (!Crypto.isValidAddress(groupInviteTransactionData.getInvitee()))
if (!Crypto.isValidAddress(this.groupInviteTransactionData.getInvitee()))
return ValidationResult.INVALID_ADDRESS;
// Check group exists
@ -102,12 +81,8 @@ public class GroupInviteTransaction extends Transaction {
if (this.repository.getGroupRepository().banExists(groupId, invitee.getAddress()))
return ValidationResult.BANNED_FROM_GROUP;
// Check fee is positive
if (groupInviteTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (admin.getConfirmedBalance(Asset.QORT).compareTo(groupInviteTransactionData.getFee()) < 0)
if (admin.getConfirmedBalance(Asset.QORT) < this.groupInviteTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -116,21 +91,21 @@ public class GroupInviteTransaction extends Transaction {
@Override
public void process() throws DataException {
// Update Group Membership
Group group = new Group(this.repository, groupInviteTransactionData.getGroupId());
group.invite(groupInviteTransactionData);
Group group = new Group(this.repository, this.groupInviteTransactionData.getGroupId());
group.invite(this.groupInviteTransactionData);
// Save this transaction with updated member/admin references to transactions that can help restore state
this.repository.getTransactionRepository().save(groupInviteTransactionData);
this.repository.getTransactionRepository().save(this.groupInviteTransactionData);
}
@Override
public void orphan() throws DataException {
// Revert group membership
Group group = new Group(this.repository, groupInviteTransactionData.getGroupId());
group.uninvite(groupInviteTransactionData);
Group group = new Group(this.repository, this.groupInviteTransactionData.getGroupId());
group.uninvite(this.groupInviteTransactionData);
// Save this transaction with removed member/admin references
this.repository.getTransactionRepository().save(groupInviteTransactionData);
this.repository.getTransactionRepository().save(this.groupInviteTransactionData);
}
}

View File

@ -1,6 +1,5 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
@ -32,32 +31,8 @@ public class GroupKickTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.emptyList();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getAdmin().getAddress()))
return true;
if (address.equals(this.getMember().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.getAdmin().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.groupKickTransactionData.getMember());
}
// Navigation
@ -74,12 +49,13 @@ public class GroupKickTransaction extends Transaction {
@Override
public ValidationResult isValid() throws DataException {
int groupId = this.groupKickTransactionData.getGroupId();
// Check member address is valid
if (!Crypto.isValidAddress(groupKickTransactionData.getMember()))
if (!Crypto.isValidAddress(this.groupKickTransactionData.getMember()))
return ValidationResult.INVALID_ADDRESS;
GroupRepository groupRepository = this.repository.getGroupRepository();
int groupId = groupKickTransactionData.getGroupId();
GroupData groupData = groupRepository.fromGroupId(groupId);
// Check group exists
@ -102,12 +78,8 @@ public class GroupKickTransaction extends Transaction {
if (!admin.getAddress().equals(groupData.getOwner()) && groupRepository.adminExists(groupId, member.getAddress()))
return ValidationResult.INVALID_GROUP_OWNER;
// Check fee is positive
if (groupKickTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (admin.getConfirmedBalance(Asset.QORT).compareTo(groupKickTransactionData.getFee()) < 0)
if (admin.getConfirmedBalance(Asset.QORT) < this.groupKickTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -116,21 +88,21 @@ public class GroupKickTransaction extends Transaction {
@Override
public void process() throws DataException {
// Update Group Membership
Group group = new Group(this.repository, groupKickTransactionData.getGroupId());
group.kick(groupKickTransactionData);
Group group = new Group(this.repository, this.groupKickTransactionData.getGroupId());
group.kick(this.groupKickTransactionData);
// Save this transaction with updated member/admin references to transactions that can help restore state
this.repository.getTransactionRepository().save(groupKickTransactionData);
this.repository.getTransactionRepository().save(this.groupKickTransactionData);
}
@Override
public void orphan() throws DataException {
// Revert group membership
Group group = new Group(this.repository, groupKickTransactionData.getGroupId());
group.unkick(groupKickTransactionData);
Group group = new Group(this.repository, this.groupKickTransactionData.getGroupId());
group.unkick(this.groupKickTransactionData);
// Save this transaction with removed member/admin references
this.repository.getTransactionRepository().save(groupKickTransactionData);
this.repository.getTransactionRepository().save(this.groupKickTransactionData);
}
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.data.transaction.IssueAssetTransactionData;
@ -18,7 +16,9 @@ import com.google.common.base.Utf8;
public class IssueAssetTransaction extends Transaction {
// Properties
private IssueAssetTransactionData issueAssetTransactionData;
private Account ownerAccount = null;
// Constructors
@ -31,82 +31,59 @@ public class IssueAssetTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(getOwner());
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getIssuer().getAddress()))
return true;
if (address.equals(this.getOwner().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.getIssuer().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
// NOTE: we're only interested in QORT amounts, and genesis account issued QORT so no need to check owner
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.issueAssetTransactionData.getOwner());
}
// Navigation
public Account getIssuer() throws DataException {
return new PublicKeyAccount(this.repository, this.issueAssetTransactionData.getIssuerPublicKey());
public Account getIssuer() {
return this.getCreator();
}
public Account getOwner() throws DataException {
return new Account(this.repository, this.issueAssetTransactionData.getOwner());
public Account getOwner() {
if (this.ownerAccount == null)
this.ownerAccount = new Account(this.repository, this.issueAssetTransactionData.getOwner());
return this.ownerAccount;
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
// Check owner address is valid
if (!Crypto.isValidAddress(this.issueAssetTransactionData.getOwner()))
return ValidationResult.INVALID_ADDRESS;
// Check name size bounds
int assetNameLength = Utf8.encodedLength(this.issueAssetTransactionData.getAssetName());
if (assetNameLength < 1 || assetNameLength > Asset.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH;
// Check description size bounds
int assetDescriptionlength = Utf8.encodedLength(this.issueAssetTransactionData.getDescription());
if (assetDescriptionlength < 1 || assetDescriptionlength > Asset.MAX_DESCRIPTION_SIZE)
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
// Check data field
String data = this.issueAssetTransactionData.getData();
int dataLength = Utf8.encodedLength(data);
if (data == null || dataLength < 1 || dataLength > Asset.MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH;
// Check owner address is valid
if (!Crypto.isValidAddress(issueAssetTransactionData.getOwner()))
return ValidationResult.INVALID_ADDRESS;
// Check name size bounds
int assetNameLength = Utf8.encodedLength(issueAssetTransactionData.getAssetName());
if (assetNameLength < 1 || assetNameLength > Asset.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH;
// Check description size bounds
int assetDescriptionlength = Utf8.encodedLength(issueAssetTransactionData.getDescription());
if (assetDescriptionlength < 1 || assetDescriptionlength > Asset.MAX_DESCRIPTION_SIZE)
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
// Check quantity
if (issueAssetTransactionData.getQuantity() < 1 || issueAssetTransactionData.getQuantity() > Asset.MAX_QUANTITY)
if (this.issueAssetTransactionData.getQuantity() < 1 || this.issueAssetTransactionData.getQuantity() > Asset.MAX_QUANTITY)
return ValidationResult.INVALID_QUANTITY;
// Check fee is positive
if (issueAssetTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check quantity versus indivisibility
if (!this.issueAssetTransactionData.getIsDivisible() && this.issueAssetTransactionData.getQuantity() % Asset.MULTIPLIER != 0)
return ValidationResult.INVALID_QUANTITY;
Account issuer = getIssuer();
// Check issuer has enough funds
if (issuer.getConfirmedBalance(Asset.QORT).compareTo(issueAssetTransactionData.getFee()) < 0)
if (issuer.getConfirmedBalance(Asset.QORT) < this.issueAssetTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -115,7 +92,7 @@ public class IssueAssetTransaction extends Transaction {
@Override
public ValidationResult isProcessable() throws DataException {
// Check the asset name isn't already taken.
if (this.repository.getAssetRepository().assetExists(issueAssetTransactionData.getAssetName()))
if (this.repository.getAssetRepository().assetExists(this.issueAssetTransactionData.getAssetName()))
return ValidationResult.ASSET_ALREADY_EXISTS;
return ValidationResult.OK;
@ -124,35 +101,35 @@ public class IssueAssetTransaction extends Transaction {
@Override
public void process() throws DataException {
// Issue asset
Asset asset = new Asset(this.repository, issueAssetTransactionData);
Asset asset = new Asset(this.repository, this.issueAssetTransactionData);
asset.issue();
// Add asset to owner
Account owner = getOwner();
owner.setConfirmedBalance(asset.getAssetData().getAssetId(), BigDecimal.valueOf(issueAssetTransactionData.getQuantity()).setScale(8));
owner.setConfirmedBalance(asset.getAssetData().getAssetId(), this.issueAssetTransactionData.getQuantity());
// Note newly assigned asset ID in our transaction record
issueAssetTransactionData.setAssetId(asset.getAssetData().getAssetId());
this.issueAssetTransactionData.setAssetId(asset.getAssetData().getAssetId());
// Save this transaction with newly assigned assetId
this.repository.getTransactionRepository().save(issueAssetTransactionData);
this.repository.getTransactionRepository().save(this.issueAssetTransactionData);
}
@Override
public void orphan() throws DataException {
// Remove asset from owner
Account owner = getOwner();
owner.deleteBalance(issueAssetTransactionData.getAssetId());
owner.deleteBalance(this.issueAssetTransactionData.getAssetId());
// Deissue asset
Asset asset = new Asset(this.repository, issueAssetTransactionData.getAssetId());
Asset asset = new Asset(this.repository, this.issueAssetTransactionData.getAssetId());
asset.deissue();
// Remove assigned asset ID from transaction info
issueAssetTransactionData.setAssetId(null);
this.issueAssetTransactionData.setAssetId(null);
// Save this transaction, with removed assetId
this.repository.getTransactionRepository().save(issueAssetTransactionData);
this.repository.getTransactionRepository().save(this.issueAssetTransactionData);
}
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.transaction.JoinGroupTransactionData;
import org.qortal.data.transaction.TransactionData;
@ -29,42 +27,21 @@ public class JoinGroupTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getJoiner().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.getJoiner().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
}
// Navigation
public Account getJoiner() throws DataException {
return new PublicKeyAccount(this.repository, this.joinGroupTransactionData.getJoinerPublicKey());
public Account getJoiner() {
return this.getCreator();
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
int groupId = joinGroupTransactionData.getGroupId();
int groupId = this.joinGroupTransactionData.getGroupId();
// Check group exists
if (!this.repository.getGroupRepository().groupExists(groupId))
@ -83,11 +60,8 @@ public class JoinGroupTransaction extends Transaction {
if (this.repository.getGroupRepository().joinRequestExists(groupId, joiner.getAddress()))
return ValidationResult.JOIN_REQUEST_EXISTS;
// Check fee is positive
if (joinGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (joiner.getConfirmedBalance(Asset.QORT).compareTo(joinGroupTransactionData.getFee()) < 0)
// Check joiner has enough funds
if (joiner.getConfirmedBalance(Asset.QORT) < this.joinGroupTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -96,21 +70,21 @@ public class JoinGroupTransaction extends Transaction {
@Override
public void process() throws DataException {
// Update Group Membership
Group group = new Group(this.repository, joinGroupTransactionData.getGroupId());
group.join(joinGroupTransactionData);
Group group = new Group(this.repository, this.joinGroupTransactionData.getGroupId());
group.join(this.joinGroupTransactionData);
// Save this transaction with cached references to transactions that can help restore state
this.repository.getTransactionRepository().save(joinGroupTransactionData);
this.repository.getTransactionRepository().save(this.joinGroupTransactionData);
}
@Override
public void orphan() throws DataException {
// Revert group membership
Group group = new Group(this.repository, joinGroupTransactionData.getGroupId());
group.unjoin(joinGroupTransactionData);
Group group = new Group(this.repository, this.joinGroupTransactionData.getGroupId());
group.unjoin(this.joinGroupTransactionData);
// Save this transaction with removed references
this.repository.getTransactionRepository().save(joinGroupTransactionData);
this.repository.getTransactionRepository().save(this.joinGroupTransactionData);
}
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.group.GroupData;
import org.qortal.data.transaction.LeaveGroupTransactionData;
@ -30,42 +28,23 @@ public class LeaveGroupTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getLeaver().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.getLeaver().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
}
// Navigation
public Account getLeaver() throws DataException {
return new PublicKeyAccount(this.repository, this.leaveGroupTransactionData.getLeaverPublicKey());
public Account getLeaver() {
return this.getCreator();
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
GroupData groupData = this.repository.getGroupRepository().fromGroupId(leaveGroupTransactionData.getGroupId());
int groupId = this.leaveGroupTransactionData.getGroupId();
GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupId);
// Check group exists
if (groupData == null)
@ -78,15 +57,11 @@ public class LeaveGroupTransaction extends Transaction {
return ValidationResult.GROUP_OWNER_CANNOT_LEAVE;
// Check leaver is actually a member of group
if (!this.repository.getGroupRepository().memberExists(leaveGroupTransactionData.getGroupId(), leaver.getAddress()))
if (!this.repository.getGroupRepository().memberExists(groupId, leaver.getAddress()))
return ValidationResult.NOT_GROUP_MEMBER;
// Check fee is positive
if (leaveGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (leaver.getConfirmedBalance(Asset.QORT).compareTo(leaveGroupTransactionData.getFee()) < 0)
// Check leaver has enough funds
if (leaver.getConfirmedBalance(Asset.QORT) < this.leaveGroupTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -95,21 +70,21 @@ public class LeaveGroupTransaction extends Transaction {
@Override
public void process() throws DataException {
// Update Group Membership
Group group = new Group(this.repository, leaveGroupTransactionData.getGroupId());
group.leave(leaveGroupTransactionData);
Group group = new Group(this.repository, this.leaveGroupTransactionData.getGroupId());
group.leave(this.leaveGroupTransactionData);
// Save this transaction with updated member/admin references to transactions that can help restore state
this.repository.getTransactionRepository().save(leaveGroupTransactionData);
this.repository.getTransactionRepository().save(this.leaveGroupTransactionData);
}
@Override
public void orphan() throws DataException {
// Revert group membership
Group group = new Group(this.repository, leaveGroupTransactionData.getGroupId());
group.unleave(leaveGroupTransactionData);
Group group = new Group(this.repository, this.leaveGroupTransactionData.getGroupId());
group.unleave(this.leaveGroupTransactionData);
// Save this transaction with removed member/admin references
this.repository.getTransactionRepository().save(leaveGroupTransactionData);
this.repository.getTransactionRepository().save(this.leaveGroupTransactionData);
}
}

View File

@ -1,12 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.PaymentData;
import org.qortal.data.transaction.MessageTransactionData;
import org.qortal.data.transaction.TransactionData;
@ -17,10 +14,13 @@ import org.qortal.repository.Repository;
public class MessageTransaction extends Transaction {
// Properties
private MessageTransactionData messageTransactionData;
private PaymentData paymentData = null;
// Other useful constants
public static final int MAX_DATA_SIZE = 4000;
private static final boolean isZeroAmountValid = true;
// Constructors
@ -33,109 +33,71 @@ public class MessageTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(new Account(this.repository, messageTransactionData.getRecipient()));
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getSender().getAddress()))
return true;
if (address.equals(messageTransactionData.getRecipient()))
return true;
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
String senderAddress = this.getSender().getAddress();
if (address.equals(senderAddress))
amount = amount.subtract(this.transactionData.getFee());
// We're only interested in QORT
if (messageTransactionData.getAssetId() == Asset.QORT) {
if (address.equals(messageTransactionData.getRecipient()))
amount = amount.add(messageTransactionData.getAmount());
else if (address.equals(senderAddress))
amount = amount.subtract(messageTransactionData.getAmount());
}
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.messageTransactionData.getRecipient());
}
// Navigation
public Account getSender() throws DataException {
return new PublicKeyAccount(this.repository, this.messageTransactionData.getSenderPublicKey());
public Account getSender() {
return this.getCreator();
}
public Account getRecipient() throws DataException {
public Account getRecipient() {
return new Account(this.repository, this.messageTransactionData.getRecipient());
}
// Processing
private PaymentData getPaymentData() {
return new PaymentData(messageTransactionData.getRecipient(), messageTransactionData.getAssetId(), messageTransactionData.getAmount());
if (this.paymentData == null)
this.paymentData = new PaymentData(this.messageTransactionData.getRecipient(), this.messageTransactionData.getAssetId(), this.messageTransactionData.getAmount());
return this.paymentData;
}
@Override
public ValidationResult isValid() throws DataException {
// Check data length
if (messageTransactionData.getData().length < 1 || messageTransactionData.getData().length > MAX_DATA_SIZE)
if (this.messageTransactionData.getData().length < 1 || this.messageTransactionData.getData().length > MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH;
// Zero-amount payments (i.e. message-only) only valid for versions later than 1
boolean isZeroAmountValid = messageTransactionData.getVersion() > 1;
// Wrap and delegate final payment checks to Payment class
return new Payment(this.repository).isValid(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
return new Payment(this.repository).isValid(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getFee(),
isZeroAmountValid);
}
@Override
public ValidationResult isProcessable() throws DataException {
// Zero-amount payments (i.e. message-only) only valid for versions later than 1
boolean isZeroAmountValid = messageTransactionData.getVersion() > 1;
// Wrap and delegate final processable checks to Payment class
return new Payment(this.repository).isProcessable(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
return new Payment(this.repository).isProcessable(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getFee(),
isZeroAmountValid);
}
@Override
public void process() throws DataException {
// Wrap and delegate payment processing to Payment class.
new Payment(this.repository).process(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
messageTransactionData.getSignature());
new Payment(this.repository).process(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getSignature());
}
@Override
public void processReferencesAndFees() throws DataException {
// Wrap and delegate references processing to Payment class. Only update recipient's last reference if transferring QORT.
new Payment(this.repository).processReferencesAndFees(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
messageTransactionData.getSignature(), false);
new Payment(this.repository).processReferencesAndFees(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getFee(),
this.messageTransactionData.getSignature(), false);
}
@Override
public void orphan() throws DataException {
// Wrap and delegate payment processing to Payment class.
new Payment(this.repository).orphan(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
messageTransactionData.getSignature(), messageTransactionData.getReference());
new Payment(this.repository).orphan(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getSignature(), this.messageTransactionData.getReference());
}
@Override
public void orphanReferencesAndFees() throws DataException {
// Wrap and delegate references processing to Payment class. Only revert recipient's last reference if transferring QORT.
new Payment(this.repository).orphanReferencesAndFees(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
messageTransactionData.getSignature(), messageTransactionData.getReference(), false);
new Payment(this.repository).orphanReferencesAndFees(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getFee(),
this.messageTransactionData.getSignature(), this.messageTransactionData.getReference(), false);
}
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.PaymentData;
import org.qortal.data.transaction.MultiPaymentTransactionData;
@ -33,109 +31,67 @@ public class MultiPaymentTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
List<Account> recipients = new ArrayList<>();
for (PaymentData paymentData : multiPaymentTransactionData.getPayments())
recipients.add(new Account(this.repository, paymentData.getRecipient()));
return recipients;
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getSender().getAddress()))
return true;
for (PaymentData paymentData : multiPaymentTransactionData.getPayments())
if (address.equals(paymentData.getRecipient()))
return true;
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
String senderAddress = this.getSender().getAddress();
if (address.equals(senderAddress))
amount = amount.subtract(this.transactionData.getFee());
// We're only interested in QORT
for (PaymentData paymentData : multiPaymentTransactionData.getPayments())
if (paymentData.getAssetId() == Asset.QORT) {
if (address.equals(paymentData.getRecipient()))
amount = amount.add(paymentData.getAmount());
else if (address.equals(senderAddress))
amount = amount.subtract(paymentData.getAmount());
}
return amount;
public List<String> getRecipientAddresses() throws DataException {
return this.multiPaymentTransactionData.getPayments().stream().map(PaymentData::getRecipient).collect(Collectors.toList());
}
// Navigation
public Account getSender() throws DataException {
return new PublicKeyAccount(this.repository, this.multiPaymentTransactionData.getSenderPublicKey());
public Account getSender() {
return this.getCreator();
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
List<PaymentData> payments = multiPaymentTransactionData.getPayments();
List<PaymentData> payments = this.multiPaymentTransactionData.getPayments();
// Check number of payments
if (payments.isEmpty() || payments.size() > MAX_PAYMENTS_COUNT)
return ValidationResult.INVALID_PAYMENTS_COUNT;
// Check reference is correct
Account sender = getSender();
// Check sender has enough funds for fee
if (sender.getConfirmedBalance(Asset.QORT).compareTo(multiPaymentTransactionData.getFee()) < 0)
if (sender.getConfirmedBalance(Asset.QORT) > this.multiPaymentTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return new Payment(this.repository).isValid(multiPaymentTransactionData.getSenderPublicKey(), payments, multiPaymentTransactionData.getFee());
return new Payment(this.repository).isValid(this.multiPaymentTransactionData.getSenderPublicKey(), payments, this.multiPaymentTransactionData.getFee());
}
@Override
public ValidationResult isProcessable() throws DataException {
List<PaymentData> payments = multiPaymentTransactionData.getPayments();
List<PaymentData> payments = this.multiPaymentTransactionData.getPayments();
return new Payment(this.repository).isProcessable(multiPaymentTransactionData.getSenderPublicKey(), payments, multiPaymentTransactionData.getFee());
return new Payment(this.repository).isProcessable(this.multiPaymentTransactionData.getSenderPublicKey(), payments, this.multiPaymentTransactionData.getFee());
}
@Override
public void process() throws DataException {
// Wrap and delegate payment processing to Payment class.
new Payment(this.repository).process(multiPaymentTransactionData.getSenderPublicKey(), multiPaymentTransactionData.getPayments(),
multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature());
new Payment(this.repository).process(this.multiPaymentTransactionData.getSenderPublicKey(), this.multiPaymentTransactionData.getPayments(), this.multiPaymentTransactionData.getSignature());
}
@Override
public void processReferencesAndFees() throws DataException {
// Wrap and delegate reference processing to Payment class. Always update recipients' last references regardless of asset.
new Payment(this.repository).processReferencesAndFees(multiPaymentTransactionData.getSenderPublicKey(), multiPaymentTransactionData.getPayments(),
multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature(), true);
new Payment(this.repository).processReferencesAndFees(this.multiPaymentTransactionData.getSenderPublicKey(), this.multiPaymentTransactionData.getPayments(),
this.multiPaymentTransactionData.getFee(), this.multiPaymentTransactionData.getSignature(), true);
}
@Override
public void orphan() throws DataException {
// Wrap and delegate payment processing to Payment class. Always revert recipients' last references regardless of asset.
new Payment(this.repository).orphan(multiPaymentTransactionData.getSenderPublicKey(), multiPaymentTransactionData.getPayments(),
multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature(), multiPaymentTransactionData.getReference());
new Payment(this.repository).orphan(this.multiPaymentTransactionData.getSenderPublicKey(), this.multiPaymentTransactionData.getPayments(),
this.multiPaymentTransactionData.getSignature(), this.multiPaymentTransactionData.getReference());
}
@Override
public void orphanReferencesAndFees() throws DataException {
// Wrap and delegate reference processing to Payment class. Always revert recipients' last references regardless of asset.
new Payment(this.repository).orphanReferencesAndFees(multiPaymentTransactionData.getSenderPublicKey(), multiPaymentTransactionData.getPayments(),
multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature(), multiPaymentTransactionData.getReference(), true);
new Payment(this.repository).orphanReferencesAndFees(this.multiPaymentTransactionData.getSenderPublicKey(), this.multiPaymentTransactionData.getPayments(),
this.multiPaymentTransactionData.getFee(), this.multiPaymentTransactionData.getSignature(), this.multiPaymentTransactionData.getReference(), true);
}
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.PaymentData;
import org.qortal.data.transaction.PaymentTransactionData;
@ -17,7 +15,9 @@ import org.qortal.repository.Repository;
public class PaymentTransaction extends Transaction {
// Properties
private PaymentTransactionData paymentTransactionData;
private PaymentData paymentData = null;
// Constructors
@ -30,88 +30,62 @@ public class PaymentTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(new Account(this.repository, paymentTransactionData.getRecipient()));
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getSender().getAddress()))
return true;
if (address.equals(paymentTransactionData.getRecipient()))
return true;
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
String senderAddress = this.getSender().getAddress();
if (address.equals(senderAddress))
amount = amount.subtract(this.transactionData.getFee()).subtract(paymentTransactionData.getAmount());
if (address.equals(paymentTransactionData.getRecipient()))
amount = amount.add(paymentTransactionData.getAmount());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.paymentTransactionData.getRecipient());
}
// Navigation
public Account getSender() throws DataException {
return new PublicKeyAccount(this.repository, this.paymentTransactionData.getSenderPublicKey());
public Account getSender() {
return this.getCreator();
}
// Processing
private PaymentData getPaymentData() {
return new PaymentData(paymentTransactionData.getRecipient(), Asset.QORT, paymentTransactionData.getAmount());
if (this.paymentData == null)
this.paymentData = new PaymentData(this.paymentTransactionData.getRecipient(), Asset.QORT, this.paymentTransactionData.getAmount());
return this.paymentData;
}
@Override
public ValidationResult isValid() throws DataException {
// Wrap and delegate final payment checks to Payment class
return new Payment(this.repository).isValid(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee());
return new Payment(this.repository).isValid(this.paymentTransactionData.getSenderPublicKey(), getPaymentData(), this.paymentTransactionData.getFee());
}
@Override
public ValidationResult isProcessable() throws DataException {
// Wrap and delegate final processable checks to Payment class
return new Payment(this.repository).isProcessable(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee());
return new Payment(this.repository).isProcessable(this.paymentTransactionData.getSenderPublicKey(), getPaymentData(), this.paymentTransactionData.getFee());
}
@Override
public void process() throws DataException {
// Wrap and delegate payment processing to Payment class.
new Payment(this.repository).process(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee(),
paymentTransactionData.getSignature());
new Payment(this.repository).process(this.paymentTransactionData.getSenderPublicKey(), getPaymentData(), this.paymentTransactionData.getSignature());
}
@Override
public void processReferencesAndFees() throws DataException {
// Wrap and delegate references processing to Payment class. Only update recipient's last reference if transferring QORT.
new Payment(this.repository).processReferencesAndFees(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee(),
paymentTransactionData.getSignature(), false);
new Payment(this.repository).processReferencesAndFees(this.paymentTransactionData.getSenderPublicKey(), getPaymentData(), this.paymentTransactionData.getFee(),
this.paymentTransactionData.getSignature(), false);
}
@Override
public void orphan() throws DataException {
// Wrap and delegate payment processing to Payment class. Only revert recipient's last reference if transferring QORT.
new Payment(this.repository).orphan(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee(),
paymentTransactionData.getSignature(), paymentTransactionData.getReference());
new Payment(this.repository).orphan(this.paymentTransactionData.getSenderPublicKey(), getPaymentData(),
this.paymentTransactionData.getSignature(), this.paymentTransactionData.getReference());
}
@Override
public void orphanReferencesAndFees() throws DataException {
// Wrap and delegate payment processing to Payment class. Only revert recipient's last reference if transferring QORT.
new Payment(this.repository).orphanReferencesAndFees(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee(),
paymentTransactionData.getSignature(), paymentTransactionData.getReference(), false);
new Payment(this.repository).orphanReferencesAndFees(this.paymentTransactionData.getSenderPublicKey(), getPaymentData(), this.paymentTransactionData.getFee(),
this.paymentTransactionData.getSignature(), this.paymentTransactionData.getReference(), false);
}
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.block.BlockChain;
import org.qortal.crypto.Crypto;
@ -33,42 +31,14 @@ public class RegisterNameTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(getOwner());
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getRegistrant().getAddress()))
return true;
if (address.equals(this.getOwner().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.getRegistrant().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.registerNameTransactionData.getOwner());
}
// Navigation
public Account getRegistrant() throws DataException {
return new PublicKeyAccount(this.repository, this.registerNameTransactionData.getRegistrantPublicKey());
}
public Account getOwner() throws DataException {
return new Account(this.repository, this.registerNameTransactionData.getOwner());
public Account getRegistrant() {
return this.getCreator();
}
// Processing
@ -78,29 +48,25 @@ public class RegisterNameTransaction extends Transaction {
Account registrant = getRegistrant();
// Check owner address is valid
if (!Crypto.isValidAddress(registerNameTransactionData.getOwner()))
if (!Crypto.isValidAddress(this.registerNameTransactionData.getOwner()))
return ValidationResult.INVALID_ADDRESS;
// Check name size bounds
int nameLength = Utf8.encodedLength(registerNameTransactionData.getName());
int nameLength = Utf8.encodedLength(this.registerNameTransactionData.getName());
if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH;
// Check data size bounds
int dataLength = Utf8.encodedLength(registerNameTransactionData.getData());
int dataLength = Utf8.encodedLength(this.registerNameTransactionData.getData());
if (dataLength < 1 || dataLength > Name.MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH;
// Check name is lowercase
if (!registerNameTransactionData.getName().equals(registerNameTransactionData.getName().toLowerCase()))
if (!this.registerNameTransactionData.getName().equals(this.registerNameTransactionData.getName().toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE;
// Check fee is positive
if (registerNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check issuer has enough funds
if (registrant.getConfirmedBalance(Asset.QORT).compareTo(registerNameTransactionData.getFee()) < 0)
// Check registrant has enough funds
if (registrant.getConfirmedBalance(Asset.QORT) < this.registerNameTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -109,7 +75,7 @@ public class RegisterNameTransaction extends Transaction {
@Override
public ValidationResult isProcessable() throws DataException {
// Check the name isn't already taken
if (this.repository.getNameRepository().nameExists(registerNameTransactionData.getName()))
if (this.repository.getNameRepository().nameExists(this.registerNameTransactionData.getName()))
return ValidationResult.NAME_ALREADY_REGISTERED;
Account registrant = getRegistrant();
@ -124,14 +90,14 @@ public class RegisterNameTransaction extends Transaction {
@Override
public void process() throws DataException {
// Register Name
Name name = new Name(this.repository, registerNameTransactionData);
Name name = new Name(this.repository, this.registerNameTransactionData);
name.register();
}
@Override
public void orphan() throws DataException {
// Unregister name
Name name = new Name(this.repository, registerNameTransactionData.getName());
Name name = new Name(this.repository, this.registerNameTransactionData.getName());
name.unregister();
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.data.group.GroupData;
@ -18,7 +16,9 @@ import org.qortal.repository.Repository;
public class RemoveGroupAdminTransaction extends Transaction {
// Properties
private RemoveGroupAdminTransactionData removeGroupAdminTransactionData;
private Account adminAccount = null;
// Constructors
@ -31,53 +31,34 @@ public class RemoveGroupAdminTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.emptyList();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getOwner().getAddress()))
return true;
if (address.equals(this.getAdmin().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;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.removeGroupAdminTransactionData.getAdmin());
}
// Navigation
public Account getOwner() throws DataException {
return new PublicKeyAccount(this.repository, this.removeGroupAdminTransactionData.getOwnerPublicKey());
public Account getOwner() {
return this.getCreator();
}
public Account getAdmin() throws DataException {
return new Account(this.repository, this.removeGroupAdminTransactionData.getAdmin());
public Account getAdmin() {
if (this.adminAccount == null)
this.adminAccount = new Account(this.repository, this.removeGroupAdminTransactionData.getAdmin());
return this.adminAccount;
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
int groupId = this.removeGroupAdminTransactionData.getGroupId();
// Check admin address is valid
if (!Crypto.isValidAddress(removeGroupAdminTransactionData.getAdmin()))
if (!Crypto.isValidAddress(this.removeGroupAdminTransactionData.getAdmin()))
return ValidationResult.INVALID_ADDRESS;
GroupData groupData = this.repository.getGroupRepository().fromGroupId(removeGroupAdminTransactionData.getGroupId());
GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupId);
// Check group exists
if (groupData == null)
@ -92,15 +73,11 @@ public class RemoveGroupAdminTransaction extends Transaction {
Account admin = getAdmin();
// Check member is an admin
if (!this.repository.getGroupRepository().adminExists(removeGroupAdminTransactionData.getGroupId(), admin.getAddress()))
if (!this.repository.getGroupRepository().adminExists(groupId, admin.getAddress()))
return ValidationResult.NOT_GROUP_ADMIN;
// Check fee is positive
if (removeGroupAdminTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (owner.getConfirmedBalance(Asset.QORT).compareTo(removeGroupAdminTransactionData.getFee()) < 0)
if (owner.getConfirmedBalance(Asset.QORT) < this.removeGroupAdminTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -109,21 +86,21 @@ public class RemoveGroupAdminTransaction extends Transaction {
@Override
public void process() throws DataException {
// Update Group adminship
Group group = new Group(this.repository, removeGroupAdminTransactionData.getGroupId());
group.demoteFromAdmin(removeGroupAdminTransactionData);
Group group = new Group(this.repository, this.removeGroupAdminTransactionData.getGroupId());
group.demoteFromAdmin(this.removeGroupAdminTransactionData);
// Save this transaction with cached references to transactions that can help restore state
this.repository.getTransactionRepository().save(removeGroupAdminTransactionData);
this.repository.getTransactionRepository().save(this.removeGroupAdminTransactionData);
}
@Override
public void orphan() throws DataException {
// Revert group adminship
Group group = new Group(this.repository, removeGroupAdminTransactionData.getGroupId());
group.undemoteFromAdmin(removeGroupAdminTransactionData);
Group group = new Group(this.repository, this.removeGroupAdminTransactionData.getGroupId());
group.undemoteFromAdmin(this.removeGroupAdminTransactionData);
// Save this transaction with removed group references
this.repository.getTransactionRepository().save(removeGroupAdminTransactionData);
this.repository.getTransactionRepository().save(this.removeGroupAdminTransactionData);
}
}

View File

@ -1,6 +1,5 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -19,8 +18,13 @@ import org.qortal.transform.Transformer;
public class RewardShareTransaction extends Transaction {
public static final int MAX_SHARE = 100 * 100; // unscaled
// Properties
private RewardShareTransactionData rewardShareTransactionData;
private boolean haveCheckedForExistingRewardShare = false;
private RewardShareData existingRewardShareData = null;
// Constructors
@ -33,42 +37,23 @@ public class RewardShareTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.emptyList();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getMintingAccount().getAddress()))
return true;
if (address.equals(this.getRecipient().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.getMintingAccount().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.rewardShareTransactionData.getRecipient());
}
private RewardShareData getExistingRewardShare() throws DataException {
// Look up any existing reward-share (using transaction's reward-share public key)
RewardShareData existingRewardShareData = this.repository.getAccountRepository().getRewardShare(this.rewardShareTransactionData.getRewardSharePublicKey());
if (existingRewardShareData == null)
// No luck, try looking up existing reward-share using minting & recipient account info
existingRewardShareData = this.repository.getAccountRepository().getRewardShare(this.rewardShareTransactionData.getMinterPublicKey(), this.rewardShareTransactionData.getRecipient());
if (this.haveCheckedForExistingRewardShare == false) {
this.haveCheckedForExistingRewardShare = true;
return existingRewardShareData;
// Look up any existing reward-share (using transaction's reward-share public key)
this.existingRewardShareData = this.repository.getAccountRepository().getRewardShare(this.rewardShareTransactionData.getRewardSharePublicKey());
if (this.existingRewardShareData == null)
// No luck, try looking up existing reward-share using minting & recipient account info
this.existingRewardShareData = this.repository.getAccountRepository().getRewardShare(this.rewardShareTransactionData.getMinterPublicKey(), this.rewardShareTransactionData.getRecipient());
}
return this.existingRewardShareData;
}
private boolean doesRewardShareMatch(RewardShareData rewardShareData) {
@ -80,7 +65,7 @@ public class RewardShareTransaction extends Transaction {
// Navigation
public PublicKeyAccount getMintingAccount() {
return new PublicKeyAccount(this.repository, this.rewardShareTransactionData.getMinterPublicKey());
return this.getCreator();
}
public Account getRecipient() {
@ -89,12 +74,11 @@ public class RewardShareTransaction extends Transaction {
// Processing
private static final BigDecimal MAX_SHARE = BigDecimal.valueOf(100).setScale(2);
@Override
public ValidationResult isFeeValid() throws DataException {
// Look up any existing reward-share (using transaction's reward-share public key)
RewardShareData existingRewardShareData = this.getExistingRewardShare();
// If we have an existing reward-share then minter/recipient/reward-share-public-key should all match.
// This is to prevent malicious actors using multiple (fake) reward-share public keys for the same minter/recipient combo,
// or reusing the same reward-share public key for a different minter/recipient pair.
@ -102,10 +86,10 @@ public class RewardShareTransaction extends Transaction {
return ValidationResult.INVALID_PUBLIC_KEY;
final boolean isRecipientAlsoMinter = getCreator().getAddress().equals(this.rewardShareTransactionData.getRecipient());
final boolean isCancellingSharePercent = this.rewardShareTransactionData.getSharePercent().compareTo(BigDecimal.ZERO) < 0;
final boolean isCancellingSharePercent = this.rewardShareTransactionData.getSharePercent() < 0;
// Fee can be zero if self-share, and not cancelling
if (isRecipientAlsoMinter && !isCancellingSharePercent && this.transactionData.getFee().compareTo(BigDecimal.ZERO) >= 0)
if (isRecipientAlsoMinter && !isCancellingSharePercent && this.transactionData.getFee() >= 0)
return ValidationResult.OK;
return super.isFeeValid();
@ -114,7 +98,7 @@ public class RewardShareTransaction extends Transaction {
@Override
public ValidationResult isValid() throws DataException {
// Check reward share given to recipient. Negative is potentially OK to end a current reward-share. Zero also fine.
if (this.rewardShareTransactionData.getSharePercent().compareTo(MAX_SHARE) > 0)
if (this.rewardShareTransactionData.getSharePercent() > MAX_SHARE)
return ValidationResult.INVALID_REWARD_SHARE_PERCENT;
// Check reward-share public key is correct length
@ -127,7 +111,7 @@ public class RewardShareTransaction extends Transaction {
PublicKeyAccount creator = getCreator();
Account recipient = getRecipient();
final boolean isCancellingSharePercent = this.rewardShareTransactionData.getSharePercent().compareTo(BigDecimal.ZERO) < 0;
final boolean isCancellingSharePercent = this.rewardShareTransactionData.getSharePercent() < 0;
// Creator themselves needs to be allowed to mint (unless cancelling)
if (!isCancellingSharePercent && !creator.canMint())
@ -140,6 +124,7 @@ public class RewardShareTransaction extends Transaction {
// Look up any existing reward-share (using transaction's reward-share public key)
RewardShareData existingRewardShareData = this.getExistingRewardShare();
// If we have an existing reward-share then minter/recipient/reward-share-public-key should all match.
// This is to prevent malicious actors using multiple (fake) reward-share public keys for the same minter/recipient combo,
// or reusing the same reward-share public key for a different minter/recipient pair.
@ -168,7 +153,7 @@ public class RewardShareTransaction extends Transaction {
// Fee checking needed if not setting up new self-share
if (!(isRecipientAlsoMinter && existingRewardShareData == null))
// Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORT).compareTo(rewardShareTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT) < this.rewardShareTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -180,24 +165,24 @@ public class RewardShareTransaction extends Transaction {
// Grab any previous share info for orphaning purposes
RewardShareData rewardShareData = this.repository.getAccountRepository().getRewardShare(mintingAccount.getPublicKey(),
rewardShareTransactionData.getRecipient());
this.rewardShareTransactionData.getRecipient());
if (rewardShareData != null)
rewardShareTransactionData.setPreviousSharePercent(rewardShareData.getSharePercent());
this.rewardShareTransactionData.setPreviousSharePercent(rewardShareData.getSharePercent());
// Save this transaction, with previous share info
this.repository.getTransactionRepository().save(rewardShareTransactionData);
this.repository.getTransactionRepository().save(this.rewardShareTransactionData);
final boolean isSharePercentNegative = this.rewardShareTransactionData.getSharePercent().compareTo(BigDecimal.ZERO) < 0;
final boolean isSharePercentNegative = this.rewardShareTransactionData.getSharePercent() < 0;
// Negative share is actually a request to delete existing reward-share
if (isSharePercentNegative) {
this.repository.getAccountRepository().delete(mintingAccount.getPublicKey(), rewardShareTransactionData.getRecipient());
this.repository.getAccountRepository().delete(mintingAccount.getPublicKey(), this.rewardShareTransactionData.getRecipient());
} else {
// Save reward-share info
rewardShareData = new RewardShareData(mintingAccount.getPublicKey(), mintingAccount.getAddress(),
rewardShareTransactionData.getRecipient(), rewardShareTransactionData.getRewardSharePublicKey(),
rewardShareTransactionData.getSharePercent());
this.rewardShareTransactionData.getRecipient(), this.rewardShareTransactionData.getRewardSharePublicKey(),
this.rewardShareTransactionData.getSharePercent());
this.repository.getAccountRepository().save(rewardShareData);
}
}
@ -207,9 +192,9 @@ public class RewardShareTransaction extends Transaction {
super.processReferencesAndFees();
// If reward-share recipient has no last-reference then use this transaction's signature as last-reference so they can spend their block rewards
Account recipient = new Account(this.repository, rewardShareTransactionData.getRecipient());
Account recipient = new Account(this.repository, this.rewardShareTransactionData.getRecipient());
if (recipient.getLastReference() == null)
recipient.setLastReference(rewardShareTransactionData.getSignature());
recipient.setLastReference(this.rewardShareTransactionData.getSignature());
}
@Override
@ -217,21 +202,21 @@ public class RewardShareTransaction extends Transaction {
// Revert
PublicKeyAccount mintingAccount = getMintingAccount();
if (rewardShareTransactionData.getPreviousSharePercent() != null) {
if (this.rewardShareTransactionData.getPreviousSharePercent() != null) {
// Revert previous sharing arrangement
RewardShareData rewardShareData = new RewardShareData(mintingAccount.getPublicKey(), mintingAccount.getAddress(),
rewardShareTransactionData.getRecipient(), rewardShareTransactionData.getRewardSharePublicKey(),
rewardShareTransactionData.getPreviousSharePercent());
this.rewardShareTransactionData.getRecipient(), this.rewardShareTransactionData.getRewardSharePublicKey(),
this.rewardShareTransactionData.getPreviousSharePercent());
this.repository.getAccountRepository().save(rewardShareData);
} else {
// No previous arrangement so simply delete
this.repository.getAccountRepository().delete(mintingAccount.getPublicKey(), rewardShareTransactionData.getRecipient());
this.repository.getAccountRepository().delete(mintingAccount.getPublicKey(), this.rewardShareTransactionData.getRecipient());
}
// Save this transaction, with removed previous share info
rewardShareTransactionData.setPreviousSharePercent(null);
this.repository.getTransactionRepository().save(rewardShareTransactionData);
this.rewardShareTransactionData.setPreviousSharePercent(null);
this.repository.getTransactionRepository().save(this.rewardShareTransactionData);
}
@Override
@ -239,8 +224,8 @@ public class RewardShareTransaction extends Transaction {
super.orphanReferencesAndFees();
// If recipient didn't have a last-reference prior to this transaction then remove it
Account recipient = new Account(this.repository, rewardShareTransactionData.getRecipient());
if (Arrays.equals(recipient.getLastReference(), rewardShareTransactionData.getSignature()))
Account recipient = new Account(this.repository, this.rewardShareTransactionData.getRecipient());
if (Arrays.equals(recipient.getLastReference(), this.rewardShareTransactionData.getSignature()))
recipient.setLastReference(null);
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.naming.NameData;
import org.qortal.data.transaction.SellNameTransactionData;
@ -19,7 +17,7 @@ import com.google.common.base.Utf8;
public class SellNameTransaction extends Transaction {
/** Maximum amount/price for selling a name. Chosen so value, including 8 decimal places, encodes into 8 bytes or fewer. */
private static final BigDecimal MAX_AMOUNT = BigDecimal.valueOf(10_000_000_000L);
private static final long MAX_AMOUNT = Asset.MAX_QUANTITY;
// Properties
private SellNameTransactionData sellNameTransactionData;
@ -35,51 +33,32 @@ public class SellNameTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() {
return new ArrayList<>();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getOwner().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;
public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList();
}
// Navigation
public Account getOwner() throws DataException {
return new PublicKeyAccount(this.repository, this.sellNameTransactionData.getOwnerPublicKey());
public Account getOwner() {
return this.getCreator();
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
String name = this.sellNameTransactionData.getName();
// Check name size bounds
int nameLength = Utf8.encodedLength(sellNameTransactionData.getName());
int nameLength = Utf8.encodedLength(name);
if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH;
// Check name is lowercase
if (!sellNameTransactionData.getName().equals(sellNameTransactionData.getName().toLowerCase()))
if (!name.equals(name.toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE;
NameData nameData = this.repository.getNameRepository().fromName(sellNameTransactionData.getName());
NameData nameData = this.repository.getNameRepository().fromName(name);
// Check name exists
if (nameData == null)
@ -95,19 +74,15 @@ public class SellNameTransaction extends Transaction {
return ValidationResult.INVALID_NAME_OWNER;
// Check amount is positive
if (sellNameTransactionData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
if (this.sellNameTransactionData.getAmount() <= 0)
return ValidationResult.NEGATIVE_AMOUNT;
// Check amount within bounds
if (sellNameTransactionData.getAmount().compareTo(MAX_AMOUNT) >= 0)
if (this.sellNameTransactionData.getAmount() >= MAX_AMOUNT)
return ValidationResult.INVALID_AMOUNT;
// Check fee is positive
if (sellNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check issuer has enough funds
if (owner.getConfirmedBalance(Asset.QORT).compareTo(sellNameTransactionData.getFee()) < 0)
if (owner.getConfirmedBalance(Asset.QORT) < this.sellNameTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -116,15 +91,15 @@ public class SellNameTransaction extends Transaction {
@Override
public void process() throws DataException {
// Sell Name
Name name = new Name(this.repository, sellNameTransactionData.getName());
name.sell(sellNameTransactionData);
Name name = new Name(this.repository, this.sellNameTransactionData.getName());
name.sell(this.sellNameTransactionData);
}
@Override
public void orphan() throws DataException {
// Revert name
Name name = new Name(this.repository, sellNameTransactionData.getName());
name.unsell(sellNameTransactionData);
Name name = new Name(this.repository, this.sellNameTransactionData.getName());
name.unsell(this.sellNameTransactionData);
}
}

View File

@ -1,6 +1,5 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
@ -28,53 +27,30 @@ public class SetGroupTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList();
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getCreator().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.getCreator().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
}
// Navigation
// Processing
@Override
public ValidationResult isValid() throws DataException {
int defaultGroupId = this.setGroupTransactionData.getDefaultGroupId();
// Check group exists
if (!this.repository.getGroupRepository().groupExists(setGroupTransactionData.getDefaultGroupId()))
if (!this.repository.getGroupRepository().groupExists(defaultGroupId))
return ValidationResult.GROUP_DOES_NOT_EXIST;
Account creator = getCreator();
// Must be member of group
if (!this.repository.getGroupRepository().memberExists(setGroupTransactionData.getDefaultGroupId(), creator.getAddress()))
if (!this.repository.getGroupRepository().memberExists(defaultGroupId, creator.getAddress()))
return ValidationResult.NOT_GROUP_MEMBER;
// Check fee is positive
if (setGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORT).compareTo(setGroupTransactionData.getFee()) < 0)
if (creator.getConfirmedBalance(Asset.QORT) < this.setGroupTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -88,13 +64,13 @@ public class SetGroupTransaction extends Transaction {
if (previousDefaultGroupId == null)
previousDefaultGroupId = Group.NO_GROUP;
setGroupTransactionData.setPreviousDefaultGroupId(previousDefaultGroupId);
this.setGroupTransactionData.setPreviousDefaultGroupId(previousDefaultGroupId);
// Save this transaction with account's previous defaultGroupId value
this.repository.getTransactionRepository().save(setGroupTransactionData);
this.repository.getTransactionRepository().save(this.setGroupTransactionData);
// Set account's new default groupID
creator.setDefaultGroupId(setGroupTransactionData.getDefaultGroupId());
creator.setDefaultGroupId(this.setGroupTransactionData.getDefaultGroupId());
}
@Override
@ -102,15 +78,15 @@ public class SetGroupTransaction extends Transaction {
// Revert
Account creator = getCreator();
Integer previousDefaultGroupId = setGroupTransactionData.getPreviousDefaultGroupId();
Integer previousDefaultGroupId = this.setGroupTransactionData.getPreviousDefaultGroupId();
if (previousDefaultGroupId == null)
previousDefaultGroupId = Group.NO_GROUP;
creator.setDefaultGroupId(previousDefaultGroupId);
// Save this transaction with removed previous defaultGroupId value
setGroupTransactionData.setPreviousDefaultGroupId(null);
this.repository.getTransactionRepository().save(setGroupTransactionData);
this.setGroupTransactionData.setPreviousDefaultGroupId(null);
this.repository.getTransactionRepository().save(this.setGroupTransactionData);
}
}

View File

@ -1,9 +1,6 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@ -261,8 +258,11 @@ public abstract class Transaction {
private static final Logger LOGGER = LogManager.getLogger(Transaction.class);
// Properties
protected Repository repository;
protected TransactionData transactionData;
/** Cached creator account. Use <tt>getCreator()</tt> to access. */
private PublicKeyAccount creator = null;
// Constructors
@ -320,12 +320,12 @@ public abstract class Transaction {
/** Returns whether transaction's fee is at least minimum unit fee as specified in blockchain config. */
public boolean hasMinimumFee() {
return this.transactionData.getFee().compareTo(BlockChain.getInstance().getUnitFee()) >= 0;
return this.transactionData.getFee() >= BlockChain.getInstance().getUnscaledUnitFee();
}
public BigDecimal feePerByte() {
public long feePerByte() {
try {
return this.transactionData.getFee().divide(new BigDecimal(TransactionTransformer.getDataLength(this.transactionData)), MathContext.DECIMAL32);
return this.transactionData.getFee() / TransactionTransformer.getDataLength(this.transactionData);
} catch (TransformationException e) {
throw new IllegalStateException("Unable to get transaction byte length?");
}
@ -333,10 +333,13 @@ public abstract class Transaction {
/** Returns whether transaction's fee is at least amount needed to cover byte-length of transaction. */
public boolean hasMinimumFeePerByte() {
return this.feePerByte().compareTo(BlockChain.getInstance().getMinFeePerByte()) >= 0;
long unitFee = BlockChain.getInstance().getUnscaledUnitFee();
int maxBytePerUnitFee = BlockChain.getInstance().getMaxBytesPerUnitFee();
return this.feePerByte() >= maxBytePerUnitFee / unitFee;
}
public BigDecimal calcRecommendedFee() {
public long calcRecommendedFee() {
int dataLength;
try {
dataLength = TransactionTransformer.getDataLength(this.transactionData);
@ -344,12 +347,11 @@ public abstract class Transaction {
throw new IllegalStateException("Unable to get transaction byte length?");
}
BigDecimal maxBytePerUnitFee = BlockChain.getInstance().getMaxBytesPerUnitFee();
int maxBytePerUnitFee = BlockChain.getInstance().getMaxBytesPerUnitFee();
BigDecimal unitFeeCount = BigDecimal.valueOf(dataLength).divide(maxBytePerUnitFee, RoundingMode.UP);
int unitFeeCount = ((dataLength - 1) / maxBytePerUnitFee) + 1;
BigDecimal recommendedFee = BlockChain.getInstance().getUnitFee().multiply(unitFeeCount).setScale(8);
return recommendedFee;
return BlockChain.getInstance().getUnscaledUnitFee() * unitFeeCount;
}
/**
@ -399,45 +401,32 @@ public abstract class Transaction {
* @return list of recipients accounts, or empty list if none
* @throws DataException
*/
public abstract List<Account> getRecipientAccounts() throws DataException;
/**
* Returns a list of involved accounts for this transaction.
* <p>
* "Involved" means sender or recipient.
*
* @return list of involved accounts, or empty list if none
* @throws DataException
*/
public List<Account> getInvolvedAccounts() throws DataException {
// Typically this is all the recipients plus the transaction creator/sender
List<Account> participants = new ArrayList<>(getRecipientAccounts());
participants.add(getCreator());
return participants;
public List<Account> getRecipientAccounts() throws DataException {
throw new DataException("Placeholder for new AT code");
}
/**
* Returns whether passed account is an involved party in this transaction.
* <p>
* Account could be sender, or any one of the potential recipients.
* Returns a list of recipient addresses for this transaction.
*
* @param account
* @return true if account is involved, false otherwise
* @return list of recipients addresses, or empty list if none
* @throws DataException
*/
public abstract boolean isInvolved(Account account) throws DataException;
public abstract List<String> getRecipientAddresses() throws DataException;
/**
* Returns amount of QORT lost/gained by passed account due to this transaction.
* Returns a list of involved addresses for this transaction.
* <p>
* Amounts "lost", e.g. sent by sender and fees, are returned as negative values.<br>
* Amounts "gained", e.g. QORT sent to recipient, are returned as positive values.
* "Involved" means sender or recipient.
*
* @param account
* @return Amount of QORT lost/gained by account, or BigDecimal.ZERO otherwise
* @return list of involved addresses, or empty list if none
* @throws DataException
*/
public abstract BigDecimal getAmount(Account account) throws DataException;
public List<String> getInvolvedAddresses() throws DataException {
// Typically this is all the recipients plus the transaction creator/sender
List<String> participants = new ArrayList<>(getRecipientAddresses());
participants.add(0, this.getCreator().getAddress());
return participants;
}
// Navigation
@ -447,11 +436,11 @@ public abstract class Transaction {
* @return creator
* @throws DataException
*/
protected PublicKeyAccount getCreator() throws DataException {
if (this.transactionData.getCreatorPublicKey() == null)
return null;
protected PublicKeyAccount getCreator() {
if (this.creator == null)
this.creator = new PublicKeyAccount(this.repository, this.transactionData.getCreatorPublicKey());
return new PublicKeyAccount(this.repository, this.transactionData.getCreatorPublicKey());
return this.creator;
}
/**
@ -460,7 +449,7 @@ public abstract class Transaction {
* @return Parent's TransactionData, or null if no parent found (which should not happen)
* @throws DataException
*/
public TransactionData getParent() throws DataException {
protected TransactionData getParent() throws DataException {
byte[] reference = this.transactionData.getReference();
if (reference == null)
return null;
@ -474,7 +463,7 @@ public abstract class Transaction {
* @return Child's TransactionData, or null if no child found
* @throws DataException
*/
public TransactionData getChild() throws DataException {
protected TransactionData getChild() throws DataException {
byte[] signature = this.transactionData.getSignature();
if (signature == null)
return null;
@ -925,9 +914,9 @@ public abstract class Transaction {
Account creator = getCreator();
// Update transaction creator's balance
creator.setConfirmedBalance(Asset.QORT, creator.getConfirmedBalance(Asset.QORT).subtract(transactionData.getFee()));
creator.setConfirmedBalance(Asset.QORT, creator.getConfirmedBalance(Asset.QORT) - transactionData.getFee());
// Update transaction creator's reference
// Update transaction creator's reference (and possibly public key)
creator.setLastReference(transactionData.getSignature());
}
@ -949,9 +938,9 @@ public abstract class Transaction {
Account creator = getCreator();
// Update transaction creator's balance
creator.setConfirmedBalance(Asset.QORT, creator.getConfirmedBalance(Asset.QORT).add(transactionData.getFee()));
creator.setConfirmedBalance(Asset.QORT, creator.getConfirmedBalance(Asset.QORT) + transactionData.getFee());
// Update transaction creator's reference
// Update transaction creator's reference (and possibly public key)
creator.setLastReference(transactionData.getReference());
}

View File

@ -1,12 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.PaymentData;
import org.qortal.data.transaction.TransactionData;
import org.qortal.data.transaction.TransferAssetTransactionData;
@ -17,7 +14,9 @@ import org.qortal.repository.Repository;
public class TransferAssetTransaction extends Transaction {
// Properties
private TransferAssetTransactionData transferAssetTransactionData;
private PaymentData paymentData = null;
// Constructors
@ -30,94 +29,63 @@ public class TransferAssetTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(new Account(this.repository, transferAssetTransactionData.getRecipient()));
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getSender().getAddress()))
return true;
if (address.equals(transferAssetTransactionData.getRecipient()))
return true;
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
String senderAddress = this.getSender().getAddress();
if (address.equals(senderAddress))
amount = amount.subtract(this.transactionData.getFee());
// We're only interested in OQRT amounts
if (transferAssetTransactionData.getAssetId() == Asset.QORT) {
if (address.equals(transferAssetTransactionData.getRecipient()))
amount = amount.add(transferAssetTransactionData.getAmount());
else if (address.equals(senderAddress))
amount = amount.subtract(transferAssetTransactionData.getAmount());
}
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.transferAssetTransactionData.getRecipient());
}
// Navigation
public Account getSender() throws DataException {
return new PublicKeyAccount(this.repository, this.transferAssetTransactionData.getSenderPublicKey());
public Account getSender() {
return this.getCreator();
}
// Processing
private PaymentData getPaymentData() {
return new PaymentData(transferAssetTransactionData.getRecipient(), transferAssetTransactionData.getAssetId(),
transferAssetTransactionData.getAmount());
if (this.paymentData == null)
this.paymentData = new PaymentData(this.transferAssetTransactionData.getRecipient(), this.transferAssetTransactionData.getAssetId(),
this.transferAssetTransactionData.getAmount());
return this.paymentData;
}
@Override
public ValidationResult isValid() throws DataException {
// Wrap asset transfer as a payment and delegate final payment checks to Payment class
return new Payment(this.repository).isValid(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee());
return new Payment(this.repository).isValid(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), this.transferAssetTransactionData.getFee());
}
@Override
public ValidationResult isProcessable() throws DataException {
// Wrap asset transfer as a payment and delegate final processable checks to Payment class
return new Payment(this.repository).isProcessable(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee());
return new Payment(this.repository).isProcessable(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), this.transferAssetTransactionData.getFee());
}
@Override
public void process() throws DataException {
// Wrap asset transfer as a payment and delegate processing to Payment class. Only update recipient's last reference if transferring QORT.
new Payment(this.repository).process(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(),
transferAssetTransactionData.getSignature());
// Wrap asset transfer as a payment and delegate processing to Payment class.
new Payment(this.repository).process(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), this.transferAssetTransactionData.getSignature());
}
@Override
public void processReferencesAndFees() throws DataException {
// Wrap asset transfer as a payment and delegate processing to Payment class. Only update recipient's last reference if transferring QORT.
new Payment(this.repository).processReferencesAndFees(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(),
transferAssetTransactionData.getSignature(), false);
new Payment(this.repository).processReferencesAndFees(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), this.transferAssetTransactionData.getFee(),
this.transferAssetTransactionData.getSignature(), false);
}
@Override
public void orphan() throws DataException {
// Wrap asset transfer as a payment and delegate processing to Payment class. Only revert recipient's last reference if transferring QORT.
new Payment(this.repository).orphan(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(),
transferAssetTransactionData.getSignature(), transferAssetTransactionData.getReference());
// Wrap asset transfer as a payment and delegate processing to Payment class.
new Payment(this.repository).orphan(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(),
this.transferAssetTransactionData.getSignature(), this.transferAssetTransactionData.getReference());
}
@Override
public void orphanReferencesAndFees() throws DataException {
// Wrap asset transfer as a payment and delegate processing to Payment class. Only revert recipient's last reference if transferring QORT.
new Payment(this.repository).orphanReferencesAndFees(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(),
transferAssetTransactionData.getSignature(), transferAssetTransactionData.getReference(), false);
new Payment(this.repository).orphanReferencesAndFees(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), this.transferAssetTransactionData.getFee(),
this.transferAssetTransactionData.getSignature(), this.transferAssetTransactionData.getReference(), false);
}
}

View File

@ -1,6 +1,5 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -8,7 +7,6 @@ import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.block.BlockChain;
import org.qortal.crypto.Crypto;
import org.qortal.data.account.AccountData;
@ -36,42 +34,17 @@ public class TransferPrivsTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(new Account(this.repository, transferPrivsTransactionData.getRecipient()));
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getSender().getAddress()))
return true;
if (address.equals(transferPrivsTransactionData.getRecipient()))
return true;
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
String senderAddress = this.getSender().getAddress();
if (address.equals(senderAddress))
amount = amount.subtract(this.transactionData.getFee());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.transferPrivsTransactionData.getRecipient());
}
// Navigation
public Account getSender() throws DataException {
return new PublicKeyAccount(this.repository, this.transferPrivsTransactionData.getSenderPublicKey());
public Account getSender() {
return this.getCreator();
}
public Account getRecipient() throws DataException {
public Account getRecipient() {
return new Account(this.repository, this.transferPrivsTransactionData.getRecipient());
}
@ -167,9 +140,9 @@ public class TransferPrivsTransaction extends Transaction {
super.processReferencesAndFees();
// If recipient has no last-reference then use this transaction's signature as last-reference so they can spend their block rewards
Account recipient = new Account(this.repository, transferPrivsTransactionData.getRecipient());
Account recipient = new Account(this.repository, this.transferPrivsTransactionData.getRecipient());
if (recipient.getLastReference() == null)
recipient.setLastReference(transferPrivsTransactionData.getSignature());
recipient.setLastReference(this.transferPrivsTransactionData.getSignature());
}
@Override
@ -249,8 +222,8 @@ public class TransferPrivsTransaction extends Transaction {
super.orphanReferencesAndFees();
// If recipient didn't have a last-reference prior to this transaction then remove it
Account recipient = new Account(this.repository, transferPrivsTransactionData.getRecipient());
if (Arrays.equals(recipient.getLastReference(), transferPrivsTransactionData.getSignature()))
Account recipient = new Account(this.repository, this.transferPrivsTransactionData.getRecipient());
if (Arrays.equals(recipient.getLastReference(), this.transferPrivsTransactionData.getSignature()))
recipient.setLastReference(null);
}

View File

@ -1,6 +1,5 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
@ -32,42 +31,14 @@ public class UpdateAssetTransaction extends Transaction {
// 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;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.updateAssetTransactionData.getNewOwner());
}
// 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());
public PublicKeyAccount getOwner() {
return this.getCreator();
}
// Processing
@ -75,37 +46,33 @@ public class UpdateAssetTransaction extends Transaction {
@Override
public ValidationResult isValid() throws DataException {
// Check asset actually exists
AssetData assetData = this.repository.getAssetRepository().fromAssetId(updateAssetTransactionData.getAssetId());
AssetData assetData = this.repository.getAssetRepository().fromAssetId(this.updateAssetTransactionData.getAssetId());
if (assetData == null)
return ValidationResult.ASSET_DOES_NOT_EXIST;
// Check new owner address is valid
if (!Crypto.isValidAddress(updateAssetTransactionData.getNewOwner()))
if (!Crypto.isValidAddress(this.updateAssetTransactionData.getNewOwner()))
return ValidationResult.INVALID_ADDRESS;
// Check new description size bounds. Note: zero length means DO NOT CHANGE description
int newDescriptionLength = Utf8.encodedLength(updateAssetTransactionData.getNewDescription());
int newDescriptionLength = Utf8.encodedLength(this.updateAssetTransactionData.getNewDescription());
if (newDescriptionLength > Asset.MAX_DESCRIPTION_SIZE)
return ValidationResult.INVALID_DATA_LENGTH;
// Check new data size bounds. Note: zero length means DO NOT CHANGE data
int newDataLength = Utf8.encodedLength(updateAssetTransactionData.getNewData());
int newDataLength = Utf8.encodedLength(this.updateAssetTransactionData.getNewData());
if (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())
if (assetData.getCreationGroupId() != this.updateAssetTransactionData.getTxGroupId())
return ValidationResult.TX_GROUP_ID_MISMATCH;
// Check fee is positive
if (updateAssetTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
Account currentOwner = getOwner();
// Check current owner has enough funds
if (currentOwner.getConfirmedBalance(Asset.QORT).compareTo(updateAssetTransactionData.getFee()) < 0)
if (currentOwner.getConfirmedBalance(Asset.QORT) < this.updateAssetTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -115,7 +82,7 @@ public class UpdateAssetTransaction extends Transaction {
public ValidationResult isProcessable() throws DataException {
// Check transaction's public key matches asset's current owner
Account currentOwner = getOwner();
AssetData assetData = this.repository.getAssetRepository().fromAssetId(updateAssetTransactionData.getAssetId());
AssetData assetData = this.repository.getAssetRepository().fromAssetId(this.updateAssetTransactionData.getAssetId());
if (!assetData.getOwner().equals(currentOwner.getAddress()))
return ValidationResult.INVALID_ASSET_OWNER;
@ -126,21 +93,21 @@ public class UpdateAssetTransaction extends Transaction {
@Override
public void process() throws DataException {
// Update Asset
Asset asset = new Asset(this.repository, updateAssetTransactionData.getAssetId());
asset.update(updateAssetTransactionData);
Asset asset = new Asset(this.repository, this.updateAssetTransactionData.getAssetId());
asset.update(this.updateAssetTransactionData);
// Save this transaction, with updated "name reference" to previous transaction that updated name
this.repository.getTransactionRepository().save(updateAssetTransactionData);
this.repository.getTransactionRepository().save(this.updateAssetTransactionData);
}
@Override
public void orphan() throws DataException {
// Revert asset
Asset asset = new Asset(this.repository, updateAssetTransactionData.getAssetId());
asset.revert(updateAssetTransactionData);
Asset asset = new Asset(this.repository, this.updateAssetTransactionData.getAssetId());
asset.revert(this.updateAssetTransactionData);
// Save this transaction, with removed "name reference" to previous transaction that updated name
this.repository.getTransactionRepository().save(updateAssetTransactionData);
this.repository.getTransactionRepository().save(this.updateAssetTransactionData);
}
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.data.group.GroupData;
@ -33,38 +31,14 @@ public class UpdateGroupTransaction extends Transaction {
// 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;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.updateGroupTransactionData.getNewOwner());
}
// Navigation
public Account getOwner() throws DataException {
return new PublicKeyAccount(this.repository, this.updateGroupTransactionData.getOwnerPublicKey());
public Account getOwner() {
return this.getCreator();
}
public Account getNewOwner() throws DataException {
@ -76,46 +50,42 @@ public class UpdateGroupTransaction extends Transaction {
@Override
public ValidationResult isValid() throws DataException {
// Check new owner address is valid
if (!Crypto.isValidAddress(updateGroupTransactionData.getNewOwner()))
if (!Crypto.isValidAddress(this.updateGroupTransactionData.getNewOwner()))
return ValidationResult.INVALID_ADDRESS;
// Check new approval threshold is valid
if (updateGroupTransactionData.getNewApprovalThreshold() == null)
if (this.updateGroupTransactionData.getNewApprovalThreshold() == null)
return ValidationResult.INVALID_GROUP_APPROVAL_THRESHOLD;
// Check min/max block delay values
if (updateGroupTransactionData.getNewMinimumBlockDelay() < 0)
if (this.updateGroupTransactionData.getNewMinimumBlockDelay() < 0)
return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
if (updateGroupTransactionData.getNewMaximumBlockDelay() < 1)
if (this.updateGroupTransactionData.getNewMaximumBlockDelay() < 1)
return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
if (updateGroupTransactionData.getNewMaximumBlockDelay() < updateGroupTransactionData.getNewMinimumBlockDelay())
if (this.updateGroupTransactionData.getNewMaximumBlockDelay() < this.updateGroupTransactionData.getNewMinimumBlockDelay())
return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
// Check new description size bounds
int newDescriptionLength = Utf8.encodedLength(updateGroupTransactionData.getNewDescription());
int newDescriptionLength = Utf8.encodedLength(this.updateGroupTransactionData.getNewDescription());
if (newDescriptionLength < 1 || newDescriptionLength > Group.MAX_DESCRIPTION_SIZE)
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
GroupData groupData = this.repository.getGroupRepository().fromGroupId(updateGroupTransactionData.getGroupId());
GroupData groupData = this.repository.getGroupRepository().fromGroupId(this.updateGroupTransactionData.getGroupId());
// Check group exists
if (groupData == null)
return ValidationResult.GROUP_DOES_NOT_EXIST;
// As this transaction type could require approval, check txGroupId matches groupID at creation
if (groupData.getCreationGroupId() != updateGroupTransactionData.getTxGroupId())
if (groupData.getCreationGroupId() != this.updateGroupTransactionData.getTxGroupId())
return ValidationResult.TX_GROUP_ID_MISMATCH;
Account owner = getOwner();
// Check fee is positive
if (updateGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (owner.getConfirmedBalance(Asset.QORT).compareTo(updateGroupTransactionData.getFee()) < 0)
if (owner.getConfirmedBalance(Asset.QORT) < this.updateGroupTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -123,7 +93,7 @@ public class UpdateGroupTransaction extends Transaction {
@Override
public ValidationResult isProcessable() throws DataException {
GroupData groupData = this.repository.getGroupRepository().fromGroupId(updateGroupTransactionData.getGroupId());
GroupData groupData = this.repository.getGroupRepository().fromGroupId(this.updateGroupTransactionData.getGroupId());
Account owner = getOwner();
// Check transaction's public key matches group's current owner
@ -133,31 +103,30 @@ public class UpdateGroupTransaction extends Transaction {
Account newOwner = getNewOwner();
// Check new owner is not banned
if (this.repository.getGroupRepository().banExists(updateGroupTransactionData.getGroupId(), newOwner.getAddress()))
if (this.repository.getGroupRepository().banExists(this.updateGroupTransactionData.getGroupId(), newOwner.getAddress()))
return ValidationResult.BANNED_FROM_GROUP;
return ValidationResult.OK;
}
@Override
public void process() throws DataException {
// Update Group
Group group = new Group(this.repository, updateGroupTransactionData.getGroupId());
group.updateGroup(updateGroupTransactionData);
Group group = new Group(this.repository, this.updateGroupTransactionData.getGroupId());
group.updateGroup(this.updateGroupTransactionData);
// Save this transaction, now with updated "group reference" to previous transaction that updated group
this.repository.getTransactionRepository().save(updateGroupTransactionData);
this.repository.getTransactionRepository().save(this.updateGroupTransactionData);
}
@Override
public void orphan() throws DataException {
// Revert Group update
Group group = new Group(this.repository, updateGroupTransactionData.getGroupId());
group.unupdateGroup(updateGroupTransactionData);
Group group = new Group(this.repository, this.updateGroupTransactionData.getGroupId());
group.unupdateGroup(this.updateGroupTransactionData);
// Save this transaction, now with removed "group reference"
this.repository.getTransactionRepository().save(updateGroupTransactionData);
this.repository.getTransactionRepository().save(this.updateGroupTransactionData);
}
}

View File

@ -1,11 +1,9 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.data.naming.NameData;
@ -33,41 +31,17 @@ public class UpdateNameTransaction extends Transaction {
// 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;
public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.updateNameTransactionData.getNewOwner());
}
// Navigation
public Account getOwner() throws DataException {
return new PublicKeyAccount(this.repository, this.updateNameTransactionData.getOwnerPublicKey());
public Account getOwner() {
return this.getCreator();
}
public Account getNewOwner() throws DataException {
public Account getNewOwner() {
return new Account(this.repository, this.updateNameTransactionData.getNewOwner());
}
@ -75,42 +49,40 @@ public class UpdateNameTransaction extends Transaction {
@Override
public ValidationResult isValid() throws DataException {
String name = this.updateNameTransactionData.getName();
// Check new owner address is valid
if (!Crypto.isValidAddress(updateNameTransactionData.getNewOwner()))
if (!Crypto.isValidAddress(this.updateNameTransactionData.getNewOwner()))
return ValidationResult.INVALID_ADDRESS;
// Check name size bounds
int nameLength = Utf8.encodedLength(updateNameTransactionData.getName());
int nameLength = Utf8.encodedLength(name);
if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH;
// Check new data size bounds
int newDataLength = Utf8.encodedLength(updateNameTransactionData.getNewData());
int newDataLength = Utf8.encodedLength(this.updateNameTransactionData.getNewData());
if (newDataLength < 1 || newDataLength > Name.MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH;
// Check name is lowercase
if (!updateNameTransactionData.getName().equals(updateNameTransactionData.getName().toLowerCase()))
if (!name.equals(name.toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE;
NameData nameData = this.repository.getNameRepository().fromName(updateNameTransactionData.getName());
NameData nameData = this.repository.getNameRepository().fromName(name);
// Check name exists
if (nameData == null)
return ValidationResult.NAME_DOES_NOT_EXIST;
// As this transaction type could require approval, check txGroupId matches groupID at creation
if (nameData.getCreationGroupId() != updateNameTransactionData.getTxGroupId())
if (nameData.getCreationGroupId() != this.updateNameTransactionData.getTxGroupId())
return ValidationResult.TX_GROUP_ID_MISMATCH;
Account owner = getOwner();
// Check fee is positive
if (updateNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check issuer has enough funds
if (owner.getConfirmedBalance(Asset.QORT).compareTo(updateNameTransactionData.getFee()) < 0)
// Check owner has enough funds
if (owner.getConfirmedBalance(Asset.QORT) < this.updateNameTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -118,7 +90,7 @@ public class UpdateNameTransaction extends Transaction {
@Override
public ValidationResult isProcessable() throws DataException {
NameData nameData = this.repository.getNameRepository().fromName(updateNameTransactionData.getName());
NameData nameData = this.repository.getNameRepository().fromName(this.updateNameTransactionData.getName());
// Check name isn't currently for sale
if (nameData.getIsForSale())
@ -136,21 +108,21 @@ public class UpdateNameTransaction extends Transaction {
@Override
public void process() throws DataException {
// Update Name
Name name = new Name(this.repository, updateNameTransactionData.getName());
name.update(updateNameTransactionData);
Name name = new Name(this.repository, this.updateNameTransactionData.getName());
name.update(this.updateNameTransactionData);
// Save this transaction, now with updated "name reference" to previous transaction that updated name
this.repository.getTransactionRepository().save(updateNameTransactionData);
this.repository.getTransactionRepository().save(this.updateNameTransactionData);
}
@Override
public void orphan() throws DataException {
// Revert name
Name name = new Name(this.repository, updateNameTransactionData.getName());
name.revert(updateNameTransactionData);
Name name = new Name(this.repository, this.updateNameTransactionData.getName());
name.revert(this.updateNameTransactionData);
// Save this transaction, now with removed "name reference"
this.repository.getTransactionRepository().save(updateNameTransactionData);
this.repository.getTransactionRepository().save(this.updateNameTransactionData);
}
}

View File

@ -1,13 +1,11 @@
package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.transaction.TransactionData;
import org.qortal.data.transaction.VoteOnPollTransactionData;
@ -39,72 +37,55 @@ public class VoteOnPollTransaction extends Transaction {
// More information
@Override
public List<Account> getRecipientAccounts() {
return new ArrayList<>();
}
@Override
public boolean isInvolved(Account account) throws DataException {
return account.getAddress().equals(this.getCreator().getAddress());
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
BigDecimal amount = BigDecimal.ZERO.setScale(8);
if (account.getAddress().equals(this.getCreator().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList();
}
// Navigation
public Account getVoter() throws DataException {
return new PublicKeyAccount(this.repository, voteOnPollTransactionData.getVoterPublicKey());
public Account getVoter() {
return this.getCreator();
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
String pollName = this.voteOnPollTransactionData.getPollName();
// Check name size bounds
int pollNameLength = Utf8.encodedLength(voteOnPollTransactionData.getPollName());
int pollNameLength = Utf8.encodedLength(pollName);
if (pollNameLength < 1 || pollNameLength > Poll.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH;
// Check poll name is lowercase
if (!voteOnPollTransactionData.getPollName().equals(voteOnPollTransactionData.getPollName().toLowerCase()))
if (!pollName.equals(pollName.toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE;
VotingRepository votingRepository = this.repository.getVotingRepository();
// Check poll exists
PollData pollData = votingRepository.fromPollName(voteOnPollTransactionData.getPollName());
PollData pollData = votingRepository.fromPollName(pollName);
if (pollData == null)
return ValidationResult.POLL_DOES_NOT_EXIST;
// Check poll option index is within bounds
List<PollOptionData> pollOptions = pollData.getPollOptions();
int optionIndex = voteOnPollTransactionData.getOptionIndex();
int optionIndex = this.voteOnPollTransactionData.getOptionIndex();
if (optionIndex < 0 || optionIndex > pollOptions.size() - 1)
return ValidationResult.POLL_OPTION_DOES_NOT_EXIST;
// Check if vote already exists
VoteOnPollData voteOnPollData = votingRepository.getVote(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey());
VoteOnPollData voteOnPollData = votingRepository.getVote(pollName, this.voteOnPollTransactionData.getVoterPublicKey());
if (voteOnPollData != null && voteOnPollData.getOptionIndex() == optionIndex)
return ValidationResult.ALREADY_VOTED_FOR_THAT_OPTION;
// Check fee is positive
if (voteOnPollTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check reference is correct
Account voter = getVoter();
// Check voter has enough funds
if (voter.getConfirmedBalance(Asset.QORT).compareTo(voteOnPollTransactionData.getFee()) < 0)
if (voter.getConfirmedBalance(Asset.QORT) < this.voteOnPollTransactionData.getFee())
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -112,27 +93,28 @@ public class VoteOnPollTransaction extends Transaction {
@Override
public void process() throws DataException {
String pollName = this.voteOnPollTransactionData.getPollName();
Account voter = getVoter();
VotingRepository votingRepository = this.repository.getVotingRepository();
// Check for previous vote so we can save option in case of orphaning
VoteOnPollData previousVoteOnPollData = votingRepository.getVote(voteOnPollTransactionData.getPollName(),
voteOnPollTransactionData.getVoterPublicKey());
VoteOnPollData previousVoteOnPollData = votingRepository.getVote(pollName, this.voteOnPollTransactionData.getVoterPublicKey());
if (previousVoteOnPollData != null) {
voteOnPollTransactionData.setPreviousOptionIndex(previousVoteOnPollData.getOptionIndex());
LOGGER.trace("Previous vote by " + voter.getAddress() + " on poll \"" + voteOnPollTransactionData.getPollName() + "\" was option index "
+ previousVoteOnPollData.getOptionIndex());
LOGGER.trace(() -> String.format("Previous vote by %s on poll \"%s\" was option index %d",
voter.getAddress(), pollName, previousVoteOnPollData.getOptionIndex()));
}
// Save this transaction, now with possible previous vote
this.repository.getTransactionRepository().save(voteOnPollTransactionData);
// Apply vote to poll
LOGGER.trace("Vote by " + voter.getAddress() + " on poll \"" + voteOnPollTransactionData.getPollName() + "\" with option index "
+ voteOnPollTransactionData.getOptionIndex());
VoteOnPollData newVoteOnPollData = new VoteOnPollData(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey(),
voteOnPollTransactionData.getOptionIndex());
LOGGER.trace(() -> String.format("Vote by %s on poll \"%s\" with option index %d",
voter.getAddress(), pollName, this.voteOnPollTransactionData.getOptionIndex()));
VoteOnPollData newVoteOnPollData = new VoteOnPollData(pollName, this.voteOnPollTransactionData.getVoterPublicKey(),
this.voteOnPollTransactionData.getOptionIndex());
votingRepository.save(newVoteOnPollData);
}
@ -142,22 +124,24 @@ public class VoteOnPollTransaction extends Transaction {
// Does this transaction have previous vote info?
VotingRepository votingRepository = this.repository.getVotingRepository();
Integer previousOptionIndex = voteOnPollTransactionData.getPreviousOptionIndex();
Integer previousOptionIndex = this.voteOnPollTransactionData.getPreviousOptionIndex();
if (previousOptionIndex != null) {
// Reinstate previous vote
LOGGER.trace(() -> String.format("Reinstating previous vote by %s on poll \"%s\" with option index %d", voter.getAddress(), voteOnPollTransactionData.getPollName(), previousOptionIndex));
VoteOnPollData previousVoteOnPollData = new VoteOnPollData(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey(),
LOGGER.trace(() -> String.format("Reinstating previous vote by %s on poll \"%s\" with option index %d",
voter.getAddress(), this.voteOnPollTransactionData.getPollName(), previousOptionIndex));
VoteOnPollData previousVoteOnPollData = new VoteOnPollData(this.voteOnPollTransactionData.getPollName(), this.voteOnPollTransactionData.getVoterPublicKey(),
previousOptionIndex);
votingRepository.save(previousVoteOnPollData);
} else {
// Delete vote
LOGGER.trace(() -> String.format("Deleting vote by %s on poll \"%s\" with option index %d", voter.getAddress(), voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getOptionIndex()));
votingRepository.delete(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey());
LOGGER.trace(() -> String.format("Deleting vote by %s on poll \"%s\" with option index %d",
voter.getAddress(), this.voteOnPollTransactionData.getPollName(), this.voteOnPollTransactionData.getOptionIndex()));
votingRepository.delete(this.voteOnPollTransactionData.getPollName(), this.voteOnPollTransactionData.getVoterPublicKey());
}
// Save this transaction, with removed previous vote info
voteOnPollTransactionData.setPreviousOptionIndex(null);
this.repository.getTransactionRepository().save(voteOnPollTransactionData);
this.voteOnPollTransactionData.setPreviousOptionIndex(null);
this.repository.getTransactionRepository().save(this.voteOnPollTransactionData);
}
}

View File

@ -2,10 +2,8 @@ package org.qortal.transform;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import org.json.simple.JSONObject;
import org.qortal.data.PaymentData;
import org.qortal.utils.Serialization;
@ -15,8 +13,6 @@ public class PaymentTransformer extends Transformer {
// Property lengths
private static final int RECIPIENT_LENGTH = ADDRESS_LENGTH;
private static final int ASSET_ID_LENGTH = LONG_LENGTH;
private static final int AMOUNT_LENGTH = 12;
private static final int TOTAL_LENGTH = RECIPIENT_LENGTH + ASSET_ID_LENGTH + AMOUNT_LENGTH;
@ -25,7 +21,7 @@ public class PaymentTransformer extends Transformer {
long assetId = byteBuffer.getLong();
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer, AMOUNT_LENGTH);
long amount = byteBuffer.getLong();
return new PaymentData(recipient, assetId, amount);
}
@ -42,7 +38,7 @@ public class PaymentTransformer extends Transformer {
bytes.write(Longs.toByteArray(paymentData.getAssetId()));
Serialization.serializeBigDecimal(bytes, paymentData.getAmount(), AMOUNT_LENGTH);
bytes.write(Longs.toByteArray(paymentData.getAmount()));
return bytes.toByteArray();
} catch (IOException | ClassCastException e) {
@ -50,24 +46,4 @@ public class PaymentTransformer extends Transformer {
}
}
@SuppressWarnings("unchecked")
public static JSONObject toJSON(PaymentData paymentData) throws TransformationException {
JSONObject json = new JSONObject();
try {
json.put("recipient", paymentData.getRecipient());
// For gen1 support:
json.put("asset", paymentData.getAssetId());
// Gen2 version:
json.put("assetId", paymentData.getAssetId());
json.put("amount", paymentData.getAmount().toPlainString());
} catch (ClassCastException e) {
throw new TransformationException(e);
}
return json;
}
}

View File

@ -6,7 +6,9 @@ public abstract class Transformer {
public static final int BYTE_LENGTH = 1;
public static final int INT_LENGTH = 4;
public static final int LONG_LENGTH = 8;
public static final int BIG_DECIMAL_LENGTH = 8;
public static final int ASSET_ID_LENGTH = LONG_LENGTH;
public static final int AMOUNT_LENGTH = LONG_LENGTH;
// Raw, not Base58-encoded
public static final int ADDRESS_LENGTH = 25;

View File

@ -2,7 +2,6 @@ package org.qortal.transform.block;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
@ -36,7 +35,6 @@ public class BlockTransformer extends Transformer {
private static final int TRANSACTIONS_SIGNATURE_LENGTH = SIGNATURE_LENGTH;
private static final int MINTER_SIGNATURE_LENGTH = SIGNATURE_LENGTH;
private static final int BLOCK_REFERENCE_LENGTH = MINTER_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
private static final int TIMESTAMP_LENGTH = LONG_LENGTH;
private static final int MINTER_PUBLIC_KEY_LENGTH = PUBLIC_KEY_LENGTH;
private static final int TRANSACTION_COUNT_LENGTH = INT_LENGTH;
@ -44,17 +42,19 @@ public class BlockTransformer extends Transformer {
+ TRANSACTIONS_SIGNATURE_LENGTH + MINTER_SIGNATURE_LENGTH + TRANSACTION_COUNT_LENGTH;
public static final int BLOCK_SIGNATURE_LENGTH = MINTER_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
protected static final int TRANSACTION_SIZE_LENGTH = INT_LENGTH; // per transaction
protected static final int AT_BYTES_LENGTH = INT_LENGTH;
protected static final int AT_FEES_LENGTH = LONG_LENGTH;
protected static final int AT_FEES_LENGTH = AMOUNT_LENGTH;
protected static final int AT_LENGTH = AT_FEES_LENGTH + AT_BYTES_LENGTH;
protected static final int ONLINE_ACCOUNTS_COUNT_LENGTH = INT_LENGTH;
protected static final int ONLINE_ACCOUNTS_SIZE_LENGTH = INT_LENGTH;
protected static final int ONLINE_ACCOUNTS_TIMESTAMP_LENGTH = LONG_LENGTH;
protected static final int ONLINE_ACCOUNTS_TIMESTAMP_LENGTH = TIMESTAMP_LENGTH;
protected static final int ONLINE_ACCOUNTS_SIGNATURES_COUNT_LENGTH = INT_LENGTH;
protected static final int AT_ENTRY_LENGTH = ADDRESS_LENGTH + SHA256_LENGTH + BIG_DECIMAL_LENGTH;
protected static final int AT_ENTRY_LENGTH = ADDRESS_LENGTH + SHA256_LENGTH + AMOUNT_LENGTH;
/**
* Extract block data and transaction data from serialized bytes.
@ -104,10 +104,10 @@ public class BlockTransformer extends Transformer {
byte[] minterSignature = new byte[MINTER_SIGNATURE_LENGTH];
byteBuffer.get(minterSignature);
BigDecimal totalFees = BigDecimal.ZERO.setScale(8);
long totalFees = 0;
int atCount = 0;
BigDecimal atFees = BigDecimal.ZERO.setScale(8);
long atFees = 0;
List<ATStateData> atStates = new ArrayList<>();
int atBytesLength = byteBuffer.getInt();
@ -130,9 +130,10 @@ public class BlockTransformer extends Transformer {
byte[] stateHash = new byte[SHA256_LENGTH];
atByteBuffer.get(stateHash);
BigDecimal fees = Serialization.deserializeBigDecimal(atByteBuffer);
long fees = atByteBuffer.getLong();
// Add this AT's fees to our total
atFees = atFees.add(fees);
atFees += fees;
atStates.add(new ATStateData(atAddress, stateHash, fees));
}
@ -141,7 +142,7 @@ public class BlockTransformer extends Transformer {
atCount = atStates.size();
// Add AT fees to totalFees
totalFees = totalFees.add(atFees);
totalFees += atFees;
int transactionCount = byteBuffer.getInt();
@ -166,7 +167,7 @@ public class BlockTransformer extends Transformer {
TransactionData transactionData = TransactionTransformer.fromBytes(transactionBytes);
transactions.add(transactionData);
totalFees = totalFees.add(transactionData.getFee());
totalFees += transactionData.getFee();
}
// Online accounts info?
@ -265,7 +266,7 @@ public class BlockTransformer extends Transformer {
for (ATStateData atStateData : block.getATStates()) {
bytes.write(Base58.decode(atStateData.getATAddress()));
bytes.write(atStateData.getStateHash());
Serialization.serializeBigDecimal(bytes, atStateData.getFees());
bytes.write(Longs.toByteArray(atStateData.getFees()));
}
// Transactions

View File

@ -2,7 +2,6 @@ package org.qortal.transform.transaction;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import org.qortal.data.transaction.AccountFlagsTransactionData;
@ -13,6 +12,7 @@ import org.qortal.transform.TransformationException;
import org.qortal.utils.Serialization;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
public class AccountFlagsTransactionTransformer extends TransactionTransformer {
@ -57,7 +57,7 @@ public class AccountFlagsTransactionTransformer extends TransactionTransformer {
int orMask = byteBuffer.getInt();
int xorMask = byteBuffer.getInt();
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
long fee = byteBuffer.getLong();
byte[] signature = new byte[SIGNATURE_LENGTH];
byteBuffer.get(signature);
@ -85,7 +85,7 @@ public class AccountFlagsTransactionTransformer extends TransactionTransformer {
bytes.write(Ints.toByteArray(accountFlagsTransactionData.getOrMask()));
bytes.write(Ints.toByteArray(accountFlagsTransactionData.getXorMask()));
Serialization.serializeBigDecimal(bytes, accountFlagsTransactionData.getFee());
bytes.write(Longs.toByteArray(accountFlagsTransactionData.getFee()));
if (accountFlagsTransactionData.getSignature() != null)
bytes.write(accountFlagsTransactionData.getSignature());

Some files were not shown because too many files have changed in this diff Show More