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; 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.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
@ -54,32 +54,24 @@ public class Account {
// Balance manipulations - assetId is 0 for QORT // 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); AccountBalanceData accountBalanceData = this.repository.getAccountRepository().getBalance(this.address, assetId);
if (accountBalanceData == null) if (accountBalanceData == null)
return BigDecimal.ZERO.setScale(8); return 0;
return accountBalanceData.getBalance(); return accountBalanceData.getBalance();
} }
public BigDecimal getConfirmedBalance(long assetId) throws DataException { public void setConfirmedBalance(long assetId, long balance) 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 {
// Safety feature! // Safety feature!
if (balance.compareTo(BigDecimal.ZERO) < 0) { if (balance < 0) {
String message = String.format("Refusing to set negative balance %s [assetId %d] for %s", balance.toPlainString(), assetId, this.address); String message = String.format("Refusing to set negative balance %s [assetId %d] for %s", prettyAmount(balance), assetId, this.address);
LOGGER.error(message); LOGGER.error(message);
throw new DataException(message); throw new DataException(message);
} }
// Delete account balance record instead of setting balance to zero // Delete account balance record instead of setting balance to zero
if (balance.signum() == 0) { if (balance == 0) {
this.repository.getAccountRepository().delete(this.address, assetId); this.repository.getAccountRepository().delete(this.address, assetId);
return; return;
} }
@ -90,7 +82,7 @@ public class Account {
AccountBalanceData accountBalanceData = new AccountBalanceData(this.address, assetId, balance); AccountBalanceData accountBalanceData = new AccountBalanceData(this.address, assetId, balance);
this.repository.getAccountRepository().save(accountBalanceData); 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 { 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; package org.qortal.api.model;
import java.math.BigDecimal;
import javax.xml.bind.Marshaller; import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.qortal.data.asset.OrderData; import org.qortal.data.asset.OrderData;
@ -29,12 +28,14 @@ public class AggregatedOrder {
} }
@XmlElement(name = "price") @XmlElement(name = "price")
public BigDecimal getPrice() { @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
public long getPrice() {
return this.orderData.getPrice(); return this.orderData.getPrice();
} }
@XmlElement(name = "unfulfilled") @XmlElement(name = "unfulfilled")
public BigDecimal getUnfulfilled() { @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
public long getUnfulfilled() {
return this.orderData.getAmount(); 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.TransformationException;
import org.qortal.transform.Transformer; import org.qortal.transform.Transformer;
import org.qortal.transform.transaction.RewardShareTransactionTransformer; import org.qortal.transform.transaction.RewardShareTransactionTransformer;
import org.qortal.utils.Amounts;
import org.qortal.utils.Base58; import org.qortal.utils.Base58;
@Path("/addresses") @Path("/addresses")
@ -195,7 +196,7 @@ public class AddressesResource {
else if (!repository.getAssetRepository().assetExists(assetId)) else if (!repository.getAssetRepository().assetExists(assetId))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID); throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
return account.getBalance(assetId); return Amounts.toBigDecimal(account.getConfirmedBalance(assetId));
} catch (ApiException e) { } catch (ApiException e) {
throw e; throw e;
} catch (DataException 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_DESCRIPTION_SIZE = 4000;
public static final int MAX_DATA_SIZE = 400000; 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 // Properties
private Repository repository; private Repository repository;

View File

@ -1,8 +1,7 @@
package org.qortal.asset; package org.qortal.asset;
import java.math.BigDecimal; import static org.qortal.utils.Amounts.prettyAmount;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -14,9 +13,9 @@ import org.qortal.account.PublicKeyAccount;
import org.qortal.data.asset.AssetData; import org.qortal.data.asset.AssetData;
import org.qortal.data.asset.OrderData; import org.qortal.data.asset.OrderData;
import org.qortal.data.asset.TradeData; import org.qortal.data.asset.TradeData;
import org.qortal.repository.AssetRepository;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.utils.Amounts;
import org.qortal.utils.Base58; import org.qortal.utils.Base58;
public class Order { public class Order {
@ -57,16 +56,16 @@ public class Order {
// More information // More information
public static BigDecimal getAmountLeft(OrderData orderData) { public static long getAmountLeft(OrderData orderData) {
return orderData.getAmount().subtract(orderData.getFulfilled()); return orderData.getAmount() - orderData.getFulfilled();
} }
public BigDecimal getAmountLeft() { public long getAmountLeft() {
return Order.getAmountLeft(this.orderData); return Order.getAmountLeft(this.orderData);
} }
public static boolean isFulfilled(OrderData orderData) { public static boolean isFulfilled(OrderData orderData) {
return orderData.getFulfilled().compareTo(orderData.getAmount()) == 0; return orderData.getFulfilled() == orderData.getAmount();
} }
public boolean isFulfilled() { public boolean isFulfilled() {
@ -83,31 +82,28 @@ public class Order {
* <p> * <p>
* @return granularity of matched-amount * @return granularity of matched-amount
*/ */
public static BigDecimal calculateAmountGranularity(boolean isAmountAssetDivisible, boolean isReturnAssetDivisible, BigDecimal price) { public static long calculateAmountGranularity(boolean isAmountAssetDivisible, boolean isReturnAssetDivisible, long price) {
// Multiplier to scale BigDecimal fractional amounts into integer domain
BigInteger multiplier = BigInteger.valueOf(1_0000_0000L);
// Calculate the minimum increment for matched-amount using greatest-common-divisor // Calculate the minimum increment for matched-amount using greatest-common-divisor
BigInteger returnAmount = multiplier; // 1 unit (* multiplier) long returnAmount = Asset.MULTIPLIER; // 1 unit * multiplier
BigInteger matchedAmount = price.movePointRight(8).toBigInteger(); long matchedAmount = price;
BigInteger gcd = returnAmount.gcd(matchedAmount); long gcd = Amounts.greatestCommonDivisor(returnAmount, matchedAmount);
returnAmount = returnAmount.divide(gcd); returnAmount /= gcd;
matchedAmount = matchedAmount.divide(gcd); matchedAmount /= gcd;
// Calculate GCD in combination with divisibility // Calculate GCD in combination with divisibility
if (isAmountAssetDivisible) if (isAmountAssetDivisible)
returnAmount = returnAmount.multiply(multiplier); returnAmount *= Asset.MULTIPLIER;
if (isReturnAssetDivisible) 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 // Calculate the granularity at which we have to buy
BigDecimal granularity = new BigDecimal(returnAmount.divide(gcd)); long granularity = returnAmount / gcd;
if (isAmountAssetDivisible) if (isAmountAssetDivisible)
granularity = granularity.movePointLeft(8); granularity /= Asset.MULTIPLIER;
// Return // Return
return granularity; 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. */ /** Returns amount of have-asset to remove from order's creator's balance on placing this order. */
private BigDecimal calcHaveAssetCommittment() { private long calcHaveAssetCommittment() {
BigDecimal committedCost = this.orderData.getAmount(); long committedCost = this.orderData.getAmount();
// If "amount" is in want-asset then we need to convert // If "amount" is in want-asset then we need to convert
if (haveAssetId < wantAssetId) 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; return committedCost;
} }
/** Returns amount of remaining have-asset to refund to order's creator's balance on cancelling this order. */ /** Returns amount of remaining have-asset to refund to order's creator's balance on cancelling this order. */
private BigDecimal calcHaveAssetRefund() { private long calcHaveAssetRefund() {
BigDecimal refund = getAmountLeft(); long refund = getAmountLeft();
// If "amount" is in want-asset then we need to convert // If "amount" is in want-asset then we need to convert
if (haveAssetId < wantAssetId) 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; return refund;
} }
@ -229,37 +225,30 @@ public class Order {
final AssetData amountAssetData = this.repository.getAssetRepository().fromAssetId(amountAssetId); final AssetData amountAssetData = this.repository.getAssetRepository().fromAssetId(amountAssetId);
final AssetData returnAssetData = this.repository.getAssetRepository().fromAssetId(returnAssetId); 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, LOGGER.trace(() -> String.format("%s amount: %s (ordered) - %s (fulfilled) = %s %s left", ourTheir,
orderData.getAmount().stripTrailingZeros().toPlainString(), prettyAmount(orderData.getAmount()),
orderData.getFulfilled().stripTrailingZeros().toPlainString(), prettyAmount(orderData.getFulfilled()),
Order.getAmountLeft(orderData).stripTrailingZeros().toPlainString(), prettyAmount(Order.getAmountLeft(orderData)),
amountAssetData.getName())); 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, LOGGER.trace(() -> String.format("%s price: %s %s (%s %s tradable)", ourTheir,
orderData.getPrice().toPlainString(), getPricePair(), prettyAmount(orderData.getPrice()),
maxReturnAmount.stripTrailingZeros().toPlainString(), returnAssetData.getName())); pricePair,
prettyAmount(maxReturnAmount),
returnAssetData.getName()));
} }
public void process() throws DataException { 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 // Subtract have-asset from creator
Account creator = new PublicKeyAccount(this.repository, this.orderData.getCreatorPublicKey()); 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 // Save this order into repository so it's available for matching, possibly by itself
this.repository.getAssetRepository().save(this.orderData); 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. // Fetch corresponding open orders that might potentially match, hence reversed want/have assetIDs.
// Returned orders are sorted with lowest "price" first. // Returned orders are sorted with lowest "price" first.
List<OrderData> orders = assetRepository.getOpenOrdersForTrading(wantAssetId, haveAssetId, this.orderData.getPrice()); List<OrderData> orders = this.repository.getAssetRepository().getOpenOrdersForTrading(wantAssetId, haveAssetId, this.orderData.getPrice());
LOGGER.trace("Open orders fetched from repository: " + orders.size()); LOGGER.trace(() -> String.format("Open orders fetched from repository: %d", orders.size()));
if (orders.isEmpty()) if (orders.isEmpty())
return; 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 // 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. * 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) { for (OrderData theirOrderData : orders) {
logOrder("Considering order", false, theirOrderData); logOrder("Considering order", false, theirOrderData);
// Determine their order price // Determine their order price
BigDecimal theirPrice; long theirPrice = theirOrderData.getPrice();
LOGGER.trace(() -> String.format("Their price: %s %s", prettyAmount(theirPrice), pricePair));
// 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()));
// 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 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) if ((haveAssetId < wantAssetId && theirPrice > ourPrice) || (haveAssetId > wantAssetId && theirPrice < ourPrice))
break;
if (haveAssetId > wantAssetId && theirPrice.compareTo(ourPrice) < 0)
break; break;
// Calculate how much we could buy at their price, "amount" is expressed in terms of asset with highest assetID. // Calculate how much we could buy at their price, "amount" is expressed in terms of asset with highest assetID.
BigDecimal ourMaxAmount = this.getAmountLeft(); long ourMaxAmount = this.getAmountLeft();
LOGGER.trace("ourMaxAmount (max we could trade at their price): " + ourMaxAmount.stripTrailingZeros().toPlainString() + " " + matchingAssetData.getName()); 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. // How much is remaining available in their order.
BigDecimal theirAmountLeft = Order.getAmountLeft(theirOrderData); long theirAmountLeft = Order.getAmountLeft(theirOrderData);
LOGGER.trace("theirAmountLeft (max amount remaining in their order): " + theirAmountLeft.stripTrailingZeros().toPlainString() + " " + matchingAssetData.getName()); 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 // So matchable want-asset amount is the minimum of above two values
BigDecimal matchedAmount = ourMaxAmount.min(theirAmountLeft); long interimMatchedAmount = Math.min(ourMaxAmount, theirAmountLeft);
LOGGER.trace("matchedAmount: " + matchedAmount.stripTrailingZeros().toPlainString() + " " + matchingAssetData.getName()); LOGGER.trace(() -> String.format("matchedAmount: %s %s", prettyAmount(interimMatchedAmount), matchingAssetData.getName()));
// If we can't buy anything then try another order // If we can't buy anything then try another order
if (matchedAmount.compareTo(BigDecimal.ZERO) <= 0) if (interimMatchedAmount <= 0)
continue; 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.) // 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()); long granularity = calculateAmountGranularity(matchingAssetData.getIsDivisible(), returnAssetData.getIsDivisible(), theirOrderData.getPrice());
LOGGER.trace("granularity (amount granularity): " + granularity.stripTrailingZeros().toPlainString() + " " + matchingAssetData.getName()); LOGGER.trace(() -> String.format("granularity (amount granularity): %s %s", prettyAmount(granularity), matchingAssetData.getName()));
// Reduce matched amount (if need be) to fit granularity // Reduce matched amount (if need be) to fit granularity
matchedAmount = matchedAmount.subtract(matchedAmount.remainder(granularity)); long matchedAmount = interimMatchedAmount - interimMatchedAmount % granularity;
LOGGER.trace("matchedAmount adjusted for granularity: " + matchedAmount.stripTrailingZeros().toPlainString() + " " + matchingAssetData.getName()); 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 we can't buy anything then try another order
if (matchedAmount.compareTo(BigDecimal.ZERO) <= 0) if (matchedAmount <= 0)
continue; continue;
// Safety check // Safety check
if (!matchingAssetData.getIsDivisible() && matchedAmount.stripTrailingZeros().scale() > 0) { checkDivisibility(matchingAssetData, matchedAmount, theirOrderData);
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);
}
// Trade can go ahead! // Trade can go ahead!
// Calculate the total cost to us, in return-asset, based on their price // Calculate the total cost to us, in return-asset, based on their price
BigDecimal returnAmountTraded = matchedAmount.multiply(theirOrderData.getPrice()).setScale(8, RoundingMode.DOWN); long returnAmountTraded = matchedAmount * theirOrderData.getPrice();
LOGGER.trace("returnAmountTraded: " + returnAmountTraded.stripTrailingZeros().toPlainString() + " " + returnAssetData.getName()); LOGGER.trace(() -> String.format("returnAmountTraded: %s %s", prettyAmount(returnAmountTraded), returnAssetData.getName()));
// Safety check // Safety check
if (!returnAssetData.getIsDivisible() && returnAmountTraded.stripTrailingZeros().scale() > 0) { checkDivisibility(returnAssetData, returnAmountTraded, this.orderData);
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);
}
BigDecimal tradedWantAmount = (haveAssetId > wantAssetId) ? returnAmountTraded : matchedAmount; long tradedWantAmount = (haveAssetId > wantAssetId) ? returnAmountTraded : matchedAmount;
BigDecimal tradedHaveAmount = (haveAssetId > wantAssetId) ? matchedAmount : returnAmountTraded; 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) // 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)", LOGGER.trace(() -> String.format("We traded %s %s (have-asset) for %s %s (want-asset), saving %s %s (have-asset)",
tradedHaveAmount.toPlainString(), haveAssetData.getName(), prettyAmount(tradedHaveAmount), haveAssetData.getName(),
tradedWantAmount.toPlainString(), wantAssetData.getName(), prettyAmount(tradedWantAmount), wantAssetData.getName(),
haveAssetRefund.toPlainString(), haveAssetData.getName())); prettyAmount(haveAssetRefund), haveAssetData.getName()));
// Construct trade // Construct trade
TradeData tradeData = new TradeData(this.orderData.getOrderId(), theirOrderData.getOrderId(), TradeData tradeData = new TradeData(this.orderData.getOrderId(), theirOrderData.getOrderId(),
@ -384,17 +369,33 @@ public class Order {
trade.process(); trade.process();
// Update our order in terms of fulfilment, etc. but do not save into repository as that's handled by Trade above // Update our order in terms of fulfilment, etc. but do not save into repository as that's handled by Trade above
BigDecimal amountFulfilled = matchedAmount; long amountFulfilled = matchedAmount;
this.orderData.setFulfilled(this.orderData.getFulfilled().add(amountFulfilled)); this.orderData.setFulfilled(this.orderData.getFulfilled() + amountFulfilled);
LOGGER.trace("Updated our order's fulfilled amount to: " + this.orderData.getFulfilled().stripTrailingZeros().toPlainString() + " " + matchingAssetData.getName()); LOGGER.trace(() -> String.format("Updated our order's fulfilled amount to: %s %s", prettyAmount(this.orderData.getFulfilled()), matchingAssetData.getName()));
LOGGER.trace("Our order's amount remaining: " + this.getAmountLeft().stripTrailingZeros().toPlainString() + " " + 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 // 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; 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 { public void orphan() throws DataException {
// Orphan trades that occurred as a result of this order // Orphan trades that occurred as a result of this order
for (TradeData tradeData : getTrades()) for (TradeData tradeData : getTrades())
@ -408,7 +409,7 @@ public class Order {
// Return asset to creator // Return asset to creator
Account creator = new PublicKeyAccount(this.repository, this.orderData.getCreatorPublicKey()); 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 // 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 // Update creator's balance with unfulfilled amount
Account creator = new PublicKeyAccount(this.repository, this.orderData.getCreatorPublicKey()); 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 // Opposite of cancel() above for use during orphaning
public void reopen() throws DataException { public void reopen() throws DataException {
// Update creator's balance with unfulfilled amount // Update creator's balance with unfulfilled amount
Account creator = new PublicKeyAccount(this.repository, this.orderData.getCreatorPublicKey()); 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.orderData.setIsClosed(false);
this.repository.getAssetRepository().save(this.orderData); this.repository.getAssetRepository().save(this.orderData);

View File

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

View File

@ -1,6 +1,5 @@
package org.qortal.at; package org.qortal.at;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
import org.ciyam.at.MachineState; import org.ciyam.at.MachineState;
@ -51,7 +50,7 @@ public class AT {
byte[] stateData = machineState.toBytes(); byte[] stateData = machineState.toBytes();
byte[] stateHash = Crypto.digest(stateData); 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 // Getters / setters
@ -97,7 +96,7 @@ public class AT {
long creation = this.atData.getCreation(); long creation = this.atData.getCreation();
byte[] stateData = state.toBytes(); byte[] stateData = state.toBytes();
byte[] stateHash = Crypto.digest(stateData); 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); 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.Arrays.stream;
import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toMap;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -85,16 +84,16 @@ public enum BlockchainAPI {
switch (transactionData.getType()) { switch (transactionData.getType()) {
case PAYMENT: case PAYMENT:
return ((PaymentTransactionData) transactionData).getAmount().unscaledValue().longValue(); return ((PaymentTransactionData) transactionData).getAmount();
case AT: case AT:
BigDecimal amount = ((ATTransactionData) transactionData).getAmount(); Long amount = ((ATTransactionData) transactionData).getAmount();
if (amount != null) if (amount == null)
return amount.unscaledValue().longValue();
else
return 0xffffffffffffffffL; return 0xffffffffffffffffL;
return amount;
default: default:
return 0xffffffffffffffffL; return 0xffffffffffffffffL;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,7 +23,11 @@ public class RewardShareData {
private String recipient; private String recipient;
private byte[] rewardSharePublicKey; private byte[] rewardSharePublicKey;
private BigDecimal sharePercent;
// JAXB to use separate getter
@XmlTransient
@Schema(hidden = true)
private int sharePercent;
// Constructors // Constructors
@ -32,7 +36,7 @@ public class RewardShareData {
} }
// Used when fetching from repository // 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.minterPublicKey = minterPublicKey;
this.minter = minter; this.minter = minter;
this.recipient = recipient; this.recipient = recipient;
@ -58,13 +62,21 @@ public class RewardShareData {
return this.rewardSharePublicKey; 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; return this.sharePercent;
} }
// Some JAXB/API-related getters
@XmlElement(name = "mintingAccount") @XmlElement(name = "mintingAccount")
public String getMintingAccount() { public String getMintingAccount() {
return this.minter; 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 // Constructors
// necessary for JAX-RS serialization // necessary for JAXB serialization
protected AssetData() { protected AssetData() {
} }

View File

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

View File

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

View File

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

View File

@ -1,7 +1,5 @@
package org.qortal.data.at; package org.qortal.data.at;
import java.math.BigDecimal;
public class ATStateData { public class ATStateData {
// Properties // Properties
@ -10,12 +8,12 @@ public class ATStateData {
private Long creation; private Long creation;
private byte[] stateData; private byte[] stateData;
private byte[] stateHash; private byte[] stateHash;
private BigDecimal fees; private Long fees;
// Constructors // Constructors
/** Create new ATStateData */ /** 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.ATAddress = ATAddress;
this.height = height; this.height = height;
this.creation = creation; this.creation = creation;
@ -25,7 +23,7 @@ public class ATStateData {
} }
/** For recreating per-block ATStateData from repository where not all info is needed */ /** 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); 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 */ /** 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); this(ATAddress, null, null, null, stateHash, fees);
} }
@ -66,7 +64,7 @@ public class ATStateData {
return this.stateHash; return this.stateHash;
} }
public BigDecimal getFees() { public Long getFees() {
return this.fees; return this.fees;
} }

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import java.math.BigDecimal;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlTransient;
import org.qortal.account.NullAccount; import org.qortal.account.NullAccount;
import org.qortal.transaction.Transaction.TransactionType; import org.qortal.transaction.Transaction.TransactionType;
@ -17,10 +18,19 @@ import io.swagger.v3.oas.annotations.media.Schema;
public class ATTransactionData extends TransactionData { public class ATTransactionData extends TransactionData {
// Properties // Properties
private String atAddress; private String atAddress;
private String recipient; private String recipient;
private BigDecimal amount;
@XmlTransient
@Schema(hidden = true)
// Not always present
private Long amount;
// Not always present
private Long assetId; private Long assetId;
private byte[] message; private byte[] message;
// Constructors // Constructors
@ -35,7 +45,7 @@ public class ATTransactionData extends TransactionData {
} }
/** From repository */ /** 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); super(TransactionType.AT, baseTransactionData);
this.creatorPublicKey = NullAccount.PUBLIC_KEY; this.creatorPublicKey = NullAccount.PUBLIC_KEY;
@ -56,7 +66,7 @@ public class ATTransactionData extends TransactionData {
return this.recipient; return this.recipient;
} }
public BigDecimal getAmount() { public Long getAmount() {
return this.amount; return this.amount;
} }
@ -68,4 +78,14 @@ public class ATTransactionData extends TransactionData {
return this.message; 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; package org.qortal.data.transaction;
import java.math.BigDecimal;
import org.qortal.transaction.Transaction.ApprovalStatus; import org.qortal.transaction.Transaction.ApprovalStatus;
public class BaseTransactionData extends TransactionData { public class BaseTransactionData extends TransactionData {
/** Constructor for use by repository. */ /** 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) { ApprovalStatus approvalStatus, Integer blockHeight, Integer approvalHeight, byte[] signature) {
this.timestamp = timestamp; this.timestamp = timestamp;
this.txGroupId = txGroupId; this.txGroupId = txGroupId;
@ -21,7 +19,7 @@ public class BaseTransactionData extends TransactionData {
} }
/** Constructor for use by transaction transformer. */ /** 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); this(timestamp, txGroupId, reference, creatorPublicKey, fee, null, null, null, signature);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import java.math.BigDecimal;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlTransient;
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
@ -27,13 +28,15 @@ public class RewardShareTransactionData extends TransactionData {
@Schema(example = "reward_share_public_key") @Schema(example = "reward_share_public_key")
private byte[] rewardSharePublicKey; private byte[] rewardSharePublicKey;
@Schema(description = "Percentage of block rewards that minter shares to recipient, or negative value to cancel existing reward-share") // JAXB will use special getter below
private BigDecimal sharePercent; @XmlTransient
@Schema(hidden = true)
private int sharePercent;
// No need to ever expose this via API // No need to ever expose this via API
@XmlTransient @XmlTransient
@Schema(hidden = true) @Schema(hidden = true)
private BigDecimal previousSharePercent; private Integer previousSharePercent;
// Constructors // Constructors
@ -48,7 +51,7 @@ public class RewardShareTransactionData extends TransactionData {
/** From repository */ /** From repository */
public RewardShareTransactionData(BaseTransactionData baseTransactionData, 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); super(TransactionType.REWARD_SHARE, baseTransactionData);
this.minterPublicKey = baseTransactionData.creatorPublicKey; this.minterPublicKey = baseTransactionData.creatorPublicKey;
@ -60,7 +63,7 @@ public class RewardShareTransactionData extends TransactionData {
/** From network/API */ /** From network/API */
public RewardShareTransactionData(BaseTransactionData baseTransactionData, public RewardShareTransactionData(BaseTransactionData baseTransactionData,
String recipient, byte[] rewardSharePublicKey, BigDecimal sharePercent) { String recipient, byte[] rewardSharePublicKey, int sharePercent) {
this(baseTransactionData, recipient, rewardSharePublicKey, sharePercent, null); this(baseTransactionData, recipient, rewardSharePublicKey, sharePercent, null);
} }
@ -78,16 +81,24 @@ public class RewardShareTransactionData extends TransactionData {
return this.rewardSharePublicKey; return this.rewardSharePublicKey;
} }
public BigDecimal getSharePercent() { public int getSharePercent() {
return this.sharePercent; return this.sharePercent;
} }
public BigDecimal getPreviousSharePercent() { public Integer getPreviousSharePercent() {
return this.previousSharePercent; return this.previousSharePercent;
} }
public void setPreviousSharePercent(BigDecimal previousSharePercent) { public void setPreviousSharePercent(Integer previousSharePercent) {
this.previousSharePercent = 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; package org.qortal.data.transaction;
import java.math.BigDecimal;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
@ -17,16 +15,16 @@ import io.swagger.v3.oas.annotations.media.Schema;
public class SellNameTransactionData extends TransactionData { public class SellNameTransactionData extends TransactionData {
// Properties // Properties
@Schema(description = "owner's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP") @Schema(description = "owner's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
private byte[] ownerPublicKey; private byte[] ownerPublicKey;
@Schema(description = "which name to sell", example = "my-name") @Schema(description = "which name to sell", example = "my-name")
private String name; private String name;
@Schema(description = "selling price", example = "123.456") @Schema(description = "selling price", example = "123.456")
@XmlJavaTypeAdapter( @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
type = BigDecimal.class, private long amount;
value = org.qortal.api.BigDecimalTypeAdapter.class
)
private BigDecimal amount;
// Constructors // Constructors
@ -39,7 +37,7 @@ public class SellNameTransactionData extends TransactionData {
this.creatorPublicKey = this.ownerPublicKey; 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); super(TransactionType.SELL_NAME, baseTransactionData);
this.ownerPublicKey = baseTransactionData.creatorPublicKey; this.ownerPublicKey = baseTransactionData.creatorPublicKey;
@ -57,7 +55,7 @@ public class SellNameTransactionData extends TransactionData {
return this.name; return this.name;
} }
public BigDecimal getAmount() { public long getAmount() {
return this.amount; return this.amount;
} }

View File

@ -58,8 +58,9 @@ public abstract class TransactionData {
protected long timestamp; protected long timestamp;
@Schema(description = "sender's last transaction ID", example = "real_transaction_reference_in_base58") @Schema(description = "sender's last transaction ID", example = "real_transaction_reference_in_base58")
protected byte[] reference; protected byte[] reference;
@Schema(description = "fee for processing transaction", example = "1.0") @XmlTransient
protected BigDecimal fee; @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") @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; protected byte[] signature;
@Schema(description = "groupID for this transaction") @Schema(description = "groupID for this transaction")
@ -138,11 +139,11 @@ public abstract class TransactionData {
this.creatorPublicKey = creatorPublicKey; this.creatorPublicKey = creatorPublicKey;
} }
public BigDecimal getFee() { public Long getFee() {
return this.fee; return this.fee;
} }
public void setFee(BigDecimal fee) { public void setFee(Long fee) {
this.fee = fee; this.fee = fee;
} }
@ -183,6 +184,14 @@ public abstract class TransactionData {
// JAXB special // 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") @XmlElement(name = "creatorAddress")
protected String getCreatorAddress() { protected String getCreatorAddress() {
return Crypto.toAddress(this.creatorPublicKey); return Crypto.toAddress(this.creatorPublicKey);

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
package org.qortal.repository; package org.qortal.repository;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
import org.qortal.data.account.AccountBalanceData; 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 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; public void save(AccountBalanceData accountBalanceData) throws DataException;

View File

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

View File

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

View File

@ -1,11 +1,11 @@
package org.qortal.repository.hsqldb; 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.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -171,8 +171,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
if (assetData.getAssetId() == null) { if (assetData.getAssetId() == null) {
// Fetch new assetId // Fetch new assetId
try (ResultSet resultSet = this.repository try (ResultSet resultSet = this.repository.checkedExecute("SELECT asset_id FROM Assets WHERE reference = ?", assetData.getReference())) {
.checkedExecute("SELECT asset_id FROM Assets WHERE reference = ?", assetData.getReference())) {
if (resultSet == null) if (resultSet == null)
throw new DataException("Unable to fetch new asset ID from repository"); throw new DataException("Unable to fetch new asset ID from repository");
@ -213,10 +212,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
byte[] creatorPublicKey = resultSet.getBytes(1); byte[] creatorPublicKey = resultSet.getBytes(1);
long haveAssetId = resultSet.getLong(2); long haveAssetId = resultSet.getLong(2);
long wantAssetId = resultSet.getLong(3); long wantAssetId = resultSet.getLong(3);
BigDecimal amount = resultSet.getBigDecimal(4); long amount = resultSet.getLong(4);
BigDecimal fulfilled = resultSet.getBigDecimal(5); long fulfilled = resultSet.getLong(5);
BigDecimal price = resultSet.getBigDecimal(6); long price = resultSet.getLong(6);
long timestamp = resultSet.getTimestamp(7, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); long timestamp = getZonedTimestampMilli(resultSet, 7);
boolean isClosed = resultSet.getBoolean(8); boolean isClosed = resultSet.getBoolean(8);
boolean isFulfilled = resultSet.getBoolean(9); boolean isFulfilled = resultSet.getBoolean(9);
String haveAssetName = resultSet.getString(10); String haveAssetName = resultSet.getString(10);
@ -263,10 +262,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
do { do {
byte[] creatorPublicKey = resultSet.getBytes(1); byte[] creatorPublicKey = resultSet.getBytes(1);
byte[] orderId = resultSet.getBytes(2); byte[] orderId = resultSet.getBytes(2);
BigDecimal amount = resultSet.getBigDecimal(3); long amount = resultSet.getLong(3);
BigDecimal fulfilled = resultSet.getBigDecimal(4); long fulfilled = resultSet.getLong(4);
BigDecimal price = resultSet.getBigDecimal(5); long price = resultSet.getLong(5);
long timestamp = resultSet.getTimestamp(6, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); long timestamp = getZonedTimestampMilli(resultSet, 6);
boolean isClosed = false; boolean isClosed = false;
boolean isFulfilled = false; boolean isFulfilled = false;
@ -282,7 +281,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
} }
@Override @Override
public List<OrderData> getOpenOrdersForTrading(long haveAssetId, long wantAssetId, BigDecimal minimumPrice) throws DataException { public List<OrderData> getOpenOrdersForTrading(long haveAssetId, long wantAssetId, Long minimumPrice) throws DataException {
List<Object> bindParams = new ArrayList<>(3); List<Object> bindParams = new ArrayList<>(3);
StringBuilder sql = new StringBuilder(512); StringBuilder sql = new StringBuilder(512);
@ -317,10 +316,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
do { do {
byte[] creatorPublicKey = resultSet.getBytes(1); byte[] creatorPublicKey = resultSet.getBytes(1);
byte[] orderId = resultSet.getBytes(2); byte[] orderId = resultSet.getBytes(2);
BigDecimal amount = resultSet.getBigDecimal(3); long amount = resultSet.getLong(3);
BigDecimal fulfilled = resultSet.getBigDecimal(4); long fulfilled = resultSet.getLong(4);
BigDecimal price = resultSet.getBigDecimal(5); long price = resultSet.getLong(5);
long timestamp = resultSet.getTimestamp(6, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); long timestamp = getZonedTimestampMilli(resultSet, 6);
boolean isClosed = false; boolean isClosed = false;
boolean isFulfilled = false; boolean isFulfilled = false;
@ -366,11 +365,11 @@ public class HSQLDBAssetRepository implements AssetRepository {
return orders; return orders;
do { do {
BigDecimal price = resultSet.getBigDecimal(1); long price = resultSet.getLong(1);
BigDecimal totalUnfulfilled = resultSet.getBigDecimal(2); long totalUnfulfilled = resultSet.getLong(2);
long timestamp = resultSet.getTimestamp(3).getTime(); long timestamp = resultSet.getTimestamp(3).getTime();
OrderData order = new OrderData(null, null, haveAssetId, wantAssetId, totalUnfulfilled, BigDecimal.ZERO, OrderData order = new OrderData(null, null, haveAssetId, wantAssetId, totalUnfulfilled, 0L,
price, timestamp, false, false, haveAssetData.getName(), wantAssetData.getName()); price, timestamp, false, false, haveAssetData.getName(), wantAssetData.getName());
orders.add(order); orders.add(order);
} while (resultSet.next()); } while (resultSet.next());
@ -417,10 +416,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
byte[] orderId = resultSet.getBytes(1); byte[] orderId = resultSet.getBytes(1);
long haveAssetId = resultSet.getLong(2); long haveAssetId = resultSet.getLong(2);
long wantAssetId = resultSet.getLong(3); long wantAssetId = resultSet.getLong(3);
BigDecimal amount = resultSet.getBigDecimal(4); long amount = resultSet.getLong(4);
BigDecimal fulfilled = resultSet.getBigDecimal(5); long fulfilled = resultSet.getLong(5);
BigDecimal price = resultSet.getBigDecimal(6); long price = resultSet.getLong(6);
long timestamp = resultSet.getTimestamp(7, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); long timestamp = getZonedTimestampMilli(resultSet, 7);
boolean isClosed = resultSet.getBoolean(8); boolean isClosed = resultSet.getBoolean(8);
boolean isFulfilled = resultSet.getBoolean(9); boolean isFulfilled = resultSet.getBoolean(9);
String haveAssetName = resultSet.getString(10); String haveAssetName = resultSet.getString(10);
@ -478,10 +477,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
do { do {
byte[] orderId = resultSet.getBytes(1); byte[] orderId = resultSet.getBytes(1);
BigDecimal amount = resultSet.getBigDecimal(2); long amount = resultSet.getLong(2);
BigDecimal fulfilled = resultSet.getBigDecimal(3); long fulfilled = resultSet.getLong(3);
BigDecimal price = resultSet.getBigDecimal(4); long price = resultSet.getLong(4);
long timestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); long timestamp = getZonedTimestampMilli(resultSet, 5);
boolean isClosed = resultSet.getBoolean(6); boolean isClosed = resultSet.getBoolean(6);
boolean isFulfilled = resultSet.getBoolean(7); 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()) saveHelper.bind("asset_order_id", orderData.getOrderId()).bind("creator", orderData.getCreatorPublicKey())
.bind("have_asset_id", orderData.getHaveAssetId()).bind("want_asset_id", orderData.getWantAssetId()) .bind("have_asset_id", orderData.getHaveAssetId()).bind("want_asset_id", orderData.getWantAssetId())
.bind("amount", orderData.getAmount()).bind("fulfilled", orderData.getFulfilled()) .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()); .bind("is_closed", orderData.getIsClosed()).bind("is_fulfilled", orderData.getIsFulfilled());
try { try {
@ -556,10 +555,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
do { do {
byte[] initiatingOrderId = resultSet.getBytes(1); byte[] initiatingOrderId = resultSet.getBytes(1);
byte[] targetOrderId = resultSet.getBytes(2); byte[] targetOrderId = resultSet.getBytes(2);
BigDecimal targetAmount = resultSet.getBigDecimal(3); long targetAmount = resultSet.getLong(3);
BigDecimal initiatorAmount = resultSet.getBigDecimal(4); long initiatorAmount = resultSet.getLong(4);
BigDecimal initiatorSaving = resultSet.getBigDecimal(5); long initiatorSaving = resultSet.getLong(5);
long timestamp = resultSet.getTimestamp(6, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); long timestamp = getZonedTimestampMilli(resultSet, 6);
TradeData trade = new TradeData(initiatingOrderId, targetOrderId, targetAmount, initiatorAmount, initiatorSaving, TradeData trade = new TradeData(initiatingOrderId, targetOrderId, targetAmount, initiatorAmount, initiatorSaving,
timestamp, haveAssetId, haveAssetData.getName(), wantAssetId, wantAssetData.getName()); timestamp, haveAssetId, haveAssetData.getName(), wantAssetId, wantAssetData.getName());
@ -648,9 +647,9 @@ public class HSQLDBAssetRepository implements AssetRepository {
do { do {
long haveAssetId = resultSet.getLong(1); long haveAssetId = resultSet.getLong(1);
long wantAssetId = resultSet.getLong(2); long wantAssetId = resultSet.getLong(2);
BigDecimal otherAmount = resultSet.getBigDecimal(3); long otherAmount = resultSet.getLong(3);
BigDecimal amount = resultSet.getBigDecimal(4); long amount = resultSet.getLong(4);
long timestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); long timestamp = getZonedTimestampMilli(resultSet, 5);
RecentTradeData recentTrade = new RecentTradeData(haveAssetId, wantAssetId, otherAmount, amount, RecentTradeData recentTrade = new RecentTradeData(haveAssetId, wantAssetId, otherAmount, amount,
timestamp); timestamp);
@ -689,10 +688,10 @@ public class HSQLDBAssetRepository implements AssetRepository {
do { do {
byte[] initiatingOrderId = resultSet.getBytes(1); byte[] initiatingOrderId = resultSet.getBytes(1);
byte[] targetOrderId = resultSet.getBytes(2); byte[] targetOrderId = resultSet.getBytes(2);
BigDecimal targetAmount = resultSet.getBigDecimal(3); long targetAmount = resultSet.getLong(3);
BigDecimal initiatorAmount = resultSet.getBigDecimal(4); long initiatorAmount = resultSet.getLong(4);
BigDecimal initiatorSaving = resultSet.getBigDecimal(5); long initiatorSaving = resultSet.getLong(5);
long timestamp = resultSet.getTimestamp(6, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); long timestamp = getZonedTimestampMilli(resultSet, 6);
long haveAssetId = resultSet.getLong(7); long haveAssetId = resultSet.getLong(7);
String haveAssetName = resultSet.getString(8); 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()) saveHelper.bind("initiating_order_id", tradeData.getInitiator()).bind("target_order_id", tradeData.getTarget())
.bind("target_amount", tradeData.getTargetAmount()).bind("initiator_amount", tradeData.getInitiatorAmount()) .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 { try {
saveHelper.execute(this.repository); 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.getZonedTimestampMilli;
import static org.qortal.repository.hsqldb.HSQLDBRepository.toOffsetDateTime; import static org.qortal.repository.hsqldb.HSQLDBRepository.toOffsetDateTime;
import java.math.BigDecimal;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
@ -39,14 +38,14 @@ public class HSQLDBBlockRepository implements BlockRepository {
int version = resultSet.getInt(1); int version = resultSet.getInt(1);
byte[] reference = resultSet.getBytes(2); byte[] reference = resultSet.getBytes(2);
int transactionCount = resultSet.getInt(3); int transactionCount = resultSet.getInt(3);
BigDecimal totalFees = resultSet.getBigDecimal(4); long totalFees = resultSet.getLong(4);
byte[] transactionsSignature = resultSet.getBytes(5); byte[] transactionsSignature = resultSet.getBytes(5);
int height = resultSet.getInt(6); int height = resultSet.getInt(6);
long timestamp = getZonedTimestampMilli(resultSet, 7); long timestamp = getZonedTimestampMilli(resultSet, 7);
byte[] minterPublicKey = resultSet.getBytes(8); byte[] minterPublicKey = resultSet.getBytes(8);
byte[] minterSignature = resultSet.getBytes(9); byte[] minterSignature = resultSet.getBytes(9);
int atCount = resultSet.getInt(10); int atCount = resultSet.getInt(10);
BigDecimal atFees = resultSet.getBigDecimal(11); long atFees = resultSet.getLong(11);
byte[] encodedOnlineAccounts = resultSet.getBytes(12); byte[] encodedOnlineAccounts = resultSet.getBytes(12);
int onlineAccountsCount = resultSet.getInt(13); int onlineAccountsCount = resultSet.getInt(13);
Long onlineAccountsTimestamp = getZonedTimestampMilli(resultSet, 14); 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 Signature AS VARBINARY(64)");
stmt.execute("CREATE TYPE QortalAddress AS VARCHAR(36)"); stmt.execute("CREATE TYPE QortalAddress AS VARCHAR(36)");
stmt.execute("CREATE TYPE QortalPublicKey AS VARBINARY(32)"); 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 GenericDescription AS VARCHAR(4000)");
stmt.execute("CREATE TYPE RegisteredName AS VARCHAR(128) COLLATE SQL_TEXT_NO_PAD"); stmt.execute("CREATE TYPE RegisteredName AS VARCHAR(128) COLLATE SQL_TEXT_NO_PAD");
stmt.execute("CREATE TYPE NameData AS VARCHAR(4000)"); 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 GroupID AS INTEGER");
stmt.execute("CREATE TYPE GroupName AS VARCHAR(400) COLLATE SQL_TEXT_UCC_NO_PAD"); 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 GroupReason AS VARCHAR(128) COLLATE SQL_TEXT_UCC_NO_PAD");
stmt.execute("CREATE TYPE RewardSharePercent AS INT");
break; break;
case 1: case 1:
@ -713,10 +714,10 @@ public class HSQLDBDatabaseUpdates {
case 46: case 46:
// Proxy forging // Proxy forging
// Transaction emitted by forger announcing they are forging on behalf of recipient // 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, " 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 DECIMAL(5,2), PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "previous_share RewardSharePercent, PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
// Table of current shares // 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))"); + "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 // 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)"); stmt.execute("CREATE INDEX ProxyForgersProxyPublicKeyIndex ON ProxyForgers (proxy_public_key)");

View File

@ -1,6 +1,8 @@
package org.qortal.repository.hsqldb; 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.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
@ -38,7 +40,11 @@ public class HSQLDBNameRepository implements NameRepository {
byte[] reference = resultSet.getBytes(5); byte[] reference = resultSet.getBytes(5);
boolean isForSale = resultSet.getBoolean(6); 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); int creationGroupId = resultSet.getInt(8);
return new NameData(owner, name, data, registered, updated, reference, isForSale, salePrice, creationGroupId); 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 name = resultSet.getString(1);
String data = resultSet.getString(2); String data = resultSet.getString(2);
String owner = resultSet.getString(3); String owner = resultSet.getString(3);
long registered = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); long registered = getZonedTimestampMilli(resultSet, 4);
Long updated = getZonedTimestampMilli(resultSet, 5); // can be null
// Special handling for possibly-NULL "updated" column
Timestamp updatedTimestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC));
Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime();
byte[] reference = resultSet.getBytes(6); byte[] reference = resultSet.getBytes(6);
boolean isForSale = resultSet.getBoolean(7); 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); int creationGroupId = resultSet.getInt(9);
names.add(new NameData(owner, name, data, registered, updated, reference, isForSale, salePrice, creationGroupId)); 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 name = resultSet.getString(1);
String data = resultSet.getString(2); String data = resultSet.getString(2);
String owner = resultSet.getString(3); String owner = resultSet.getString(3);
long registered = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); long registered = getZonedTimestampMilli(resultSet, 4);
Long updated = getZonedTimestampMilli(resultSet, 5); // can be null
// Special handling for possibly-NULL "updated" column
Timestamp updatedTimestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC));
Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime();
byte[] reference = resultSet.getBytes(6); byte[] reference = resultSet.getBytes(6);
boolean isForSale = true; 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); int creationGroupId = resultSet.getInt(8);
names.add(new NameData(owner, name, data, registered, updated, reference, isForSale, salePrice, creationGroupId)); names.add(new NameData(owner, name, data, registered, updated, reference, isForSale, salePrice, creationGroupId));
@ -152,15 +158,15 @@ public class HSQLDBNameRepository implements NameRepository {
do { do {
String name = resultSet.getString(1); String name = resultSet.getString(1);
String data = resultSet.getString(2); String data = resultSet.getString(2);
long registered = resultSet.getTimestamp(3, Calendar.getInstance(HSQLDBRepository.UTC)).getTime(); long registered = getZonedTimestampMilli(resultSet, 3);
Long updated = getZonedTimestampMilli(resultSet, 4); // can be null
// Special handling for possibly-NULL "updated" column
Timestamp updatedTimestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC));
Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime();
byte[] reference = resultSet.getBytes(5); byte[] reference = resultSet.getBytes(5);
boolean isForSale = resultSet.getBoolean(6); 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); int creationGroupId = resultSet.getInt(8);
names.add(new NameData(owner, name, data, registered, updated, reference, isForSale, salePrice, creationGroupId)); 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 { public void save(NameData nameData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("Names"); 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()) 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("is_for_sale", nameData.getIsForSale()).bind("sale_price", nameData.getSalePrice())
.bind("creation_group_id", nameData.getCreationGroupId()); .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. */ /** 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) if (timestamp == null)
return null; return null;
@ -771,12 +771,12 @@ public class HSQLDBRepository implements Repository {
} }
/** Converts OffsetDateTime from TIMESTAMP WITH TIME ZONE column to milliseconds from epoch. */ /** 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(); return offsetDateTime.toInstant().toEpochMilli();
} }
/** Returns TIMESTAMP WITH TIME ZONE column value as milliseconds from epoch, or null. */ /** 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); OffsetDateTime offsetDateTime = resultSet.getObject(columnIndex, OffsetDateTime.class);
if (offsetDateTime == null) if (offsetDateTime == null)
return null; return null;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,9 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.NullAccount;
import org.qortal.asset.Asset;
import org.qortal.block.BlockChain; import org.qortal.block.BlockChain;
import org.qortal.data.transaction.AccountLevelTransactionData; import org.qortal.data.transaction.AccountLevelTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
@ -16,7 +13,9 @@ import org.qortal.repository.Repository;
public class AccountLevelTransaction extends Transaction { public class AccountLevelTransaction extends Transaction {
// Properties // Properties
private AccountLevelTransactionData accountLevelTransactionData; private AccountLevelTransactionData accountLevelTransactionData;
private Account targetAccount = null;
// Constructors // Constructors
@ -29,59 +28,25 @@ public class AccountLevelTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList(); return Collections.singletonList(this.accountLevelTransactionData.getTarget());
}
@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;
} }
// Navigation // Navigation
public Account getTarget() { 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 // Processing
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
Account creator = getCreator(); // Invalid outside of genesis block
return ValidationResult.NO_FLAG_PERMISSION;
// 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;
} }
@Override @Override
@ -89,13 +54,13 @@ public class AccountLevelTransaction extends Transaction {
Account target = getTarget(); Account target = getTarget();
// Save this transaction // Save this transaction
this.repository.getTransactionRepository().save(accountLevelTransactionData); this.repository.getTransactionRepository().save(this.accountLevelTransactionData);
// Set account's initial level // Set account's initial level
target.setLevel(this.accountLevelTransactionData.getLevel()); target.setLevel(this.accountLevelTransactionData.getLevel());
// Set account's blocks minted adjustment // 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()); int blocksMintedAdjustment = cumulativeBlocksByLevel.get(this.accountLevelTransactionData.getLevel());
target.setBlocksMintedAdjustment(blocksMintedAdjustment); target.setBlocksMintedAdjustment(blocksMintedAdjustment);
} }

View File

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

View File

@ -1,12 +1,9 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.PaymentData; import org.qortal.data.PaymentData;
import org.qortal.data.transaction.ArbitraryTransactionData; import org.qortal.data.transaction.ArbitraryTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
@ -33,57 +30,14 @@ public class ArbitraryTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
List<Account> recipients = new ArrayList<>(); return this.arbitraryTransactionData.getPayments().stream().map(PaymentData::getRecipient).collect(Collectors.toList());
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;
} }
// Navigation // Navigation
public Account getSender() throws DataException { public Account getSender() {
return new PublicKeyAccount(this.repository, this.arbitraryTransactionData.getSenderPublicKey()); return this.getCreator();
} }
// Processing // Processing
@ -110,7 +64,7 @@ public class ArbitraryTransaction extends Transaction {
public void process() throws DataException { public void process() throws DataException {
// Wrap and delegate payment processing to Payment class. // Wrap and delegate payment processing to Payment class.
new Payment(this.repository).process(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(), new Payment(this.repository).process(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(),
arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature()); arbitraryTransactionData.getSignature());
} }
@Override @Override
@ -124,7 +78,7 @@ public class ArbitraryTransaction extends Transaction {
public void orphan() throws DataException { public void orphan() throws DataException {
// Wrap and delegate payment processing to Payment class. // Wrap and delegate payment processing to Payment class.
new Payment(this.repository).orphan(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(), new Payment(this.repository).orphan(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(),
arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature(), arbitraryTransactionData.getReference()); arbitraryTransactionData.getSignature(), arbitraryTransactionData.getReference());
} }
@Override @Override

View File

@ -1,9 +1,6 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
@ -22,7 +19,10 @@ import com.google.common.primitives.Bytes;
public class AtTransaction extends Transaction { public class AtTransaction extends Transaction {
// Properties // Properties
private ATTransactionData atTransactionData; private ATTransactionData atTransactionData;
private Account atAccount = null;
private Account recipientAccount = null;
// Other useful constants // Other useful constants
public static final int MAX_DATA_SIZE = 256; public static final int MAX_DATA_SIZE = 256;
@ -50,97 +50,62 @@ public class AtTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(new Account(this.repository, this.atTransactionData.getRecipient())); return Arrays.asList(this.atTransactionData.getATAddress(), 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;
} }
// Navigation // Navigation
public Account getATAccount() throws DataException { public Account getATAccount() {
return new Account(this.repository, this.atTransactionData.getATAddress()); if (this.atAccount == null)
this.atAccount = new Account(this.repository, this.atTransactionData.getATAddress());
return this.atAccount;
} }
public Account getRecipient() throws DataException { public Account getRecipient() {
return new Account(this.repository, this.atTransactionData.getRecipient()); if (this.recipientAccount == null)
this.recipientAccount = new Account(this.repository, this.atTransactionData.getRecipient());
return this.recipientAccount;
} }
// Processing // Processing
@Override @Override
public boolean hasValidReference() throws DataException { 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(); Account atAccount = getATAccount();
return Arrays.equals(atAccount.getLastReference(), atTransactionData.getReference()); return Arrays.equals(atAccount.getLastReference(), atTransactionData.getReference());
} }
@Override @Override
public ValidationResult isValid() throws DataException { 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 // Check recipient address is valid
if (!Crypto.isValidAddress(this.atTransactionData.getRecipient())) if (!Crypto.isValidAddress(this.atTransactionData.getRecipient()))
return ValidationResult.INVALID_ADDRESS; 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(); long assetId = this.atTransactionData.getAssetId();
AssetData assetData = this.repository.getAssetRepository().fromAssetId(assetId); AssetData assetData = this.repository.getAssetRepository().fromAssetId(assetId);
// Check asset even exists // Check asset even exists
@ -148,12 +113,12 @@ public class AtTransaction extends Transaction {
return ValidationResult.ASSET_DOES_NOT_EXIST; return ValidationResult.ASSET_DOES_NOT_EXIST;
// Check asset amount is integer if asset is not divisible // 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; return ValidationResult.INVALID_AMOUNT;
Account sender = getATAccount(); Account sender = getATAccount();
// Check sender has enough of asset // Check sender has enough of asset
if (sender.getConfirmedBalance(assetId).compareTo(amount) < 0) if (sender.getConfirmedBalance(assetId) < amount)
return ValidationResult.NO_BALANCE; return ValidationResult.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -161,18 +126,19 @@ public class AtTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
if (this.atTransactionData.getAmount() != null) { Long amount = this.atTransactionData.getAmount();
if (amount != null) {
Account sender = getATAccount(); Account sender = getATAccount();
Account recipient = getRecipient(); Account recipient = getRecipient();
long assetId = this.atTransactionData.getAssetId(); long assetId = this.atTransactionData.getAssetId();
BigDecimal amount = this.atTransactionData.getAmount();
// Update sender's balance due to amount // 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 // 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 @Override
public void orphan() throws DataException { public void orphan() throws DataException {
if (this.atTransactionData.getAmount() != null) { Long amount = this.atTransactionData.getAmount();
if (amount != null) {
Account sender = getATAccount(); Account sender = getATAccount();
Account recipient = getRecipient(); Account recipient = getRecipient();
long assetId = this.atTransactionData.getAssetId(); long assetId = this.atTransactionData.getAssetId();
BigDecimal amount = this.atTransactionData.getAmount();
// Update sender's balance due to amount // 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 // 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 // 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; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.data.naming.NameData; import org.qortal.data.naming.NameData;
import org.qortal.data.transaction.BuyNameTransactionData; import org.qortal.data.transaction.BuyNameTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
@ -19,6 +18,7 @@ import com.google.common.base.Utf8;
public class BuyNameTransaction extends Transaction { public class BuyNameTransaction extends Transaction {
// Properties // Properties
private BuyNameTransactionData buyNameTransactionData; private BuyNameTransactionData buyNameTransactionData;
// Constructors // Constructors
@ -32,57 +32,36 @@ public class BuyNameTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(new Account(this.repository, this.buyNameTransactionData.getSeller())); return Collections.singletonList(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;
} }
// Navigation // Navigation
public Account getBuyer() throws DataException { public Account getBuyer() {
return new PublicKeyAccount(this.repository, this.buyNameTransactionData.getBuyerPublicKey()); return this.getCreator();
} }
// Processing // Processing
@Override @Override
public ValidationResult isValid() throws DataException { 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 // Check name size bounds
int nameLength = Utf8.encodedLength(buyNameTransactionData.getName()); int nameLength = Utf8.encodedLength(name);
if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE) if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH; return ValidationResult.INVALID_NAME_LENGTH;
// Check name is lowercase // Check name is lowercase
if (!buyNameTransactionData.getName().equals(buyNameTransactionData.getName().toLowerCase())) if (!name.equals(name.toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE; return ValidationResult.NAME_NOT_LOWER_CASE;
NameData nameData = this.repository.getNameRepository().fromName(buyNameTransactionData.getName()); NameData nameData = this.repository.getNameRepository().fromName(name);
// Check name exists // Check name exists
if (nameData == null) if (nameData == null)
@ -98,19 +77,15 @@ public class BuyNameTransaction extends Transaction {
return ValidationResult.BUYER_ALREADY_OWNER; return ValidationResult.BUYER_ALREADY_OWNER;
// Check expected seller currently owns name // Check expected seller currently owns name
if (!buyNameTransactionData.getSeller().equals(nameData.getOwner())) if (!this.buyNameTransactionData.getSeller().equals(nameData.getOwner()))
return ValidationResult.INVALID_SELLER; return ValidationResult.INVALID_SELLER;
// Check amounts agree // Check amounts agree
if (buyNameTransactionData.getAmount().compareTo(nameData.getSalePrice()) != 0) if (this.buyNameTransactionData.getAmount() != nameData.getSalePrice())
return ValidationResult.INVALID_AMOUNT; return ValidationResult.INVALID_AMOUNT;
// Check fee is positive
if (buyNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check issuer has enough funds // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -119,21 +94,21 @@ public class BuyNameTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Update Name // Update Name
Name name = new Name(this.repository, buyNameTransactionData.getName()); Name name = new Name(this.repository, this.buyNameTransactionData.getName());
name.buy(buyNameTransactionData); name.buy(this.buyNameTransactionData);
// Save transaction with updated "name reference" pointing to previous transaction that updated name // 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 @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Revert name // Revert name
Name name = new Name(this.repository, buyNameTransactionData.getName()); Name name = new Name(this.repository, this.buyNameTransactionData.getName());
name.unbuy(buyNameTransactionData); name.unbuy(this.buyNameTransactionData);
// Save this transaction, with removed "name reference" // 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; package org.qortal.transaction;
import java.math.BigDecimal; import java.util.Arrays;
import java.util.ArrayList; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.asset.Order; import org.qortal.asset.Order;
import org.qortal.crypto.Crypto;
import org.qortal.data.asset.OrderData; import org.qortal.data.asset.OrderData;
import org.qortal.data.transaction.CancelAssetOrderTransactionData; import org.qortal.data.transaction.CancelAssetOrderTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
@ -32,30 +30,8 @@ public class CancelAssetOrderTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() { public List<String> getRecipientAddresses() {
return new ArrayList<>(); return Collections.emptyList();
}
@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());
} }
// Processing // Processing
@ -64,12 +40,8 @@ public class CancelAssetOrderTransaction extends Transaction {
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
AssetRepository assetRepository = this.repository.getAssetRepository(); AssetRepository assetRepository = this.repository.getAssetRepository();
// Check fee is positive
if (cancelOrderTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check order even exists // Check order even exists
OrderData orderData = assetRepository.fromOrderId(cancelOrderTransactionData.getOrderId()); OrderData orderData = assetRepository.fromOrderId(this.cancelOrderTransactionData.getOrderId());
if (orderData == null) if (orderData == null)
return ValidationResult.ORDER_DOES_NOT_EXIST; return ValidationResult.ORDER_DOES_NOT_EXIST;
@ -77,19 +49,14 @@ public class CancelAssetOrderTransaction extends Transaction {
if (orderData.getIsClosed()) if (orderData.getIsClosed())
return ValidationResult.ORDER_ALREADY_CLOSED; return ValidationResult.ORDER_ALREADY_CLOSED;
Account creator = getCreator(); // Check transaction creator matches order creator
if (!Arrays.equals(this.transactionData.getCreatorPublicKey(), orderData.getCreatorPublicKey()))
// 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()))
return ValidationResult.INVALID_ORDER_CREATOR; return ValidationResult.INVALID_ORDER_CREATOR;
Account creator = getCreator();
// Check creator has enough QORT for fee // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -98,7 +65,7 @@ public class CancelAssetOrderTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Mark Order as completed so no more trades can happen // 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 order = new Order(this.repository, orderData);
order.cancel(); order.cancel();
} }
@ -106,7 +73,7 @@ public class CancelAssetOrderTransaction extends Transaction {
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Unmark Order as completed so trades can happen again // 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 order = new Order(this.repository, orderData);
order.reopen(); order.reopen();
} }

View File

@ -1,11 +1,9 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.group.GroupData; import org.qortal.data.group.GroupData;
@ -18,7 +16,9 @@ import org.qortal.repository.Repository;
public class CancelGroupBanTransaction extends Transaction { public class CancelGroupBanTransaction extends Transaction {
// Properties // Properties
private CancelGroupBanTransactionData groupUnbanTransactionData; private CancelGroupBanTransactionData groupUnbanTransactionData;
private Account memberAccount = null;
// Constructors // Constructors
@ -31,53 +31,34 @@ public class CancelGroupBanTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList(); return Collections.singletonList(this.groupUnbanTransactionData.getMember());
}
@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;
} }
// Navigation // Navigation
public Account getAdmin() throws DataException { public Account getAdmin() {
return new PublicKeyAccount(this.repository, this.groupUnbanTransactionData.getAdminPublicKey()); return this.getCreator();
} }
public Account getMember() throws DataException { public Account getMember() {
return new Account(this.repository, this.groupUnbanTransactionData.getMember()); if (this.memberAccount == null)
this.memberAccount = new Account(this.repository, this.groupUnbanTransactionData.getMember());
return this.memberAccount;
} }
// Processing // Processing
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
int groupId = this.groupUnbanTransactionData.getGroupId();
// Check member address is valid // Check member address is valid
if (!Crypto.isValidAddress(groupUnbanTransactionData.getMember())) if (!Crypto.isValidAddress(this.groupUnbanTransactionData.getMember()))
return ValidationResult.INVALID_ADDRESS; return ValidationResult.INVALID_ADDRESS;
GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupUnbanTransactionData.getGroupId()); GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupId);
// Check group exists // Check group exists
if (groupData == null) if (groupData == null)
@ -86,21 +67,17 @@ public class CancelGroupBanTransaction extends Transaction {
Account admin = getAdmin(); Account admin = getAdmin();
// Can't unban if not an admin // 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; return ValidationResult.NOT_GROUP_ADMIN;
Account member = getMember(); Account member = getMember();
// Check ban actually exists // 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; return ValidationResult.BAN_UNKNOWN;
// Check fee is positive // Check admin has enough funds
if (groupUnbanTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) if (admin.getConfirmedBalance(Asset.QORT) < this.groupUnbanTransactionData.getFee())
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (admin.getConfirmedBalance(Asset.QORT).compareTo(groupUnbanTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE; return ValidationResult.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -109,21 +86,21 @@ public class CancelGroupBanTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Update Group Membership // Update Group Membership
Group group = new Group(this.repository, groupUnbanTransactionData.getGroupId()); Group group = new Group(this.repository, this.groupUnbanTransactionData.getGroupId());
group.cancelBan(groupUnbanTransactionData); group.cancelBan(this.groupUnbanTransactionData);
// Save this transaction with updated member/admin references to transactions that can help restore state // 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 @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Revert group membership // Revert group membership
Group group = new Group(this.repository, groupUnbanTransactionData.getGroupId()); Group group = new Group(this.repository, this.groupUnbanTransactionData.getGroupId());
group.uncancelBan(groupUnbanTransactionData); group.uncancelBan(this.groupUnbanTransactionData);
// Save this transaction with removed member/admin references // 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; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.group.GroupData; import org.qortal.data.group.GroupData;
@ -18,7 +16,9 @@ import org.qortal.repository.Repository;
public class CancelGroupInviteTransaction extends Transaction { public class CancelGroupInviteTransaction extends Transaction {
// Properties // Properties
private CancelGroupInviteTransactionData cancelGroupInviteTransactionData; private CancelGroupInviteTransactionData cancelGroupInviteTransactionData;
private Account inviteeAccount = null;
// Constructors // Constructors
@ -31,53 +31,34 @@ public class CancelGroupInviteTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList(); return Collections.singletonList(this.cancelGroupInviteTransactionData.getInvitee());
}
@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;
} }
// Navigation // Navigation
public Account getAdmin() throws DataException { public Account getAdmin() {
return new PublicKeyAccount(this.repository, this.cancelGroupInviteTransactionData.getAdminPublicKey()); return this.getCreator();
} }
public Account getInvitee() throws DataException { public Account getInvitee() {
return new Account(this.repository, this.cancelGroupInviteTransactionData.getInvitee()); if (this.inviteeAccount == null)
this.inviteeAccount = new Account(this.repository, this.cancelGroupInviteTransactionData.getInvitee());
return this.inviteeAccount;
} }
// Processing // Processing
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
int groupId = this.cancelGroupInviteTransactionData.getGroupId();
// Check invitee address is valid // Check invitee address is valid
if (!Crypto.isValidAddress(cancelGroupInviteTransactionData.getInvitee())) if (!Crypto.isValidAddress(this.cancelGroupInviteTransactionData.getInvitee()))
return ValidationResult.INVALID_ADDRESS; return ValidationResult.INVALID_ADDRESS;
GroupData groupData = this.repository.getGroupRepository().fromGroupId(cancelGroupInviteTransactionData.getGroupId()); GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupId);
// Check group exists // Check group exists
if (groupData == null) if (groupData == null)
@ -86,21 +67,17 @@ public class CancelGroupInviteTransaction extends Transaction {
Account admin = getAdmin(); Account admin = getAdmin();
// Check admin is actually an admin // 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; return ValidationResult.NOT_GROUP_ADMIN;
Account invitee = getInvitee(); Account invitee = getInvitee();
// Check invite exists // Check invite exists
if (!this.repository.getGroupRepository().inviteExists(cancelGroupInviteTransactionData.getGroupId(), invitee.getAddress())) if (!this.repository.getGroupRepository().inviteExists(groupId, invitee.getAddress()))
return ValidationResult.INVITE_UNKNOWN; return ValidationResult.INVITE_UNKNOWN;
// Check fee is positive
if (cancelGroupInviteTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -109,21 +86,21 @@ public class CancelGroupInviteTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Update Group Membership // Update Group Membership
Group group = new Group(this.repository, cancelGroupInviteTransactionData.getGroupId()); Group group = new Group(this.repository, this.cancelGroupInviteTransactionData.getGroupId());
group.cancelInvite(cancelGroupInviteTransactionData); group.cancelInvite(this.cancelGroupInviteTransactionData);
// Save this transaction with updated member/admin references to transactions that can help restore state // 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 @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Revert group membership // Revert group membership
Group group = new Group(this.repository, cancelGroupInviteTransactionData.getGroupId()); Group group = new Group(this.repository, this.cancelGroupInviteTransactionData.getGroupId());
group.uncancelInvite(cancelGroupInviteTransactionData); group.uncancelInvite(this.cancelGroupInviteTransactionData);
// Save this transaction with removed member/admin references // 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; package org.qortal.transaction;
import java.math.BigDecimal; import java.util.Collections;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.data.naming.NameData; import org.qortal.data.naming.NameData;
import org.qortal.data.transaction.CancelSellNameTransactionData; import org.qortal.data.transaction.CancelSellNameTransactionData;
@ -32,51 +30,32 @@ public class CancelSellNameTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() { public List<String> getRecipientAddresses() throws DataException {
return new ArrayList<>(); return Collections.emptyList();
}
@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;
} }
// Navigation // Navigation
public Account getOwner() throws DataException { public Account getOwner() {
return new PublicKeyAccount(this.repository, this.cancelSellNameTransactionData.getOwnerPublicKey()); return this.getCreator();
} }
// Processing // Processing
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
String name = this.cancelSellNameTransactionData.getName();
// Check name size bounds // Check name size bounds
int nameLength = Utf8.encodedLength(cancelSellNameTransactionData.getName()); int nameLength = Utf8.encodedLength(name);
if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE) if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH; return ValidationResult.INVALID_NAME_LENGTH;
// Check name is lowercase // Check name is lowercase
if (!cancelSellNameTransactionData.getName().equals(cancelSellNameTransactionData.getName().toLowerCase())) if (!name.equals(name.toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE; return ValidationResult.NAME_NOT_LOWER_CASE;
NameData nameData = this.repository.getNameRepository().fromName(cancelSellNameTransactionData.getName()); NameData nameData = this.repository.getNameRepository().fromName(name);
// Check name exists // Check name exists
if (nameData == null) if (nameData == null)
@ -86,17 +65,13 @@ public class CancelSellNameTransaction extends Transaction {
if (!nameData.getIsForSale()) if (!nameData.getIsForSale())
return ValidationResult.NAME_NOT_FOR_SALE; 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(); Account owner = getOwner();
if (!owner.getAddress().equals(nameData.getOwner())) if (!owner.getAddress().equals(nameData.getOwner()))
return ValidationResult.INVALID_NAME_OWNER; 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 // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;

View File

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

View File

@ -1,6 +1,5 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -31,38 +30,14 @@ public class CreateGroupTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(getOwner()); return Collections.singletonList(this.createGroupTransactionData.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;
} }
// Navigation // Navigation
public Account getOwner() throws DataException { public Account getOwner() {
return new Account(this.repository, this.createGroupTransactionData.getOwner()); return this.getCreator();
} }
// Processing // Processing
@ -70,45 +45,41 @@ public class CreateGroupTransaction extends Transaction {
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Check owner address is valid // Check owner address is valid
if (!Crypto.isValidAddress(createGroupTransactionData.getOwner())) if (!Crypto.isValidAddress(this.createGroupTransactionData.getOwner()))
return ValidationResult.INVALID_ADDRESS; return ValidationResult.INVALID_ADDRESS;
// Check approval threshold is valid // Check approval threshold is valid
if (createGroupTransactionData.getApprovalThreshold() == null) if (this.createGroupTransactionData.getApprovalThreshold() == null)
return ValidationResult.INVALID_GROUP_APPROVAL_THRESHOLD; return ValidationResult.INVALID_GROUP_APPROVAL_THRESHOLD;
// Check min/max block delay values // Check min/max block delay values
if (createGroupTransactionData.getMinimumBlockDelay() < 0) if (this.createGroupTransactionData.getMinimumBlockDelay() < 0)
return ValidationResult.INVALID_GROUP_BLOCK_DELAY; return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
if (createGroupTransactionData.getMaximumBlockDelay() < 1) if (this.createGroupTransactionData.getMaximumBlockDelay() < 1)
return ValidationResult.INVALID_GROUP_BLOCK_DELAY; return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
if (createGroupTransactionData.getMaximumBlockDelay() < createGroupTransactionData.getMinimumBlockDelay()) if (this.createGroupTransactionData.getMaximumBlockDelay() < this.createGroupTransactionData.getMinimumBlockDelay())
return ValidationResult.INVALID_GROUP_BLOCK_DELAY; return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
// Check group name size bounds // 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) if (groupNameLength < 1 || groupNameLength > Group.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH; return ValidationResult.INVALID_NAME_LENGTH;
// Check description size bounds // 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) if (descriptionLength < 1 || descriptionLength > Group.MAX_DESCRIPTION_SIZE)
return ValidationResult.INVALID_DESCRIPTION_LENGTH; return ValidationResult.INVALID_DESCRIPTION_LENGTH;
// Check group name is lowercase // 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; return ValidationResult.NAME_NOT_LOWER_CASE;
// Check fee is positive
if (createGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
Account creator = getCreator(); Account creator = getCreator();
// Check creator has enough funds // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -117,7 +88,7 @@ public class CreateGroupTransaction extends Transaction {
@Override @Override
public ValidationResult isProcessable() throws DataException { public ValidationResult isProcessable() throws DataException {
// Check the group name isn't already taken // 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.GROUP_ALREADY_EXISTS;
return ValidationResult.OK; return ValidationResult.OK;
@ -126,27 +97,27 @@ public class CreateGroupTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Create Group // Create Group
Group group = new Group(this.repository, createGroupTransactionData); Group group = new Group(this.repository, this.createGroupTransactionData);
group.create(createGroupTransactionData); group.create(this.createGroupTransactionData);
// Note newly assigned group ID in our transaction record // 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 // Save this transaction with newly assigned group ID
this.repository.getTransactionRepository().save(createGroupTransactionData); this.repository.getTransactionRepository().save(this.createGroupTransactionData);
} }
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Uncreate group // Uncreate group
Group group = new Group(this.repository, createGroupTransactionData.getGroupId()); Group group = new Group(this.repository, this.createGroupTransactionData.getGroupId());
group.uncreate(); group.uncreate();
// Remove assigned group ID from transaction record // Remove assigned group ID from transaction record
createGroupTransactionData.setGroupId(null); this.createGroupTransactionData.setGroupId(null);
// Save this transaction with removed group ID // 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; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.transaction.CreatePollTransactionData; import org.qortal.data.transaction.CreatePollTransactionData;
@ -34,42 +32,13 @@ public class CreatePollTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(getOwner()); return Collections.singletonList(this.createPollTransactionData.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;
} }
// Navigation // Navigation
@Override public Account getOwner() {
public PublicKeyAccount getCreator() throws DataException {
return new PublicKeyAccount(this.repository, this.createPollTransactionData.getCreatorPublicKey());
}
public Account getOwner() throws DataException {
return new Account(this.repository, this.createPollTransactionData.getOwner()); return new Account(this.repository, this.createPollTransactionData.getOwner());
} }
@ -78,33 +47,31 @@ public class CreatePollTransaction extends Transaction {
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Check owner address is valid // Check owner address is valid
if (!Crypto.isValidAddress(createPollTransactionData.getOwner())) if (!Crypto.isValidAddress(this.createPollTransactionData.getOwner()))
return ValidationResult.INVALID_ADDRESS; return ValidationResult.INVALID_ADDRESS;
// Check name size bounds // 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) if (pollNameLength < 1 || pollNameLength > Poll.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH; return ValidationResult.INVALID_NAME_LENGTH;
// Check description size bounds // 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) if (pollDescriptionLength < 1 || pollDescriptionLength > Poll.MAX_DESCRIPTION_SIZE)
return ValidationResult.INVALID_DESCRIPTION_LENGTH; return ValidationResult.INVALID_DESCRIPTION_LENGTH;
// Check poll name is lowercase // 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; 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 // Check number of options
List<PollOptionData> pollOptions = createPollTransactionData.getPollOptions(); List<PollOptionData> pollOptions = this.createPollTransactionData.getPollOptions();
int pollOptionsCount = pollOptions.size(); int pollOptionsCount = pollOptions.size();
if (pollOptionsCount < 1 || pollOptionsCount > Poll.MAX_OPTIONS) if (pollOptionsCount < 1 || pollOptionsCount > Poll.MAX_OPTIONS)
return ValidationResult.INVALID_OPTIONS_COUNT; return ValidationResult.INVALID_OPTIONS_COUNT;
// Check each option // Check each option
List<String> optionNames = new ArrayList<String>(); List<String> optionNames = new ArrayList<>();
for (PollOptionData pollOptionData : pollOptions) { for (PollOptionData pollOptionData : pollOptions) {
// Check option length // Check option length
int optionNameLength = Utf8.encodedLength(pollOptionData.getOptionName()); int optionNameLength = Utf8.encodedLength(pollOptionData.getOptionName());
@ -119,15 +86,10 @@ public class CreatePollTransaction extends Transaction {
optionNames.add(pollOptionData.getOptionName()); 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(); Account creator = getCreator();
// Check issuer has enough funds // Check creator has enough funds
if (creator.getConfirmedBalance(Asset.QORT).compareTo(createPollTransactionData.getFee()) < 0) if (creator.getConfirmedBalance(Asset.QORT) < this.createPollTransactionData.getFee())
return ValidationResult.NO_BALANCE; return ValidationResult.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -136,7 +98,7 @@ public class CreatePollTransaction extends Transaction {
@Override @Override
public ValidationResult isProcessable() throws DataException { public ValidationResult isProcessable() throws DataException {
// Check the poll name isn't already taken // 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.POLL_ALREADY_EXISTS;
return ValidationResult.OK; return ValidationResult.OK;
@ -145,14 +107,14 @@ public class CreatePollTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Publish poll to allow voting // Publish poll to allow voting
Poll poll = new Poll(this.repository, createPollTransactionData); Poll poll = new Poll(this.repository, this.createPollTransactionData);
poll.publish(); poll.publish();
} }
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Unpublish poll // Unpublish poll
Poll poll = new Poll(this.repository, createPollTransactionData.getPollName()); Poll poll = new Poll(this.repository, this.createPollTransactionData.getPollName());
poll.unpublish(); poll.unpublish();
} }

View File

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

View File

@ -1,6 +1,5 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -36,40 +35,8 @@ public class GenesisTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(new Account(this.repository, genesisTransactionData.getRecipient())); return Collections.singletonList(this.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;
} }
// Processing // Processing
@ -123,11 +90,11 @@ public class GenesisTransaction extends Transaction {
@Override @Override
public ValidationResult isValid() { public ValidationResult isValid() {
// Check amount is zero or positive // Check amount is zero or positive
if (genesisTransactionData.getAmount().compareTo(BigDecimal.ZERO) < 0) if (this.genesisTransactionData.getAmount() < 0)
return ValidationResult.NEGATIVE_AMOUNT; return ValidationResult.NEGATIVE_AMOUNT;
// Check recipient address is valid // Check recipient address is valid
if (!Crypto.isValidAddress(genesisTransactionData.getRecipient())) if (!Crypto.isValidAddress(this.genesisTransactionData.getRecipient()))
return ValidationResult.INVALID_ADDRESS; return ValidationResult.INVALID_ADDRESS;
return ValidationResult.OK; return ValidationResult.OK;
@ -135,26 +102,26 @@ public class GenesisTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
Account recipient = new Account(repository, genesisTransactionData.getRecipient()); Account recipient = new Account(repository, this.genesisTransactionData.getRecipient());
// Update recipient's balance // Update recipient's balance
recipient.setConfirmedBalance(genesisTransactionData.getAssetId(), genesisTransactionData.getAmount()); recipient.setConfirmedBalance(this.genesisTransactionData.getAssetId(), this.genesisTransactionData.getAmount());
} }
@Override @Override
public void processReferencesAndFees() throws DataException { public void processReferencesAndFees() throws DataException {
// Do not attempt to update non-existent genesis account's reference! // 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) // Set recipient's starting reference (also creates account)
recipient.setLastReference(genesisTransactionData.getSignature()); recipient.setLastReference(this.genesisTransactionData.getSignature());
} }
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Delete recipient's account (and balance) // Delete recipient's account (and balance)
this.repository.getAccountRepository().delete(genesisTransactionData.getRecipient()); this.repository.getAccountRepository().delete(this.genesisTransactionData.getRecipient());
} }
@Override @Override

View File

@ -1,11 +1,9 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.data.transaction.GroupApprovalTransactionData; import org.qortal.data.transaction.GroupApprovalTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
@ -28,35 +26,14 @@ public class GroupApprovalTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList(); 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 // Navigation
public Account getAdmin() throws DataException { public Account getAdmin() {
return new PublicKeyAccount(this.repository, this.groupApprovalTransactionData.getAdminPublicKey()); return this.getCreator();
} }
// Processing // Processing
@ -64,7 +41,7 @@ public class GroupApprovalTransaction extends Transaction {
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Grab pending transaction's data // 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) if (pendingTransactionData == null)
return ValidationResult.TRANSACTION_UNKNOWN; return ValidationResult.TRANSACTION_UNKNOWN;
@ -82,12 +59,8 @@ public class GroupApprovalTransaction extends Transaction {
if (!this.repository.getGroupRepository().adminExists(pendingTransactionData.getTxGroupId(), admin.getAddress())) if (!this.repository.getGroupRepository().adminExists(pendingTransactionData.getTxGroupId(), admin.getAddress()))
return ValidationResult.NOT_GROUP_ADMIN; 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 // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -96,20 +69,20 @@ public class GroupApprovalTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Find previous approval decision (if any) by this admin for pending transaction // 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) 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 // 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 @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Save this transaction with removed prior reference // Save this transaction with removed prior reference
groupApprovalTransactionData.setPriorReference(null); this.groupApprovalTransactionData.setPriorReference(null);
this.repository.getTransactionRepository().save(groupApprovalTransactionData); this.repository.getTransactionRepository().save(this.groupApprovalTransactionData);
} }
} }

View File

@ -1,11 +1,9 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.group.GroupData; import org.qortal.data.group.GroupData;
@ -18,7 +16,9 @@ import org.qortal.repository.Repository;
public class GroupBanTransaction extends Transaction { public class GroupBanTransaction extends Transaction {
// Properties // Properties
private GroupBanTransactionData groupBanTransactionData; private GroupBanTransactionData groupBanTransactionData;
private Account offenderAccount = null;
// Constructors // Constructors
@ -31,53 +31,34 @@ public class GroupBanTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList(); return Collections.singletonList(this.groupBanTransactionData.getOffender());
}
@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;
} }
// Navigation // Navigation
public Account getAdmin() throws DataException { public Account getAdmin() {
return new PublicKeyAccount(this.repository, this.groupBanTransactionData.getAdminPublicKey()); return this.getCreator();
} }
public Account getOffender() throws DataException { public Account getOffender() {
return new Account(this.repository, this.groupBanTransactionData.getOffender()); if (this.offenderAccount == null)
this.offenderAccount = new Account(this.repository, this.groupBanTransactionData.getOffender());
return this.offenderAccount;
} }
// Processing // Processing
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
int groupId = this.groupBanTransactionData.getGroupId();
// Check offender address is valid // Check offender address is valid
if (!Crypto.isValidAddress(groupBanTransactionData.getOffender())) if (!Crypto.isValidAddress(this.groupBanTransactionData.getOffender()))
return ValidationResult.INVALID_ADDRESS; return ValidationResult.INVALID_ADDRESS;
GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupBanTransactionData.getGroupId()); GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupId);
// Check group exists // Check group exists
if (groupData == null) if (groupData == null)
@ -86,22 +67,17 @@ public class GroupBanTransaction extends Transaction {
Account admin = getAdmin(); Account admin = getAdmin();
// Can't ban if not an admin // 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; return ValidationResult.NOT_GROUP_ADMIN;
Account offender = getOffender(); Account offender = getOffender();
// Can't ban another admin unless the group owner // Can't ban another admin unless the group owner
if (!admin.getAddress().equals(groupData.getOwner()) if (!admin.getAddress().equals(groupData.getOwner()) && this.repository.getGroupRepository().adminExists(groupId, offender.getAddress()))
&& this.repository.getGroupRepository().adminExists(groupBanTransactionData.getGroupId(), offender.getAddress()))
return ValidationResult.INVALID_GROUP_OWNER; 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 // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -110,21 +86,21 @@ public class GroupBanTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Update Group Membership // Update Group Membership
Group group = new Group(this.repository, groupBanTransactionData.getGroupId()); Group group = new Group(this.repository, this.groupBanTransactionData.getGroupId());
group.ban(groupBanTransactionData); group.ban(this.groupBanTransactionData);
// Save this transaction with updated member/admin references to transactions that can help restore state // 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 @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Revert group membership // Revert group membership
Group group = new Group(this.repository, groupBanTransactionData.getGroupId()); Group group = new Group(this.repository, this.groupBanTransactionData.getGroupId());
group.unban(groupBanTransactionData); group.unban(this.groupBanTransactionData);
// Save this transaction with removed member/admin references // 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; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.transaction.GroupInviteTransactionData; import org.qortal.data.transaction.GroupInviteTransactionData;
@ -17,7 +15,9 @@ import org.qortal.repository.Repository;
public class GroupInviteTransaction extends Transaction { public class GroupInviteTransaction extends Transaction {
// Properties // Properties
private GroupInviteTransactionData groupInviteTransactionData; private GroupInviteTransactionData groupInviteTransactionData;
private Account inviteeAccount = null;
// Constructors // Constructors
@ -30,56 +30,35 @@ public class GroupInviteTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList(); return Collections.singletonList(this.groupInviteTransactionData.getInvitee());
}
@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;
} }
// Navigation // Navigation
public Account getAdmin() throws DataException { public Account getAdmin() {
return new PublicKeyAccount(this.repository, this.groupInviteTransactionData.getAdminPublicKey()); return this.getCreator();
} }
public Account getInvitee() throws DataException { public Account getInvitee() {
return new Account(this.repository, this.groupInviteTransactionData.getInvitee()); if (this.inviteeAccount == null)
this.inviteeAccount = new Account(this.repository, this.groupInviteTransactionData.getInvitee());
return this.inviteeAccount;
} }
// Processing // Processing
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
int groupId = groupInviteTransactionData.getGroupId(); int groupId = this.groupInviteTransactionData.getGroupId();
// Check time to live zero (infinite) or positive // Check time to live zero (infinite) or positive
if (groupInviteTransactionData.getTimeToLive() < 0) if (this.groupInviteTransactionData.getTimeToLive() < 0)
return ValidationResult.INVALID_LIFETIME; return ValidationResult.INVALID_LIFETIME;
// Check member address is valid // Check member address is valid
if (!Crypto.isValidAddress(groupInviteTransactionData.getInvitee())) if (!Crypto.isValidAddress(this.groupInviteTransactionData.getInvitee()))
return ValidationResult.INVALID_ADDRESS; return ValidationResult.INVALID_ADDRESS;
// Check group exists // Check group exists
@ -102,12 +81,8 @@ public class GroupInviteTransaction extends Transaction {
if (this.repository.getGroupRepository().banExists(groupId, invitee.getAddress())) if (this.repository.getGroupRepository().banExists(groupId, invitee.getAddress()))
return ValidationResult.BANNED_FROM_GROUP; 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 // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -116,21 +91,21 @@ public class GroupInviteTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Update Group Membership // Update Group Membership
Group group = new Group(this.repository, groupInviteTransactionData.getGroupId()); Group group = new Group(this.repository, this.groupInviteTransactionData.getGroupId());
group.invite(groupInviteTransactionData); group.invite(this.groupInviteTransactionData);
// Save this transaction with updated member/admin references to transactions that can help restore state // 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 @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Revert group membership // Revert group membership
Group group = new Group(this.repository, groupInviteTransactionData.getGroupId()); Group group = new Group(this.repository, this.groupInviteTransactionData.getGroupId());
group.uninvite(groupInviteTransactionData); group.uninvite(this.groupInviteTransactionData);
// Save this transaction with removed member/admin references // 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; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -32,32 +31,8 @@ public class GroupKickTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList(); return Collections.singletonList(this.groupKickTransactionData.getMember());
}
@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;
} }
// Navigation // Navigation
@ -74,12 +49,13 @@ public class GroupKickTransaction extends Transaction {
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
int groupId = this.groupKickTransactionData.getGroupId();
// Check member address is valid // Check member address is valid
if (!Crypto.isValidAddress(groupKickTransactionData.getMember())) if (!Crypto.isValidAddress(this.groupKickTransactionData.getMember()))
return ValidationResult.INVALID_ADDRESS; return ValidationResult.INVALID_ADDRESS;
GroupRepository groupRepository = this.repository.getGroupRepository(); GroupRepository groupRepository = this.repository.getGroupRepository();
int groupId = groupKickTransactionData.getGroupId();
GroupData groupData = groupRepository.fromGroupId(groupId); GroupData groupData = groupRepository.fromGroupId(groupId);
// Check group exists // Check group exists
@ -102,12 +78,8 @@ public class GroupKickTransaction extends Transaction {
if (!admin.getAddress().equals(groupData.getOwner()) && groupRepository.adminExists(groupId, member.getAddress())) if (!admin.getAddress().equals(groupData.getOwner()) && groupRepository.adminExists(groupId, member.getAddress()))
return ValidationResult.INVALID_GROUP_OWNER; 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 // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -116,21 +88,21 @@ public class GroupKickTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Update Group Membership // Update Group Membership
Group group = new Group(this.repository, groupKickTransactionData.getGroupId()); Group group = new Group(this.repository, this.groupKickTransactionData.getGroupId());
group.kick(groupKickTransactionData); group.kick(this.groupKickTransactionData);
// Save this transaction with updated member/admin references to transactions that can help restore state // 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 @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Revert group membership // Revert group membership
Group group = new Group(this.repository, groupKickTransactionData.getGroupId()); Group group = new Group(this.repository, this.groupKickTransactionData.getGroupId());
group.unkick(groupKickTransactionData); group.unkick(this.groupKickTransactionData);
// Save this transaction with removed member/admin references // 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; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.transaction.IssueAssetTransactionData; import org.qortal.data.transaction.IssueAssetTransactionData;
@ -18,7 +16,9 @@ import com.google.common.base.Utf8;
public class IssueAssetTransaction extends Transaction { public class IssueAssetTransaction extends Transaction {
// Properties // Properties
private IssueAssetTransactionData issueAssetTransactionData; private IssueAssetTransactionData issueAssetTransactionData;
private Account ownerAccount = null;
// Constructors // Constructors
@ -31,82 +31,59 @@ public class IssueAssetTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(getOwner()); return Collections.singletonList(this.issueAssetTransactionData.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;
} }
// Navigation // Navigation
public Account getIssuer() throws DataException { public Account getIssuer() {
return new PublicKeyAccount(this.repository, this.issueAssetTransactionData.getIssuerPublicKey()); return this.getCreator();
} }
public Account getOwner() throws DataException { public Account getOwner() {
return new Account(this.repository, this.issueAssetTransactionData.getOwner()); if (this.ownerAccount == null)
this.ownerAccount = new Account(this.repository, this.issueAssetTransactionData.getOwner());
return this.ownerAccount;
} }
// Processing // Processing
@Override @Override
public ValidationResult isValid() throws DataException { 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 // Check data field
String data = this.issueAssetTransactionData.getData(); String data = this.issueAssetTransactionData.getData();
int dataLength = Utf8.encodedLength(data); int dataLength = Utf8.encodedLength(data);
if (data == null || dataLength < 1 || dataLength > Asset.MAX_DATA_SIZE) if (data == null || dataLength < 1 || dataLength > Asset.MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH; 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 // 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; return ValidationResult.INVALID_QUANTITY;
// Check fee is positive // Check quantity versus indivisibility
if (issueAssetTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) if (!this.issueAssetTransactionData.getIsDivisible() && this.issueAssetTransactionData.getQuantity() % Asset.MULTIPLIER != 0)
return ValidationResult.NEGATIVE_FEE; return ValidationResult.INVALID_QUANTITY;
Account issuer = getIssuer(); Account issuer = getIssuer();
// Check issuer has enough funds // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -115,7 +92,7 @@ public class IssueAssetTransaction extends Transaction {
@Override @Override
public ValidationResult isProcessable() throws DataException { public ValidationResult isProcessable() throws DataException {
// Check the asset name isn't already taken. // 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.ASSET_ALREADY_EXISTS;
return ValidationResult.OK; return ValidationResult.OK;
@ -124,35 +101,35 @@ public class IssueAssetTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Issue asset // Issue asset
Asset asset = new Asset(this.repository, issueAssetTransactionData); Asset asset = new Asset(this.repository, this.issueAssetTransactionData);
asset.issue(); asset.issue();
// Add asset to owner // Add asset to owner
Account owner = getOwner(); 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 // 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 // Save this transaction with newly assigned assetId
this.repository.getTransactionRepository().save(issueAssetTransactionData); this.repository.getTransactionRepository().save(this.issueAssetTransactionData);
} }
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Remove asset from owner // Remove asset from owner
Account owner = getOwner(); Account owner = getOwner();
owner.deleteBalance(issueAssetTransactionData.getAssetId()); owner.deleteBalance(this.issueAssetTransactionData.getAssetId());
// Deissue asset // Deissue asset
Asset asset = new Asset(this.repository, issueAssetTransactionData.getAssetId()); Asset asset = new Asset(this.repository, this.issueAssetTransactionData.getAssetId());
asset.deissue(); asset.deissue();
// Remove assigned asset ID from transaction info // Remove assigned asset ID from transaction info
issueAssetTransactionData.setAssetId(null); this.issueAssetTransactionData.setAssetId(null);
// Save this transaction, with removed assetId // 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; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.data.transaction.JoinGroupTransactionData; import org.qortal.data.transaction.JoinGroupTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
@ -29,42 +27,21 @@ public class JoinGroupTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList(); 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 // Navigation
public Account getJoiner() throws DataException { public Account getJoiner() {
return new PublicKeyAccount(this.repository, this.joinGroupTransactionData.getJoinerPublicKey()); return this.getCreator();
} }
// Processing // Processing
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
int groupId = joinGroupTransactionData.getGroupId(); int groupId = this.joinGroupTransactionData.getGroupId();
// Check group exists // Check group exists
if (!this.repository.getGroupRepository().groupExists(groupId)) if (!this.repository.getGroupRepository().groupExists(groupId))
@ -83,11 +60,8 @@ public class JoinGroupTransaction extends Transaction {
if (this.repository.getGroupRepository().joinRequestExists(groupId, joiner.getAddress())) if (this.repository.getGroupRepository().joinRequestExists(groupId, joiner.getAddress()))
return ValidationResult.JOIN_REQUEST_EXISTS; return ValidationResult.JOIN_REQUEST_EXISTS;
// Check fee is positive // Check joiner has enough funds
if (joinGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) if (joiner.getConfirmedBalance(Asset.QORT) < this.joinGroupTransactionData.getFee())
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (joiner.getConfirmedBalance(Asset.QORT).compareTo(joinGroupTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE; return ValidationResult.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -96,21 +70,21 @@ public class JoinGroupTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Update Group Membership // Update Group Membership
Group group = new Group(this.repository, joinGroupTransactionData.getGroupId()); Group group = new Group(this.repository, this.joinGroupTransactionData.getGroupId());
group.join(joinGroupTransactionData); group.join(this.joinGroupTransactionData);
// Save this transaction with cached references to transactions that can help restore state // 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 @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Revert group membership // Revert group membership
Group group = new Group(this.repository, joinGroupTransactionData.getGroupId()); Group group = new Group(this.repository, this.joinGroupTransactionData.getGroupId());
group.unjoin(joinGroupTransactionData); group.unjoin(this.joinGroupTransactionData);
// Save this transaction with removed references // 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; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.data.group.GroupData; import org.qortal.data.group.GroupData;
import org.qortal.data.transaction.LeaveGroupTransactionData; import org.qortal.data.transaction.LeaveGroupTransactionData;
@ -30,42 +28,23 @@ public class LeaveGroupTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList(); 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 // Navigation
public Account getLeaver() throws DataException { public Account getLeaver() {
return new PublicKeyAccount(this.repository, this.leaveGroupTransactionData.getLeaverPublicKey()); return this.getCreator();
} }
// Processing // Processing
@Override @Override
public ValidationResult isValid() throws DataException { 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 // Check group exists
if (groupData == null) if (groupData == null)
@ -78,15 +57,11 @@ public class LeaveGroupTransaction extends Transaction {
return ValidationResult.GROUP_OWNER_CANNOT_LEAVE; return ValidationResult.GROUP_OWNER_CANNOT_LEAVE;
// Check leaver is actually a member of group // 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; return ValidationResult.NOT_GROUP_MEMBER;
// Check fee is positive // Check leaver has enough funds
if (leaveGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) if (leaver.getConfirmedBalance(Asset.QORT) < this.leaveGroupTransactionData.getFee())
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds
if (leaver.getConfirmedBalance(Asset.QORT).compareTo(leaveGroupTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE; return ValidationResult.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -95,21 +70,21 @@ public class LeaveGroupTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Update Group Membership // Update Group Membership
Group group = new Group(this.repository, leaveGroupTransactionData.getGroupId()); Group group = new Group(this.repository, this.leaveGroupTransactionData.getGroupId());
group.leave(leaveGroupTransactionData); group.leave(this.leaveGroupTransactionData);
// Save this transaction with updated member/admin references to transactions that can help restore state // 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 @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Revert group membership // Revert group membership
Group group = new Group(this.repository, leaveGroupTransactionData.getGroupId()); Group group = new Group(this.repository, this.leaveGroupTransactionData.getGroupId());
group.unleave(leaveGroupTransactionData); group.unleave(this.leaveGroupTransactionData);
// Save this transaction with removed member/admin references // 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; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.PaymentData; import org.qortal.data.PaymentData;
import org.qortal.data.transaction.MessageTransactionData; import org.qortal.data.transaction.MessageTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
@ -17,10 +14,13 @@ import org.qortal.repository.Repository;
public class MessageTransaction extends Transaction { public class MessageTransaction extends Transaction {
// Properties // Properties
private MessageTransactionData messageTransactionData; private MessageTransactionData messageTransactionData;
private PaymentData paymentData = null;
// Other useful constants // Other useful constants
public static final int MAX_DATA_SIZE = 4000; public static final int MAX_DATA_SIZE = 4000;
private static final boolean isZeroAmountValid = true;
// Constructors // Constructors
@ -33,109 +33,71 @@ public class MessageTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(new Account(this.repository, messageTransactionData.getRecipient())); return Collections.singletonList(this.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;
} }
// Navigation // Navigation
public Account getSender() throws DataException { public Account getSender() {
return new PublicKeyAccount(this.repository, this.messageTransactionData.getSenderPublicKey()); return this.getCreator();
} }
public Account getRecipient() throws DataException { public Account getRecipient() {
return new Account(this.repository, this.messageTransactionData.getRecipient()); return new Account(this.repository, this.messageTransactionData.getRecipient());
} }
// Processing // Processing
private PaymentData getPaymentData() { 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 @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Check data length // 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; 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 // 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); isZeroAmountValid);
} }
@Override @Override
public ValidationResult isProcessable() throws DataException { 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 // 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); isZeroAmountValid);
} }
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Wrap and delegate payment processing to Payment class. // Wrap and delegate payment processing to Payment class.
new Payment(this.repository).process(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(), new Payment(this.repository).process(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getSignature());
messageTransactionData.getSignature());
} }
@Override @Override
public void processReferencesAndFees() throws DataException { public void processReferencesAndFees() throws DataException {
// Wrap and delegate references processing to Payment class. Only update recipient's last reference if transferring QORT. // 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(), new Payment(this.repository).processReferencesAndFees(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getFee(),
messageTransactionData.getSignature(), false); this.messageTransactionData.getSignature(), false);
} }
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Wrap and delegate payment processing to Payment class. // Wrap and delegate payment processing to Payment class.
new Payment(this.repository).orphan(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(), new Payment(this.repository).orphan(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getSignature(), this.messageTransactionData.getReference());
messageTransactionData.getSignature(), messageTransactionData.getReference());
} }
@Override @Override
public void orphanReferencesAndFees() throws DataException { public void orphanReferencesAndFees() throws DataException {
// Wrap and delegate references processing to Payment class. Only revert recipient's last reference if transferring QORT. // 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(), new Payment(this.repository).orphanReferencesAndFees(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getFee(),
messageTransactionData.getSignature(), messageTransactionData.getReference(), false); this.messageTransactionData.getSignature(), this.messageTransactionData.getReference(), false);
} }
} }

View File

@ -1,11 +1,9 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.data.PaymentData; import org.qortal.data.PaymentData;
import org.qortal.data.transaction.MultiPaymentTransactionData; import org.qortal.data.transaction.MultiPaymentTransactionData;
@ -33,109 +31,67 @@ public class MultiPaymentTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
List<Account> recipients = new ArrayList<>(); return this.multiPaymentTransactionData.getPayments().stream().map(PaymentData::getRecipient).collect(Collectors.toList());
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;
} }
// Navigation // Navigation
public Account getSender() throws DataException { public Account getSender() {
return new PublicKeyAccount(this.repository, this.multiPaymentTransactionData.getSenderPublicKey()); return this.getCreator();
} }
// Processing // Processing
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
List<PaymentData> payments = multiPaymentTransactionData.getPayments(); List<PaymentData> payments = this.multiPaymentTransactionData.getPayments();
// Check number of payments // Check number of payments
if (payments.isEmpty() || payments.size() > MAX_PAYMENTS_COUNT) if (payments.isEmpty() || payments.size() > MAX_PAYMENTS_COUNT)
return ValidationResult.INVALID_PAYMENTS_COUNT; return ValidationResult.INVALID_PAYMENTS_COUNT;
// Check reference is correct
Account sender = getSender(); Account sender = getSender();
// Check sender has enough funds for fee // 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 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 @Override
public ValidationResult isProcessable() throws DataException { 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 @Override
public void process() throws DataException { public void process() throws DataException {
// Wrap and delegate payment processing to Payment class. // Wrap and delegate payment processing to Payment class.
new Payment(this.repository).process(multiPaymentTransactionData.getSenderPublicKey(), multiPaymentTransactionData.getPayments(), new Payment(this.repository).process(this.multiPaymentTransactionData.getSenderPublicKey(), this.multiPaymentTransactionData.getPayments(), this.multiPaymentTransactionData.getSignature());
multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature());
} }
@Override @Override
public void processReferencesAndFees() throws DataException { public void processReferencesAndFees() throws DataException {
// Wrap and delegate reference processing to Payment class. Always update recipients' last references regardless of asset. // 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(), new Payment(this.repository).processReferencesAndFees(this.multiPaymentTransactionData.getSenderPublicKey(), this.multiPaymentTransactionData.getPayments(),
multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature(), true); this.multiPaymentTransactionData.getFee(), this.multiPaymentTransactionData.getSignature(), true);
} }
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Wrap and delegate payment processing to Payment class. Always revert recipients' last references regardless of asset. // 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(), new Payment(this.repository).orphan(this.multiPaymentTransactionData.getSenderPublicKey(), this.multiPaymentTransactionData.getPayments(),
multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature(), multiPaymentTransactionData.getReference()); this.multiPaymentTransactionData.getSignature(), this.multiPaymentTransactionData.getReference());
} }
@Override @Override
public void orphanReferencesAndFees() throws DataException { public void orphanReferencesAndFees() throws DataException {
// Wrap and delegate reference processing to Payment class. Always revert recipients' last references regardless of asset. // 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(), new Payment(this.repository).orphanReferencesAndFees(this.multiPaymentTransactionData.getSenderPublicKey(), this.multiPaymentTransactionData.getPayments(),
multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature(), multiPaymentTransactionData.getReference(), true); this.multiPaymentTransactionData.getFee(), this.multiPaymentTransactionData.getSignature(), this.multiPaymentTransactionData.getReference(), true);
} }
} }

View File

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

View File

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

View File

@ -1,11 +1,9 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.group.GroupData; import org.qortal.data.group.GroupData;
@ -18,7 +16,9 @@ import org.qortal.repository.Repository;
public class RemoveGroupAdminTransaction extends Transaction { public class RemoveGroupAdminTransaction extends Transaction {
// Properties // Properties
private RemoveGroupAdminTransactionData removeGroupAdminTransactionData; private RemoveGroupAdminTransactionData removeGroupAdminTransactionData;
private Account adminAccount = null;
// Constructors // Constructors
@ -31,53 +31,34 @@ public class RemoveGroupAdminTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList(); return Collections.singletonList(this.removeGroupAdminTransactionData.getAdmin());
}
@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;
} }
// Navigation // Navigation
public Account getOwner() throws DataException { public Account getOwner() {
return new PublicKeyAccount(this.repository, this.removeGroupAdminTransactionData.getOwnerPublicKey()); return this.getCreator();
} }
public Account getAdmin() throws DataException { public Account getAdmin() {
return new Account(this.repository, this.removeGroupAdminTransactionData.getAdmin()); if (this.adminAccount == null)
this.adminAccount = new Account(this.repository, this.removeGroupAdminTransactionData.getAdmin());
return this.adminAccount;
} }
// Processing // Processing
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
int groupId = this.removeGroupAdminTransactionData.getGroupId();
// Check admin address is valid // Check admin address is valid
if (!Crypto.isValidAddress(removeGroupAdminTransactionData.getAdmin())) if (!Crypto.isValidAddress(this.removeGroupAdminTransactionData.getAdmin()))
return ValidationResult.INVALID_ADDRESS; return ValidationResult.INVALID_ADDRESS;
GroupData groupData = this.repository.getGroupRepository().fromGroupId(removeGroupAdminTransactionData.getGroupId()); GroupData groupData = this.repository.getGroupRepository().fromGroupId(groupId);
// Check group exists // Check group exists
if (groupData == null) if (groupData == null)
@ -92,15 +73,11 @@ public class RemoveGroupAdminTransaction extends Transaction {
Account admin = getAdmin(); Account admin = getAdmin();
// Check member is an admin // 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; 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 // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -109,21 +86,21 @@ public class RemoveGroupAdminTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Update Group adminship // Update Group adminship
Group group = new Group(this.repository, removeGroupAdminTransactionData.getGroupId()); Group group = new Group(this.repository, this.removeGroupAdminTransactionData.getGroupId());
group.demoteFromAdmin(removeGroupAdminTransactionData); group.demoteFromAdmin(this.removeGroupAdminTransactionData);
// Save this transaction with cached references to transactions that can help restore state // 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 @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Revert group adminship // Revert group adminship
Group group = new Group(this.repository, removeGroupAdminTransactionData.getGroupId()); Group group = new Group(this.repository, this.removeGroupAdminTransactionData.getGroupId());
group.undemoteFromAdmin(removeGroupAdminTransactionData); group.undemoteFromAdmin(this.removeGroupAdminTransactionData);
// Save this transaction with removed group references // 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; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -19,8 +18,13 @@ import org.qortal.transform.Transformer;
public class RewardShareTransaction extends Transaction { public class RewardShareTransaction extends Transaction {
public static final int MAX_SHARE = 100 * 100; // unscaled
// Properties // Properties
private RewardShareTransactionData rewardShareTransactionData; private RewardShareTransactionData rewardShareTransactionData;
private boolean haveCheckedForExistingRewardShare = false;
private RewardShareData existingRewardShareData = null;
// Constructors // Constructors
@ -33,42 +37,23 @@ public class RewardShareTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.emptyList(); return Collections.singletonList(this.rewardShareTransactionData.getRecipient());
}
@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;
} }
private RewardShareData getExistingRewardShare() throws DataException { private RewardShareData getExistingRewardShare() throws DataException {
// Look up any existing reward-share (using transaction's reward-share public key) if (this.haveCheckedForExistingRewardShare == false) {
RewardShareData existingRewardShareData = this.repository.getAccountRepository().getRewardShare(this.rewardShareTransactionData.getRewardSharePublicKey()); this.haveCheckedForExistingRewardShare = true;
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());
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) { private boolean doesRewardShareMatch(RewardShareData rewardShareData) {
@ -80,7 +65,7 @@ public class RewardShareTransaction extends Transaction {
// Navigation // Navigation
public PublicKeyAccount getMintingAccount() { public PublicKeyAccount getMintingAccount() {
return new PublicKeyAccount(this.repository, this.rewardShareTransactionData.getMinterPublicKey()); return this.getCreator();
} }
public Account getRecipient() { public Account getRecipient() {
@ -89,12 +74,11 @@ public class RewardShareTransaction extends Transaction {
// Processing // Processing
private static final BigDecimal MAX_SHARE = BigDecimal.valueOf(100).setScale(2);
@Override @Override
public ValidationResult isFeeValid() throws DataException { public ValidationResult isFeeValid() throws DataException {
// Look up any existing reward-share (using transaction's reward-share public key) // Look up any existing reward-share (using transaction's reward-share public key)
RewardShareData existingRewardShareData = this.getExistingRewardShare(); RewardShareData existingRewardShareData = this.getExistingRewardShare();
// If we have an existing reward-share then minter/recipient/reward-share-public-key should all match. // 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, // 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. // 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; return ValidationResult.INVALID_PUBLIC_KEY;
final boolean isRecipientAlsoMinter = getCreator().getAddress().equals(this.rewardShareTransactionData.getRecipient()); 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 // 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 ValidationResult.OK;
return super.isFeeValid(); return super.isFeeValid();
@ -114,7 +98,7 @@ public class RewardShareTransaction extends Transaction {
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Check reward share given to recipient. Negative is potentially OK to end a current reward-share. Zero also fine. // 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; return ValidationResult.INVALID_REWARD_SHARE_PERCENT;
// Check reward-share public key is correct length // Check reward-share public key is correct length
@ -127,7 +111,7 @@ public class RewardShareTransaction extends Transaction {
PublicKeyAccount creator = getCreator(); PublicKeyAccount creator = getCreator();
Account recipient = getRecipient(); 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) // Creator themselves needs to be allowed to mint (unless cancelling)
if (!isCancellingSharePercent && !creator.canMint()) 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) // Look up any existing reward-share (using transaction's reward-share public key)
RewardShareData existingRewardShareData = this.getExistingRewardShare(); RewardShareData existingRewardShareData = this.getExistingRewardShare();
// If we have an existing reward-share then minter/recipient/reward-share-public-key should all match. // 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, // 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. // 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 // Fee checking needed if not setting up new self-share
if (!(isRecipientAlsoMinter && existingRewardShareData == null)) if (!(isRecipientAlsoMinter && existingRewardShareData == null))
// Check creator has enough funds // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -180,24 +165,24 @@ public class RewardShareTransaction extends Transaction {
// Grab any previous share info for orphaning purposes // Grab any previous share info for orphaning purposes
RewardShareData rewardShareData = this.repository.getAccountRepository().getRewardShare(mintingAccount.getPublicKey(), RewardShareData rewardShareData = this.repository.getAccountRepository().getRewardShare(mintingAccount.getPublicKey(),
rewardShareTransactionData.getRecipient()); this.rewardShareTransactionData.getRecipient());
if (rewardShareData != null) if (rewardShareData != null)
rewardShareTransactionData.setPreviousSharePercent(rewardShareData.getSharePercent()); this.rewardShareTransactionData.setPreviousSharePercent(rewardShareData.getSharePercent());
// Save this transaction, with previous share info // 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 // Negative share is actually a request to delete existing reward-share
if (isSharePercentNegative) { if (isSharePercentNegative) {
this.repository.getAccountRepository().delete(mintingAccount.getPublicKey(), rewardShareTransactionData.getRecipient()); this.repository.getAccountRepository().delete(mintingAccount.getPublicKey(), this.rewardShareTransactionData.getRecipient());
} else { } else {
// Save reward-share info // Save reward-share info
rewardShareData = new RewardShareData(mintingAccount.getPublicKey(), mintingAccount.getAddress(), rewardShareData = new RewardShareData(mintingAccount.getPublicKey(), mintingAccount.getAddress(),
rewardShareTransactionData.getRecipient(), rewardShareTransactionData.getRewardSharePublicKey(), this.rewardShareTransactionData.getRecipient(), this.rewardShareTransactionData.getRewardSharePublicKey(),
rewardShareTransactionData.getSharePercent()); this.rewardShareTransactionData.getSharePercent());
this.repository.getAccountRepository().save(rewardShareData); this.repository.getAccountRepository().save(rewardShareData);
} }
} }
@ -207,9 +192,9 @@ public class RewardShareTransaction extends Transaction {
super.processReferencesAndFees(); 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 // 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) if (recipient.getLastReference() == null)
recipient.setLastReference(rewardShareTransactionData.getSignature()); recipient.setLastReference(this.rewardShareTransactionData.getSignature());
} }
@Override @Override
@ -217,21 +202,21 @@ public class RewardShareTransaction extends Transaction {
// Revert // Revert
PublicKeyAccount mintingAccount = getMintingAccount(); PublicKeyAccount mintingAccount = getMintingAccount();
if (rewardShareTransactionData.getPreviousSharePercent() != null) { if (this.rewardShareTransactionData.getPreviousSharePercent() != null) {
// Revert previous sharing arrangement // Revert previous sharing arrangement
RewardShareData rewardShareData = new RewardShareData(mintingAccount.getPublicKey(), mintingAccount.getAddress(), RewardShareData rewardShareData = new RewardShareData(mintingAccount.getPublicKey(), mintingAccount.getAddress(),
rewardShareTransactionData.getRecipient(), rewardShareTransactionData.getRewardSharePublicKey(), this.rewardShareTransactionData.getRecipient(), this.rewardShareTransactionData.getRewardSharePublicKey(),
rewardShareTransactionData.getPreviousSharePercent()); this.rewardShareTransactionData.getPreviousSharePercent());
this.repository.getAccountRepository().save(rewardShareData); this.repository.getAccountRepository().save(rewardShareData);
} else { } else {
// No previous arrangement so simply delete // 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 // Save this transaction, with removed previous share info
rewardShareTransactionData.setPreviousSharePercent(null); this.rewardShareTransactionData.setPreviousSharePercent(null);
this.repository.getTransactionRepository().save(rewardShareTransactionData); this.repository.getTransactionRepository().save(this.rewardShareTransactionData);
} }
@Override @Override
@ -239,8 +224,8 @@ public class RewardShareTransaction extends Transaction {
super.orphanReferencesAndFees(); super.orphanReferencesAndFees();
// If recipient didn't have a last-reference prior to this transaction then remove it // If recipient didn't have a last-reference prior to this transaction then remove it
Account recipient = new Account(this.repository, rewardShareTransactionData.getRecipient()); Account recipient = new Account(this.repository, this.rewardShareTransactionData.getRecipient());
if (Arrays.equals(recipient.getLastReference(), rewardShareTransactionData.getSignature())) if (Arrays.equals(recipient.getLastReference(), this.rewardShareTransactionData.getSignature()))
recipient.setLastReference(null); recipient.setLastReference(null);
} }

View File

@ -1,11 +1,9 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.math.BigDecimal; import java.util.Collections;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.data.naming.NameData; import org.qortal.data.naming.NameData;
import org.qortal.data.transaction.SellNameTransactionData; import org.qortal.data.transaction.SellNameTransactionData;
@ -19,7 +17,7 @@ import com.google.common.base.Utf8;
public class SellNameTransaction extends Transaction { 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. */ /** 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 // Properties
private SellNameTransactionData sellNameTransactionData; private SellNameTransactionData sellNameTransactionData;
@ -35,51 +33,32 @@ public class SellNameTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() { public List<String> getRecipientAddresses() throws DataException {
return new ArrayList<>(); return Collections.emptyList();
}
@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;
} }
// Navigation // Navigation
public Account getOwner() throws DataException { public Account getOwner() {
return new PublicKeyAccount(this.repository, this.sellNameTransactionData.getOwnerPublicKey()); return this.getCreator();
} }
// Processing // Processing
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
String name = this.sellNameTransactionData.getName();
// Check name size bounds // Check name size bounds
int nameLength = Utf8.encodedLength(sellNameTransactionData.getName()); int nameLength = Utf8.encodedLength(name);
if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE) if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH; return ValidationResult.INVALID_NAME_LENGTH;
// Check name is lowercase // Check name is lowercase
if (!sellNameTransactionData.getName().equals(sellNameTransactionData.getName().toLowerCase())) if (!name.equals(name.toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE; return ValidationResult.NAME_NOT_LOWER_CASE;
NameData nameData = this.repository.getNameRepository().fromName(sellNameTransactionData.getName()); NameData nameData = this.repository.getNameRepository().fromName(name);
// Check name exists // Check name exists
if (nameData == null) if (nameData == null)
@ -95,19 +74,15 @@ public class SellNameTransaction extends Transaction {
return ValidationResult.INVALID_NAME_OWNER; return ValidationResult.INVALID_NAME_OWNER;
// Check amount is positive // Check amount is positive
if (sellNameTransactionData.getAmount().compareTo(BigDecimal.ZERO) <= 0) if (this.sellNameTransactionData.getAmount() <= 0)
return ValidationResult.NEGATIVE_AMOUNT; return ValidationResult.NEGATIVE_AMOUNT;
// Check amount within bounds // Check amount within bounds
if (sellNameTransactionData.getAmount().compareTo(MAX_AMOUNT) >= 0) if (this.sellNameTransactionData.getAmount() >= MAX_AMOUNT)
return ValidationResult.INVALID_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 // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -116,15 +91,15 @@ public class SellNameTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Sell Name // Sell Name
Name name = new Name(this.repository, sellNameTransactionData.getName()); Name name = new Name(this.repository, this.sellNameTransactionData.getName());
name.sell(sellNameTransactionData); name.sell(this.sellNameTransactionData);
} }
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Revert name // Revert name
Name name = new Name(this.repository, sellNameTransactionData.getName()); Name name = new Name(this.repository, this.sellNameTransactionData.getName());
name.unsell(sellNameTransactionData); name.unsell(this.sellNameTransactionData);
} }
} }

View File

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

View File

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

View File

@ -1,12 +1,9 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.data.PaymentData; import org.qortal.data.PaymentData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.data.transaction.TransferAssetTransactionData; import org.qortal.data.transaction.TransferAssetTransactionData;
@ -17,7 +14,9 @@ import org.qortal.repository.Repository;
public class TransferAssetTransaction extends Transaction { public class TransferAssetTransaction extends Transaction {
// Properties // Properties
private TransferAssetTransactionData transferAssetTransactionData; private TransferAssetTransactionData transferAssetTransactionData;
private PaymentData paymentData = null;
// Constructors // Constructors
@ -30,94 +29,63 @@ public class TransferAssetTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(new Account(this.repository, transferAssetTransactionData.getRecipient())); return Collections.singletonList(this.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;
} }
// Navigation // Navigation
public Account getSender() throws DataException { public Account getSender() {
return new PublicKeyAccount(this.repository, this.transferAssetTransactionData.getSenderPublicKey()); return this.getCreator();
} }
// Processing // Processing
private PaymentData getPaymentData() { private PaymentData getPaymentData() {
return new PaymentData(transferAssetTransactionData.getRecipient(), transferAssetTransactionData.getAssetId(), if (this.paymentData == null)
transferAssetTransactionData.getAmount()); this.paymentData = new PaymentData(this.transferAssetTransactionData.getRecipient(), this.transferAssetTransactionData.getAssetId(),
this.transferAssetTransactionData.getAmount());
return this.paymentData;
} }
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Wrap asset transfer as a payment and delegate final payment checks to Payment class // 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 @Override
public ValidationResult isProcessable() throws DataException { public ValidationResult isProcessable() throws DataException {
// Wrap asset transfer as a payment and delegate final processable checks to Payment class // 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 @Override
public void process() throws DataException { 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. // Wrap asset transfer as a payment and delegate processing to Payment class.
new Payment(this.repository).process(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(), new Payment(this.repository).process(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), this.transferAssetTransactionData.getSignature());
transferAssetTransactionData.getSignature());
} }
@Override @Override
public void processReferencesAndFees() throws DataException { 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. // 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(), new Payment(this.repository).processReferencesAndFees(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), this.transferAssetTransactionData.getFee(),
transferAssetTransactionData.getSignature(), false); this.transferAssetTransactionData.getSignature(), false);
} }
@Override @Override
public void orphan() throws DataException { 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. // Wrap asset transfer as a payment and delegate processing to Payment class.
new Payment(this.repository).orphan(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(), new Payment(this.repository).orphan(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(),
transferAssetTransactionData.getSignature(), transferAssetTransactionData.getReference()); this.transferAssetTransactionData.getSignature(), this.transferAssetTransactionData.getReference());
} }
@Override @Override
public void orphanReferencesAndFees() throws DataException { 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. // 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(), new Payment(this.repository).orphanReferencesAndFees(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), this.transferAssetTransactionData.getFee(),
transferAssetTransactionData.getSignature(), transferAssetTransactionData.getReference(), false); this.transferAssetTransactionData.getSignature(), this.transferAssetTransactionData.getReference(), false);
} }
} }

View File

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

View File

@ -1,6 +1,5 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -32,42 +31,14 @@ public class UpdateAssetTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(getNewOwner()); return Collections.singletonList(this.updateAssetTransactionData.getNewOwner());
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getOwner().getAddress()))
return true;
if (address.equals(this.getNewOwner().getAddress()))
return true;
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
if (address.equals(this.getOwner().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
} }
// Navigation // Navigation
public PublicKeyAccount getOwner() throws DataException { public PublicKeyAccount getOwner() {
return new PublicKeyAccount(this.repository, this.updateAssetTransactionData.getOwnerPublicKey()); return this.getCreator();
}
public Account getNewOwner() throws DataException {
return new Account(this.repository, this.updateAssetTransactionData.getNewOwner());
} }
// Processing // Processing
@ -75,37 +46,33 @@ public class UpdateAssetTransaction extends Transaction {
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Check asset actually exists // Check asset actually exists
AssetData assetData = this.repository.getAssetRepository().fromAssetId(updateAssetTransactionData.getAssetId()); AssetData assetData = this.repository.getAssetRepository().fromAssetId(this.updateAssetTransactionData.getAssetId());
if (assetData == null) if (assetData == null)
return ValidationResult.ASSET_DOES_NOT_EXIST; return ValidationResult.ASSET_DOES_NOT_EXIST;
// Check new owner address is valid // Check new owner address is valid
if (!Crypto.isValidAddress(updateAssetTransactionData.getNewOwner())) if (!Crypto.isValidAddress(this.updateAssetTransactionData.getNewOwner()))
return ValidationResult.INVALID_ADDRESS; return ValidationResult.INVALID_ADDRESS;
// Check new description size bounds. Note: zero length means DO NOT CHANGE description // 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) if (newDescriptionLength > Asset.MAX_DESCRIPTION_SIZE)
return ValidationResult.INVALID_DATA_LENGTH; return ValidationResult.INVALID_DATA_LENGTH;
// Check new data size bounds. Note: zero length means DO NOT CHANGE data // 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) if (newDataLength > Asset.MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH; return ValidationResult.INVALID_DATA_LENGTH;
// As this transaction type could require approval, check txGroupId // As this transaction type could require approval, check txGroupId
// matches groupID at creation // matches groupID at creation
if (assetData.getCreationGroupId() != updateAssetTransactionData.getTxGroupId()) if (assetData.getCreationGroupId() != this.updateAssetTransactionData.getTxGroupId())
return ValidationResult.TX_GROUP_ID_MISMATCH; return ValidationResult.TX_GROUP_ID_MISMATCH;
// Check fee is positive
if (updateAssetTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
Account currentOwner = getOwner(); Account currentOwner = getOwner();
// Check current owner has enough funds // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -115,7 +82,7 @@ public class UpdateAssetTransaction extends Transaction {
public ValidationResult isProcessable() throws DataException { public ValidationResult isProcessable() throws DataException {
// Check transaction's public key matches asset's current owner // Check transaction's public key matches asset's current owner
Account currentOwner = getOwner(); 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())) if (!assetData.getOwner().equals(currentOwner.getAddress()))
return ValidationResult.INVALID_ASSET_OWNER; return ValidationResult.INVALID_ASSET_OWNER;
@ -126,21 +93,21 @@ public class UpdateAssetTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Update Asset // Update Asset
Asset asset = new Asset(this.repository, updateAssetTransactionData.getAssetId()); Asset asset = new Asset(this.repository, this.updateAssetTransactionData.getAssetId());
asset.update(updateAssetTransactionData); asset.update(this.updateAssetTransactionData);
// Save this transaction, with updated "name reference" to previous transaction that updated name // 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 @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Revert asset // Revert asset
Asset asset = new Asset(this.repository, updateAssetTransactionData.getAssetId()); Asset asset = new Asset(this.repository, this.updateAssetTransactionData.getAssetId());
asset.revert(updateAssetTransactionData); asset.revert(this.updateAssetTransactionData);
// Save this transaction, with removed "name reference" to previous transaction that updated name // 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; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.group.GroupData; import org.qortal.data.group.GroupData;
@ -33,38 +31,14 @@ public class UpdateGroupTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(getNewOwner()); return Collections.singletonList(this.updateGroupTransactionData.getNewOwner());
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getOwner().getAddress()))
return true;
if (address.equals(this.getNewOwner().getAddress()))
return true;
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
if (address.equals(this.getOwner().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
} }
// Navigation // Navigation
public Account getOwner() throws DataException { public Account getOwner() {
return new PublicKeyAccount(this.repository, this.updateGroupTransactionData.getOwnerPublicKey()); return this.getCreator();
} }
public Account getNewOwner() throws DataException { public Account getNewOwner() throws DataException {
@ -76,46 +50,42 @@ public class UpdateGroupTransaction extends Transaction {
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Check new owner address is valid // Check new owner address is valid
if (!Crypto.isValidAddress(updateGroupTransactionData.getNewOwner())) if (!Crypto.isValidAddress(this.updateGroupTransactionData.getNewOwner()))
return ValidationResult.INVALID_ADDRESS; return ValidationResult.INVALID_ADDRESS;
// Check new approval threshold is valid // Check new approval threshold is valid
if (updateGroupTransactionData.getNewApprovalThreshold() == null) if (this.updateGroupTransactionData.getNewApprovalThreshold() == null)
return ValidationResult.INVALID_GROUP_APPROVAL_THRESHOLD; return ValidationResult.INVALID_GROUP_APPROVAL_THRESHOLD;
// Check min/max block delay values // Check min/max block delay values
if (updateGroupTransactionData.getNewMinimumBlockDelay() < 0) if (this.updateGroupTransactionData.getNewMinimumBlockDelay() < 0)
return ValidationResult.INVALID_GROUP_BLOCK_DELAY; return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
if (updateGroupTransactionData.getNewMaximumBlockDelay() < 1) if (this.updateGroupTransactionData.getNewMaximumBlockDelay() < 1)
return ValidationResult.INVALID_GROUP_BLOCK_DELAY; return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
if (updateGroupTransactionData.getNewMaximumBlockDelay() < updateGroupTransactionData.getNewMinimumBlockDelay()) if (this.updateGroupTransactionData.getNewMaximumBlockDelay() < this.updateGroupTransactionData.getNewMinimumBlockDelay())
return ValidationResult.INVALID_GROUP_BLOCK_DELAY; return ValidationResult.INVALID_GROUP_BLOCK_DELAY;
// Check new description size bounds // 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) if (newDescriptionLength < 1 || newDescriptionLength > Group.MAX_DESCRIPTION_SIZE)
return ValidationResult.INVALID_DESCRIPTION_LENGTH; 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 // Check group exists
if (groupData == null) if (groupData == null)
return ValidationResult.GROUP_DOES_NOT_EXIST; return ValidationResult.GROUP_DOES_NOT_EXIST;
// As this transaction type could require approval, check txGroupId matches groupID at creation // 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; return ValidationResult.TX_GROUP_ID_MISMATCH;
Account owner = getOwner(); Account owner = getOwner();
// Check fee is positive
if (updateGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check creator has enough funds // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -123,7 +93,7 @@ public class UpdateGroupTransaction extends Transaction {
@Override @Override
public ValidationResult isProcessable() throws DataException { 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(); Account owner = getOwner();
// Check transaction's public key matches group's current owner // Check transaction's public key matches group's current owner
@ -133,31 +103,30 @@ public class UpdateGroupTransaction extends Transaction {
Account newOwner = getNewOwner(); Account newOwner = getNewOwner();
// Check new owner is not banned // 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.BANNED_FROM_GROUP;
return ValidationResult.OK; return ValidationResult.OK;
} }
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Update Group // Update Group
Group group = new Group(this.repository, updateGroupTransactionData.getGroupId()); Group group = new Group(this.repository, this.updateGroupTransactionData.getGroupId());
group.updateGroup(updateGroupTransactionData); group.updateGroup(this.updateGroupTransactionData);
// Save this transaction, now with updated "group reference" to previous transaction that updated group // 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 @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Revert Group update // Revert Group update
Group group = new Group(this.repository, updateGroupTransactionData.getGroupId()); Group group = new Group(this.repository, this.updateGroupTransactionData.getGroupId());
group.unupdateGroup(updateGroupTransactionData); group.unupdateGroup(this.updateGroupTransactionData);
// Save this transaction, now with removed "group reference" // 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; package org.qortal.transaction;
import java.math.BigDecimal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.naming.NameData; import org.qortal.data.naming.NameData;
@ -33,41 +31,17 @@ public class UpdateNameTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(getNewOwner()); return Collections.singletonList(this.updateNameTransactionData.getNewOwner());
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getOwner().getAddress()))
return true;
if (address.equals(this.getNewOwner().getAddress()))
return true;
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
if (address.equals(this.getOwner().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
} }
// Navigation // Navigation
public Account getOwner() throws DataException { public Account getOwner() {
return new PublicKeyAccount(this.repository, this.updateNameTransactionData.getOwnerPublicKey()); return this.getCreator();
} }
public Account getNewOwner() throws DataException { public Account getNewOwner() {
return new Account(this.repository, this.updateNameTransactionData.getNewOwner()); return new Account(this.repository, this.updateNameTransactionData.getNewOwner());
} }
@ -75,42 +49,40 @@ public class UpdateNameTransaction extends Transaction {
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
String name = this.updateNameTransactionData.getName();
// Check new owner address is valid // Check new owner address is valid
if (!Crypto.isValidAddress(updateNameTransactionData.getNewOwner())) if (!Crypto.isValidAddress(this.updateNameTransactionData.getNewOwner()))
return ValidationResult.INVALID_ADDRESS; return ValidationResult.INVALID_ADDRESS;
// Check name size bounds // Check name size bounds
int nameLength = Utf8.encodedLength(updateNameTransactionData.getName()); int nameLength = Utf8.encodedLength(name);
if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE) if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH; return ValidationResult.INVALID_NAME_LENGTH;
// Check new data size bounds // 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) if (newDataLength < 1 || newDataLength > Name.MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH; return ValidationResult.INVALID_DATA_LENGTH;
// Check name is lowercase // Check name is lowercase
if (!updateNameTransactionData.getName().equals(updateNameTransactionData.getName().toLowerCase())) if (!name.equals(name.toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE; return ValidationResult.NAME_NOT_LOWER_CASE;
NameData nameData = this.repository.getNameRepository().fromName(updateNameTransactionData.getName()); NameData nameData = this.repository.getNameRepository().fromName(name);
// Check name exists // Check name exists
if (nameData == null) if (nameData == null)
return ValidationResult.NAME_DOES_NOT_EXIST; return ValidationResult.NAME_DOES_NOT_EXIST;
// As this transaction type could require approval, check txGroupId matches groupID at creation // 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; return ValidationResult.TX_GROUP_ID_MISMATCH;
Account owner = getOwner(); Account owner = getOwner();
// Check fee is positive // Check owner has enough funds
if (updateNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) if (owner.getConfirmedBalance(Asset.QORT) < this.updateNameTransactionData.getFee())
return ValidationResult.NEGATIVE_FEE;
// Check issuer has enough funds
if (owner.getConfirmedBalance(Asset.QORT).compareTo(updateNameTransactionData.getFee()) < 0)
return ValidationResult.NO_BALANCE; return ValidationResult.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -118,7 +90,7 @@ public class UpdateNameTransaction extends Transaction {
@Override @Override
public ValidationResult isProcessable() throws DataException { 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 // Check name isn't currently for sale
if (nameData.getIsForSale()) if (nameData.getIsForSale())
@ -136,21 +108,21 @@ public class UpdateNameTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Update Name // Update Name
Name name = new Name(this.repository, updateNameTransactionData.getName()); Name name = new Name(this.repository, this.updateNameTransactionData.getName());
name.update(updateNameTransactionData); name.update(this.updateNameTransactionData);
// Save this transaction, now with updated "name reference" to previous transaction that updated name // 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 @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Revert name // Revert name
Name name = new Name(this.repository, updateNameTransactionData.getName()); Name name = new Name(this.repository, this.updateNameTransactionData.getName());
name.revert(updateNameTransactionData); name.revert(this.updateNameTransactionData);
// Save this transaction, now with removed "name reference" // 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; package org.qortal.transaction;
import java.math.BigDecimal; import java.util.Collections;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.data.transaction.VoteOnPollTransactionData; import org.qortal.data.transaction.VoteOnPollTransactionData;
@ -39,72 +37,55 @@ public class VoteOnPollTransaction extends Transaction {
// More information // More information
@Override @Override
public List<Account> getRecipientAccounts() { public List<String> getRecipientAddresses() throws DataException {
return new ArrayList<>(); return Collections.emptyList();
}
@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 // Navigation
public Account getVoter() throws DataException { public Account getVoter() {
return new PublicKeyAccount(this.repository, voteOnPollTransactionData.getVoterPublicKey()); return this.getCreator();
} }
// Processing // Processing
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
String pollName = this.voteOnPollTransactionData.getPollName();
// Check name size bounds // Check name size bounds
int pollNameLength = Utf8.encodedLength(voteOnPollTransactionData.getPollName()); int pollNameLength = Utf8.encodedLength(pollName);
if (pollNameLength < 1 || pollNameLength > Poll.MAX_NAME_SIZE) if (pollNameLength < 1 || pollNameLength > Poll.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH; return ValidationResult.INVALID_NAME_LENGTH;
// Check poll name is lowercase // Check poll name is lowercase
if (!voteOnPollTransactionData.getPollName().equals(voteOnPollTransactionData.getPollName().toLowerCase())) if (!pollName.equals(pollName.toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE; return ValidationResult.NAME_NOT_LOWER_CASE;
VotingRepository votingRepository = this.repository.getVotingRepository(); VotingRepository votingRepository = this.repository.getVotingRepository();
// Check poll exists // Check poll exists
PollData pollData = votingRepository.fromPollName(voteOnPollTransactionData.getPollName()); PollData pollData = votingRepository.fromPollName(pollName);
if (pollData == null) if (pollData == null)
return ValidationResult.POLL_DOES_NOT_EXIST; return ValidationResult.POLL_DOES_NOT_EXIST;
// Check poll option index is within bounds // Check poll option index is within bounds
List<PollOptionData> pollOptions = pollData.getPollOptions(); List<PollOptionData> pollOptions = pollData.getPollOptions();
int optionIndex = voteOnPollTransactionData.getOptionIndex(); int optionIndex = this.voteOnPollTransactionData.getOptionIndex();
if (optionIndex < 0 || optionIndex > pollOptions.size() - 1) if (optionIndex < 0 || optionIndex > pollOptions.size() - 1)
return ValidationResult.POLL_OPTION_DOES_NOT_EXIST; return ValidationResult.POLL_OPTION_DOES_NOT_EXIST;
// Check if vote already exists // 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) if (voteOnPollData != null && voteOnPollData.getOptionIndex() == optionIndex)
return ValidationResult.ALREADY_VOTED_FOR_THAT_OPTION; 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 // Check reference is correct
Account voter = getVoter(); Account voter = getVoter();
// Check voter has enough funds // 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.NO_BALANCE;
return ValidationResult.OK; return ValidationResult.OK;
@ -112,27 +93,28 @@ public class VoteOnPollTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
String pollName = this.voteOnPollTransactionData.getPollName();
Account voter = getVoter(); Account voter = getVoter();
VotingRepository votingRepository = this.repository.getVotingRepository(); VotingRepository votingRepository = this.repository.getVotingRepository();
// Check for previous vote so we can save option in case of orphaning // Check for previous vote so we can save option in case of orphaning
VoteOnPollData previousVoteOnPollData = votingRepository.getVote(voteOnPollTransactionData.getPollName(), VoteOnPollData previousVoteOnPollData = votingRepository.getVote(pollName, this.voteOnPollTransactionData.getVoterPublicKey());
voteOnPollTransactionData.getVoterPublicKey());
if (previousVoteOnPollData != null) { if (previousVoteOnPollData != null) {
voteOnPollTransactionData.setPreviousOptionIndex(previousVoteOnPollData.getOptionIndex()); voteOnPollTransactionData.setPreviousOptionIndex(previousVoteOnPollData.getOptionIndex());
LOGGER.trace("Previous vote by " + voter.getAddress() + " on poll \"" + voteOnPollTransactionData.getPollName() + "\" was option index " LOGGER.trace(() -> String.format("Previous vote by %s on poll \"%s\" was option index %d",
+ previousVoteOnPollData.getOptionIndex()); voter.getAddress(), pollName, previousVoteOnPollData.getOptionIndex()));
} }
// Save this transaction, now with possible previous vote // Save this transaction, now with possible previous vote
this.repository.getTransactionRepository().save(voteOnPollTransactionData); this.repository.getTransactionRepository().save(voteOnPollTransactionData);
// Apply vote to poll // Apply vote to poll
LOGGER.trace("Vote by " + voter.getAddress() + " on poll \"" + voteOnPollTransactionData.getPollName() + "\" with option index " LOGGER.trace(() -> String.format("Vote by %s on poll \"%s\" with option index %d",
+ voteOnPollTransactionData.getOptionIndex()); voter.getAddress(), pollName, this.voteOnPollTransactionData.getOptionIndex()));
VoteOnPollData newVoteOnPollData = new VoteOnPollData(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey(), VoteOnPollData newVoteOnPollData = new VoteOnPollData(pollName, this.voteOnPollTransactionData.getVoterPublicKey(),
voteOnPollTransactionData.getOptionIndex()); this.voteOnPollTransactionData.getOptionIndex());
votingRepository.save(newVoteOnPollData); votingRepository.save(newVoteOnPollData);
} }
@ -142,22 +124,24 @@ public class VoteOnPollTransaction extends Transaction {
// Does this transaction have previous vote info? // Does this transaction have previous vote info?
VotingRepository votingRepository = this.repository.getVotingRepository(); VotingRepository votingRepository = this.repository.getVotingRepository();
Integer previousOptionIndex = voteOnPollTransactionData.getPreviousOptionIndex(); Integer previousOptionIndex = this.voteOnPollTransactionData.getPreviousOptionIndex();
if (previousOptionIndex != null) { if (previousOptionIndex != null) {
// Reinstate previous vote // Reinstate previous vote
LOGGER.trace(() -> String.format("Reinstating previous vote by %s on poll \"%s\" with option index %d", voter.getAddress(), voteOnPollTransactionData.getPollName(), previousOptionIndex)); LOGGER.trace(() -> String.format("Reinstating previous vote by %s on poll \"%s\" with option index %d",
VoteOnPollData previousVoteOnPollData = new VoteOnPollData(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey(), voter.getAddress(), this.voteOnPollTransactionData.getPollName(), previousOptionIndex));
VoteOnPollData previousVoteOnPollData = new VoteOnPollData(this.voteOnPollTransactionData.getPollName(), this.voteOnPollTransactionData.getVoterPublicKey(),
previousOptionIndex); previousOptionIndex);
votingRepository.save(previousVoteOnPollData); votingRepository.save(previousVoteOnPollData);
} else { } else {
// Delete vote // Delete vote
LOGGER.trace(() -> String.format("Deleting vote by %s on poll \"%s\" with option index %d", voter.getAddress(), voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getOptionIndex())); LOGGER.trace(() -> String.format("Deleting vote by %s on poll \"%s\" with option index %d",
votingRepository.delete(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey()); voter.getAddress(), this.voteOnPollTransactionData.getPollName(), this.voteOnPollTransactionData.getOptionIndex()));
votingRepository.delete(this.voteOnPollTransactionData.getPollName(), this.voteOnPollTransactionData.getVoterPublicKey());
} }
// Save this transaction, with removed previous vote info // Save this transaction, with removed previous vote info
voteOnPollTransactionData.setPreviousOptionIndex(null); this.voteOnPollTransactionData.setPreviousOptionIndex(null);
this.repository.getTransactionRepository().save(voteOnPollTransactionData); this.repository.getTransactionRepository().save(this.voteOnPollTransactionData);
} }
} }

View File

@ -2,10 +2,8 @@ package org.qortal.transform;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.json.simple.JSONObject;
import org.qortal.data.PaymentData; import org.qortal.data.PaymentData;
import org.qortal.utils.Serialization; import org.qortal.utils.Serialization;
@ -15,8 +13,6 @@ public class PaymentTransformer extends Transformer {
// Property lengths // Property lengths
private static final int RECIPIENT_LENGTH = ADDRESS_LENGTH; 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; 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(); long assetId = byteBuffer.getLong();
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer, AMOUNT_LENGTH); long amount = byteBuffer.getLong();
return new PaymentData(recipient, assetId, amount); return new PaymentData(recipient, assetId, amount);
} }
@ -42,7 +38,7 @@ public class PaymentTransformer extends Transformer {
bytes.write(Longs.toByteArray(paymentData.getAssetId())); bytes.write(Longs.toByteArray(paymentData.getAssetId()));
Serialization.serializeBigDecimal(bytes, paymentData.getAmount(), AMOUNT_LENGTH); bytes.write(Longs.toByteArray(paymentData.getAmount()));
return bytes.toByteArray(); return bytes.toByteArray();
} catch (IOException | ClassCastException e) { } 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 BYTE_LENGTH = 1;
public static final int INT_LENGTH = 4; public static final int INT_LENGTH = 4;
public static final int LONG_LENGTH = 8; 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 // Raw, not Base58-encoded
public static final int ADDRESS_LENGTH = 25; 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.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; 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 TRANSACTIONS_SIGNATURE_LENGTH = SIGNATURE_LENGTH;
private static final int MINTER_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 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 MINTER_PUBLIC_KEY_LENGTH = PUBLIC_KEY_LENGTH;
private static final int TRANSACTION_COUNT_LENGTH = INT_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; + TRANSACTIONS_SIGNATURE_LENGTH + MINTER_SIGNATURE_LENGTH + TRANSACTION_COUNT_LENGTH;
public static final int BLOCK_SIGNATURE_LENGTH = MINTER_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_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 TRANSACTION_SIZE_LENGTH = INT_LENGTH; // per transaction
protected static final int AT_BYTES_LENGTH = INT_LENGTH; 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 AT_LENGTH = AT_FEES_LENGTH + AT_BYTES_LENGTH;
protected static final int ONLINE_ACCOUNTS_COUNT_LENGTH = INT_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_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 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. * 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]; byte[] minterSignature = new byte[MINTER_SIGNATURE_LENGTH];
byteBuffer.get(minterSignature); byteBuffer.get(minterSignature);
BigDecimal totalFees = BigDecimal.ZERO.setScale(8); long totalFees = 0;
int atCount = 0; int atCount = 0;
BigDecimal atFees = BigDecimal.ZERO.setScale(8); long atFees = 0;
List<ATStateData> atStates = new ArrayList<>(); List<ATStateData> atStates = new ArrayList<>();
int atBytesLength = byteBuffer.getInt(); int atBytesLength = byteBuffer.getInt();
@ -130,9 +130,10 @@ public class BlockTransformer extends Transformer {
byte[] stateHash = new byte[SHA256_LENGTH]; byte[] stateHash = new byte[SHA256_LENGTH];
atByteBuffer.get(stateHash); atByteBuffer.get(stateHash);
BigDecimal fees = Serialization.deserializeBigDecimal(atByteBuffer); long fees = atByteBuffer.getLong();
// Add this AT's fees to our total // Add this AT's fees to our total
atFees = atFees.add(fees); atFees += fees;
atStates.add(new ATStateData(atAddress, stateHash, fees)); atStates.add(new ATStateData(atAddress, stateHash, fees));
} }
@ -141,7 +142,7 @@ public class BlockTransformer extends Transformer {
atCount = atStates.size(); atCount = atStates.size();
// Add AT fees to totalFees // Add AT fees to totalFees
totalFees = totalFees.add(atFees); totalFees += atFees;
int transactionCount = byteBuffer.getInt(); int transactionCount = byteBuffer.getInt();
@ -166,7 +167,7 @@ public class BlockTransformer extends Transformer {
TransactionData transactionData = TransactionTransformer.fromBytes(transactionBytes); TransactionData transactionData = TransactionTransformer.fromBytes(transactionBytes);
transactions.add(transactionData); transactions.add(transactionData);
totalFees = totalFees.add(transactionData.getFee()); totalFees += transactionData.getFee();
} }
// Online accounts info? // Online accounts info?
@ -265,7 +266,7 @@ public class BlockTransformer extends Transformer {
for (ATStateData atStateData : block.getATStates()) { for (ATStateData atStateData : block.getATStates()) {
bytes.write(Base58.decode(atStateData.getATAddress())); bytes.write(Base58.decode(atStateData.getATAddress()));
bytes.write(atStateData.getStateHash()); bytes.write(atStateData.getStateHash());
Serialization.serializeBigDecimal(bytes, atStateData.getFees()); bytes.write(Longs.toByteArray(atStateData.getFees()));
} }
// Transactions // Transactions

View File

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

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