mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-18 13:14:59 +00:00
Work on assets/payments
* Created PaymentData transfer objects for (recipient, assetId, amount) tuples * Created corresponding Payment class for validating, processing and orphaning payment(s) * Modified OrderData to support isClosed for when an Order is cancelled so no more trades can occur * Migrated CancelOrderTransactions and MultiPaymentTransactions * Converted MessageTransactions, PaymentTransactions and TransferAssetTransactions to use new Payment class Can't use PaymentTransformer in PaymentTransformer or TransferAssetTransformer due to serialization differences.
This commit is contained in:
parent
2f8c160627
commit
9897981de1
34
src/data/PaymentData.java
Normal file
34
src/data/PaymentData.java
Normal file
@ -0,0 +1,34 @@
|
||||
package data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class PaymentData {
|
||||
|
||||
// Properties
|
||||
protected String recipient;
|
||||
protected long assetId;
|
||||
protected BigDecimal amount;
|
||||
|
||||
// Constructors
|
||||
|
||||
public PaymentData(String recipient, long assetId, BigDecimal amount) {
|
||||
this.recipient = recipient;
|
||||
this.assetId = assetId;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
// Getters/setters
|
||||
|
||||
public String getRecipient() {
|
||||
return this.recipient;
|
||||
}
|
||||
|
||||
public long getAssetId() {
|
||||
return this.assetId;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
}
|
@ -12,9 +12,10 @@ public class OrderData implements Comparable<OrderData> {
|
||||
private BigDecimal fulfilled;
|
||||
private BigDecimal price;
|
||||
private long timestamp;
|
||||
private boolean isClosed;
|
||||
|
||||
public OrderData(byte[] orderId, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal fulfilled, BigDecimal price,
|
||||
long timestamp) {
|
||||
long timestamp, boolean isClosed) {
|
||||
this.orderId = orderId;
|
||||
this.creatorPublicKey = creatorPublicKey;
|
||||
this.haveAssetId = haveAssetId;
|
||||
@ -23,10 +24,11 @@ public class OrderData implements Comparable<OrderData> {
|
||||
this.fulfilled = fulfilled;
|
||||
this.price = price;
|
||||
this.timestamp = timestamp;
|
||||
this.isClosed = isClosed;
|
||||
}
|
||||
|
||||
public OrderData(byte[] orderId, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal price, long timestamp) {
|
||||
this(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, BigDecimal.ZERO.setScale(8), price, timestamp);
|
||||
this(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, BigDecimal.ZERO.setScale(8), price, timestamp, false);
|
||||
}
|
||||
|
||||
public byte[] getOrderId() {
|
||||
@ -65,6 +67,14 @@ public class OrderData implements Comparable<OrderData> {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public boolean getIsClosed() {
|
||||
return this.isClosed;
|
||||
}
|
||||
|
||||
public void setIsClosed(boolean isClosed) {
|
||||
this.isClosed = isClosed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(OrderData orderData) {
|
||||
// Compare using prices
|
||||
|
32
src/data/transaction/CancelOrderTransactionData.java
Normal file
32
src/data/transaction/CancelOrderTransactionData.java
Normal file
@ -0,0 +1,32 @@
|
||||
package data.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import qora.transaction.Transaction;
|
||||
|
||||
public class CancelOrderTransactionData extends TransactionData {
|
||||
|
||||
// Properties
|
||||
private byte[] creatorPublicKey;
|
||||
private byte[] orderId;
|
||||
|
||||
// Constructors
|
||||
|
||||
public CancelOrderTransactionData(byte[] creatorPublicKey, byte[] orderId, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) {
|
||||
super(Transaction.TransactionType.CANCEL_ASSET_ORDER, fee, creatorPublicKey, timestamp, reference, signature);
|
||||
|
||||
this.creatorPublicKey = creatorPublicKey;
|
||||
this.orderId = orderId;
|
||||
}
|
||||
|
||||
// Getters/Setters
|
||||
|
||||
public byte[] getCreatorPublicKey() {
|
||||
return this.creatorPublicKey;
|
||||
}
|
||||
|
||||
public byte[] getOrderId() {
|
||||
return this.orderId;
|
||||
}
|
||||
|
||||
}
|
39
src/data/transaction/MultiPaymentTransactionData.java
Normal file
39
src/data/transaction/MultiPaymentTransactionData.java
Normal file
@ -0,0 +1,39 @@
|
||||
package data.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import data.PaymentData;
|
||||
import qora.transaction.Transaction;
|
||||
|
||||
public class MultiPaymentTransactionData extends TransactionData {
|
||||
|
||||
// Properties
|
||||
private byte[] senderPublicKey;
|
||||
private List<PaymentData> payments;
|
||||
|
||||
// Constructors
|
||||
|
||||
public MultiPaymentTransactionData(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) {
|
||||
super(Transaction.TransactionType.MULTIPAYMENT, fee, senderPublicKey, timestamp, reference, signature);
|
||||
|
||||
this.senderPublicKey = senderPublicKey;
|
||||
this.payments = payments;
|
||||
}
|
||||
|
||||
public MultiPaymentTransactionData(byte[] senderPublicKey, BigDecimal fee, long timestamp, byte[] reference) {
|
||||
this(senderPublicKey, new ArrayList<PaymentData>(), fee, timestamp, reference, null);
|
||||
}
|
||||
|
||||
// Getters/setters
|
||||
|
||||
public byte[] getSenderPublicKey() {
|
||||
return this.senderPublicKey;
|
||||
}
|
||||
|
||||
public List<PaymentData> getPayments() {
|
||||
return this.payments;
|
||||
}
|
||||
|
||||
}
|
@ -54,11 +54,26 @@ public class Order {
|
||||
// Processing
|
||||
|
||||
public void process() throws DataException {
|
||||
this.repository.getAssetRepository().save(this.orderData);
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
public void orphan() throws DataException {
|
||||
// TODO
|
||||
|
||||
this.repository.getAssetRepository().delete(this.orderData.getOrderId());
|
||||
}
|
||||
|
||||
// This is CancelOrderTransactions so that an Order can no longer trade
|
||||
public void cancel() throws DataException {
|
||||
this.orderData.setIsClosed(true);
|
||||
this.repository.getAssetRepository().save(this.orderData);
|
||||
}
|
||||
|
||||
// Opposite of cancel() above for use during orphaning
|
||||
public void reopen() throws DataException {
|
||||
// TODO
|
||||
}
|
||||
|
||||
}
|
||||
|
156
src/qora/payment/Payment.java
Normal file
156
src/qora/payment/Payment.java
Normal file
@ -0,0 +1,156 @@
|
||||
package qora.payment;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import data.PaymentData;
|
||||
import data.assets.AssetData;
|
||||
import qora.account.Account;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.assets.Asset;
|
||||
import qora.crypto.Crypto;
|
||||
import qora.transaction.Transaction.ValidationResult;
|
||||
import repository.AssetRepository;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
|
||||
public class Payment {
|
||||
|
||||
// Properties
|
||||
private Repository repository;
|
||||
|
||||
// Constructors
|
||||
|
||||
public Payment(Repository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
// Processing
|
||||
|
||||
// Validate multiple payments
|
||||
public ValidationResult isValid(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee, boolean isZeroAmountValid) throws DataException {
|
||||
AssetRepository assetRepository = this.repository.getAssetRepository();
|
||||
|
||||
// Check fee is positive
|
||||
if (fee.compareTo(BigDecimal.ZERO) <= 0)
|
||||
return ValidationResult.NEGATIVE_FEE;
|
||||
|
||||
// Total up payment amounts by assetId
|
||||
Map<Long, BigDecimal> amountsByAssetId = new HashMap<Long, BigDecimal>();
|
||||
// Add transaction fee to start with
|
||||
amountsByAssetId.put(Asset.QORA, fee);
|
||||
|
||||
// Check payments, and calculate amount total by assetId
|
||||
for (PaymentData paymentData : payments) {
|
||||
// Check amount is positive
|
||||
if (paymentData.getAmount().compareTo(BigDecimal.ZERO) < 0)
|
||||
return ValidationResult.NEGATIVE_AMOUNT;
|
||||
|
||||
// Optional zero-amount check
|
||||
if (!isZeroAmountValid && paymentData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
|
||||
return ValidationResult.NEGATIVE_AMOUNT;
|
||||
|
||||
// Check recipient address is valid
|
||||
if (!Crypto.isValidAddress(paymentData.getRecipient()))
|
||||
return ValidationResult.INVALID_ADDRESS;
|
||||
|
||||
// Check asset amount is integer if asset is not divisible
|
||||
AssetData assetData = assetRepository.fromAssetId(paymentData.getAssetId());
|
||||
if (!assetData.getIsDivisible() && paymentData.getAmount().stripTrailingZeros().scale() > 0)
|
||||
return ValidationResult.INVALID_AMOUNT;
|
||||
|
||||
amountsByAssetId.compute(paymentData.getAssetId(), (assetId, amount) -> amount == null ? amount : amount.add(paymentData.getAmount()));
|
||||
}
|
||||
|
||||
// Check sender has enough of each asset
|
||||
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
|
||||
for (Entry<Long, BigDecimal> pair : amountsByAssetId.entrySet())
|
||||
if (sender.getConfirmedBalance(pair.getKey()).compareTo(pair.getValue()) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
public ValidationResult isValid(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee) throws DataException {
|
||||
return isValid(senderPublicKey, payments, fee, false);
|
||||
}
|
||||
|
||||
// Single payment forms
|
||||
public ValidationResult isValid(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, boolean isZeroAmountValid) throws DataException {
|
||||
return isValid(senderPublicKey, Collections.singletonList(paymentData), fee);
|
||||
}
|
||||
|
||||
public ValidationResult isValid(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee) throws DataException {
|
||||
return isValid(senderPublicKey, paymentData, fee, false);
|
||||
}
|
||||
|
||||
public void process(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee, byte[] signature) throws DataException {
|
||||
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
|
||||
|
||||
// Update sender's balance due to fee
|
||||
sender.setConfirmedBalance(Asset.QORA, sender.getConfirmedBalance(Asset.QORA).subtract(fee));
|
||||
|
||||
// Update sender's reference
|
||||
sender.setLastReference(signature);
|
||||
|
||||
// Process all payments
|
||||
for (PaymentData paymentData : payments) {
|
||||
Account recipient = new Account(this.repository, paymentData.getRecipient());
|
||||
long assetId = paymentData.getAssetId();
|
||||
BigDecimal amount = paymentData.getAmount();
|
||||
|
||||
// Update sender's balance due to amount
|
||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(amount));
|
||||
|
||||
// Update recipient's balance
|
||||
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(amount));
|
||||
|
||||
// For QORA amounts only: if recipient has no reference yet, then this is their starting reference
|
||||
if (assetId == Asset.QORA && recipient.getLastReference() == null)
|
||||
recipient.setLastReference(signature);
|
||||
}
|
||||
}
|
||||
|
||||
public void process(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature) throws DataException {
|
||||
process(senderPublicKey, Collections.singletonList(paymentData), fee, signature);
|
||||
}
|
||||
|
||||
public void orphan(byte[] senderPublicKey, List<PaymentData> payments, BigDecimal fee, byte[] signature, byte[] reference) throws DataException {
|
||||
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
|
||||
|
||||
// Update sender's balance due to fee
|
||||
sender.setConfirmedBalance(Asset.QORA, sender.getConfirmedBalance(Asset.QORA).subtract(fee));
|
||||
|
||||
// Update sender's reference
|
||||
sender.setLastReference(reference);
|
||||
|
||||
for (PaymentData paymentData : payments) {
|
||||
Account recipient = new Account(this.repository, paymentData.getRecipient());
|
||||
long assetId = paymentData.getAssetId();
|
||||
BigDecimal amount = paymentData.getAmount();
|
||||
|
||||
// Update sender's balance due to amount
|
||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).add(amount));
|
||||
|
||||
// Update recipient's balance
|
||||
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).subtract(amount));
|
||||
|
||||
/*
|
||||
* For QORA amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own
|
||||
* (which would have changed their last reference) thus this is their first reference so remove it.
|
||||
*/
|
||||
if (assetId == Asset.QORA && Arrays.equals(recipient.getLastReference(), signature))
|
||||
recipient.setLastReference(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void orphan(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature, byte[] reference) throws DataException {
|
||||
orphan(senderPublicKey, Collections.singletonList(paymentData), fee, signature, reference);
|
||||
}
|
||||
|
||||
}
|
113
src/qora/transaction/CancelOrderTransaction.java
Normal file
113
src/qora/transaction/CancelOrderTransaction.java
Normal file
@ -0,0 +1,113 @@
|
||||
package qora.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
|
||||
import data.assets.OrderData;
|
||||
import data.transaction.CancelOrderTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.Account;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.assets.Asset;
|
||||
import qora.assets.Order;
|
||||
import qora.crypto.Crypto;
|
||||
import repository.AssetRepository;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
|
||||
public class CancelOrderTransaction extends Transaction {
|
||||
|
||||
// Constructors
|
||||
|
||||
public CancelOrderTransaction(Repository repository, TransactionData transactionData) {
|
||||
super(repository, transactionData);
|
||||
}
|
||||
|
||||
// Processing
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() throws DataException {
|
||||
CancelOrderTransactionData cancelOrderTransactionData = (CancelOrderTransactionData) this.transactionData;
|
||||
AssetRepository assetRepository = this.repository.getAssetRepository();
|
||||
|
||||
// Check fee is positive
|
||||
if (cancelOrderTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
|
||||
return ValidationResult.NEGATIVE_FEE;
|
||||
|
||||
// Check order even exists
|
||||
OrderData orderData = assetRepository.fromOrderId(cancelOrderTransactionData.getOrderId());
|
||||
|
||||
if (orderData == null)
|
||||
return ValidationResult.ORDER_DOES_NOT_EXIST;
|
||||
|
||||
Account creator = new PublicKeyAccount(this.repository, cancelOrderTransactionData.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;
|
||||
|
||||
// Check creator has enough QORA for fee
|
||||
if (creator.getConfirmedBalance(Asset.QORA).compareTo(cancelOrderTransactionData.getFee()) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
// Check reference is correct
|
||||
if (!Arrays.equals(creator.getLastReference(), cancelOrderTransactionData.getReference()))
|
||||
return ValidationResult.INVALID_REFERENCE;
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
// PROCESS/ORPHAN
|
||||
|
||||
@Override
|
||||
public void process() throws DataException {
|
||||
CancelOrderTransactionData cancelOrderTransactionData = (CancelOrderTransactionData) this.transactionData;
|
||||
Account creator = new PublicKeyAccount(this.repository, cancelOrderTransactionData.getCreatorPublicKey());
|
||||
|
||||
// Save this transaction itself
|
||||
this.repository.getTransactionRepository().save(this.transactionData);
|
||||
|
||||
// Update creator's balance regarding fee
|
||||
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(cancelOrderTransactionData.getFee()));
|
||||
|
||||
// Update creator's last reference
|
||||
creator.setLastReference(cancelOrderTransactionData.getSignature());
|
||||
|
||||
// Mark Order as completed so no more trades can happen
|
||||
OrderData orderData = this.repository.getAssetRepository().fromOrderId(cancelOrderTransactionData.getOrderId());
|
||||
Order order = new Order(this.repository, orderData);
|
||||
order.cancel();
|
||||
|
||||
// Update creator's balance with unfulfilled amount
|
||||
creator.setConfirmedBalance(orderData.getHaveAssetId(), creator.getConfirmedBalance(orderData.getHaveAssetId()).add(order.getAmountLeft()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orphan() throws DataException {
|
||||
CancelOrderTransactionData cancelOrderTransactionData = (CancelOrderTransactionData) this.transactionData;
|
||||
Account creator = new PublicKeyAccount(this.repository, cancelOrderTransactionData.getCreatorPublicKey());
|
||||
|
||||
// Save this transaction itself
|
||||
this.repository.getTransactionRepository().delete(this.transactionData);
|
||||
|
||||
// Update creator's balance regarding fee
|
||||
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).add(cancelOrderTransactionData.getFee()));
|
||||
|
||||
// Update creator's last reference
|
||||
creator.setLastReference(cancelOrderTransactionData.getReference());
|
||||
|
||||
// Unmark Order as completed so trades can happen again
|
||||
OrderData orderData = this.repository.getAssetRepository().fromOrderId(cancelOrderTransactionData.getOrderId());
|
||||
Order order = new Order(this.repository, orderData);
|
||||
order.reopen();
|
||||
|
||||
// Update creator's balance with unfulfilled amount
|
||||
creator.setConfirmedBalance(orderData.getHaveAssetId(), creator.getConfirmedBalance(orderData.getHaveAssetId()).subtract(order.getAmountLeft()));
|
||||
}
|
||||
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
package qora.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
|
||||
import data.PaymentData;
|
||||
import data.transaction.MessageTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.Account;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.assets.Asset;
|
||||
import qora.block.Block;
|
||||
import qora.crypto.Crypto;
|
||||
import qora.payment.Payment;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
|
||||
@ -24,9 +24,12 @@ public class MessageTransaction extends Transaction {
|
||||
|
||||
// Processing
|
||||
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Lowest cost checks first
|
||||
private PaymentData getPaymentData() {
|
||||
MessageTransactionData messageTransactionData = (MessageTransactionData) this.transactionData;
|
||||
return new PaymentData(messageTransactionData.getRecipient(), Asset.QORA, messageTransactionData.getAmount());
|
||||
}
|
||||
|
||||
public ValidationResult isValid() throws DataException {
|
||||
MessageTransactionData messageTransactionData = (MessageTransactionData) this.transactionData;
|
||||
|
||||
// Are message transactions even allowed at this point?
|
||||
@ -40,103 +43,39 @@ public class MessageTransaction extends Transaction {
|
||||
if (messageTransactionData.getData().length < 1 || messageTransactionData.getData().length > MAX_DATA_SIZE)
|
||||
return ValidationResult.INVALID_DATA_LENGTH;
|
||||
|
||||
// Check recipient is a valid address
|
||||
if (!Crypto.isValidAddress(messageTransactionData.getRecipient()))
|
||||
return ValidationResult.INVALID_ADDRESS;
|
||||
|
||||
if (messageTransactionData.getVersion() == 1) {
|
||||
// Check amount is positive (V1)
|
||||
if (messageTransactionData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
|
||||
return ValidationResult.NEGATIVE_AMOUNT;
|
||||
} else {
|
||||
// Check amount is not negative (V3) as sending messages without a payment is OK
|
||||
if (messageTransactionData.getAmount().compareTo(BigDecimal.ZERO) < 0)
|
||||
return ValidationResult.NEGATIVE_AMOUNT;
|
||||
}
|
||||
|
||||
// Check fee is positive
|
||||
if (messageTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
|
||||
return ValidationResult.NEGATIVE_FEE;
|
||||
|
||||
// Check reference is correct
|
||||
Account sender = new PublicKeyAccount(this.repository, messageTransactionData.getSenderPublicKey());
|
||||
if (!Arrays.equals(sender.getLastReference(), messageTransactionData.getReference()))
|
||||
return ValidationResult.INVALID_REFERENCE;
|
||||
|
||||
// Does asset exist? (This test not present in gen1)
|
||||
long assetId = messageTransactionData.getAssetId();
|
||||
if (assetId != Asset.QORA && !this.repository.getAssetRepository().assetExists(assetId))
|
||||
return ValidationResult.ASSET_DOES_NOT_EXIST;
|
||||
// Zero-amount payments (i.e. message-only) only valid for versions later than 1
|
||||
boolean isZeroAmountValid = messageTransactionData.getVersion() > 1;
|
||||
|
||||
// If asset is QORA then we need to check amount + fee in one go
|
||||
if (assetId == Asset.QORA) {
|
||||
// Check sender has enough funds for amount + fee in QORA
|
||||
if (sender.getConfirmedBalance(Asset.QORA).compareTo(messageTransactionData.getAmount().add(messageTransactionData.getFee())) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
} else {
|
||||
// Check sender has enough funds for amount in whatever asset
|
||||
if (sender.getConfirmedBalance(assetId).compareTo(messageTransactionData.getAmount()) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
// Check sender has enough funds for fee in QORA
|
||||
if (sender.getConfirmedBalance(Asset.QORA).compareTo(messageTransactionData.getFee()) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
}
|
||||
|
||||
return ValidationResult.OK;
|
||||
// Wrap and delegate final payment checks to Payment class
|
||||
return new Payment(this.repository).isValid(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
|
||||
isZeroAmountValid);
|
||||
}
|
||||
|
||||
public void process() throws DataException {
|
||||
MessageTransactionData messageTransactionData = (MessageTransactionData) this.transactionData;
|
||||
long assetId = messageTransactionData.getAssetId();
|
||||
|
||||
// Save this transaction itself
|
||||
this.repository.getTransactionRepository().save(this.transactionData);
|
||||
|
||||
// Update sender's balance due to amount
|
||||
Account sender = new PublicKeyAccount(this.repository, messageTransactionData.getSenderPublicKey());
|
||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(messageTransactionData.getAmount()));
|
||||
// Update sender's balance due to fee
|
||||
sender.setConfirmedBalance(Asset.QORA, sender.getConfirmedBalance(Asset.QORA).subtract(messageTransactionData.getFee()));
|
||||
|
||||
// Update recipient's balance
|
||||
Account recipient = new Account(this.repository, messageTransactionData.getRecipient());
|
||||
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(messageTransactionData.getAmount()));
|
||||
|
||||
// Update sender's reference
|
||||
sender.setLastReference(messageTransactionData.getSignature());
|
||||
|
||||
// For QORA amounts only: if recipient has no reference yet, then this is their starting reference
|
||||
if (assetId == Asset.QORA && recipient.getLastReference() == null)
|
||||
recipient.setLastReference(messageTransactionData.getSignature());
|
||||
// Wrap and delegate payment processing to Payment class
|
||||
new Payment(this.repository).process(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
|
||||
messageTransactionData.getSignature());
|
||||
}
|
||||
|
||||
public void orphan() throws DataException {
|
||||
MessageTransactionData messageTransactionData = (MessageTransactionData) this.transactionData;
|
||||
long assetId = messageTransactionData.getAssetId();
|
||||
|
||||
// Delete this transaction itself
|
||||
this.repository.getTransactionRepository().delete(this.transactionData);
|
||||
|
||||
// Update sender's balance due to amount
|
||||
Account sender = new PublicKeyAccount(this.repository, messageTransactionData.getSenderPublicKey());
|
||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).add(messageTransactionData.getAmount()));
|
||||
// Update sender's balance due to fee
|
||||
sender.setConfirmedBalance(Asset.QORA, sender.getConfirmedBalance(Asset.QORA).add(messageTransactionData.getFee()));
|
||||
|
||||
// Update recipient's balance
|
||||
Account recipient = new Account(this.repository, messageTransactionData.getRecipient());
|
||||
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).subtract(messageTransactionData.getAmount()));
|
||||
|
||||
// Update sender's reference
|
||||
sender.setLastReference(messageTransactionData.getReference());
|
||||
|
||||
/*
|
||||
* For QORA amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own (which
|
||||
* would have changed their last reference) thus this is their first reference so remove it.
|
||||
*/
|
||||
if (assetId == Asset.QORA && Arrays.equals(recipient.getLastReference(), messageTransactionData.getSignature()))
|
||||
recipient.setLastReference(null);
|
||||
// Wrap and delegate payment processing to Payment class
|
||||
new Payment(this.repository).orphan(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
|
||||
messageTransactionData.getSignature(), messageTransactionData.getReference());
|
||||
}
|
||||
|
||||
}
|
||||
|
81
src/qora/transaction/MultiPaymentTransaction.java
Normal file
81
src/qora/transaction/MultiPaymentTransaction.java
Normal file
@ -0,0 +1,81 @@
|
||||
package qora.transaction;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import data.PaymentData;
|
||||
import data.transaction.MultiPaymentTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.assets.Asset;
|
||||
import qora.block.Block;
|
||||
import qora.payment.Payment;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
import utils.NTP;
|
||||
|
||||
public class MultiPaymentTransaction extends Transaction {
|
||||
|
||||
private static final int MAX_PAYMENTS_COUNT = 400;
|
||||
|
||||
// Constructors
|
||||
|
||||
public MultiPaymentTransaction(Repository repository, TransactionData transactionData) {
|
||||
super(repository, transactionData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() throws DataException {
|
||||
MultiPaymentTransactionData multiPaymentTransactionData = (MultiPaymentTransactionData) this.transactionData;
|
||||
List<PaymentData> payments = multiPaymentTransactionData.getPayments();
|
||||
|
||||
// Are MultiPaymentTransactions even allowed at this point?
|
||||
if (NTP.getTime() < Block.ASSETS_RELEASE_TIMESTAMP)
|
||||
return ValidationResult.NOT_YET_RELEASED;
|
||||
|
||||
// Check number of payments
|
||||
if (payments.size() < 1 || payments.size() > MAX_PAYMENTS_COUNT)
|
||||
return ValidationResult.INVALID_PAYMENTS_COUNT;
|
||||
|
||||
// Check reference is correct
|
||||
PublicKeyAccount sender = new PublicKeyAccount(this.repository, multiPaymentTransactionData.getSenderPublicKey());
|
||||
|
||||
if (!Arrays.equals(sender.getLastReference(), multiPaymentTransactionData.getReference()))
|
||||
return ValidationResult.INVALID_REFERENCE;
|
||||
|
||||
// Check sender has enough funds for fee
|
||||
// NOTE: in Gen1 pre-POWFIX-RELEASE transactions didn't have this check
|
||||
if (multiPaymentTransactionData.getTimestamp() >= Block.POWFIX_RELEASE_TIMESTAMP
|
||||
&& sender.getConfirmedBalance(Asset.QORA).compareTo(multiPaymentTransactionData.getFee()) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
return new Payment(this.repository).isValid(multiPaymentTransactionData.getSenderPublicKey(), payments, multiPaymentTransactionData.getFee());
|
||||
}
|
||||
|
||||
// PROCESS/ORPHAN
|
||||
|
||||
@Override
|
||||
public void process() throws DataException {
|
||||
MultiPaymentTransactionData multiPaymentTransactionData = (MultiPaymentTransactionData) this.transactionData;
|
||||
|
||||
// Save this transaction itself
|
||||
this.repository.getTransactionRepository().save(this.transactionData);
|
||||
|
||||
// Wrap and delegate payment processing to Payment class
|
||||
new Payment(this.repository).process(multiPaymentTransactionData.getSenderPublicKey(), multiPaymentTransactionData.getPayments(),
|
||||
multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orphan() throws DataException {
|
||||
MultiPaymentTransactionData multiPaymentTransactionData = (MultiPaymentTransactionData) this.transactionData;
|
||||
|
||||
// Delete this transaction itself
|
||||
this.repository.getTransactionRepository().delete(this.transactionData);
|
||||
|
||||
// Wrap and delegate payment processing to Payment class
|
||||
new Payment(this.repository).orphan(multiPaymentTransactionData.getSenderPublicKey(), multiPaymentTransactionData.getPayments(),
|
||||
multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature(), multiPaymentTransactionData.getReference());
|
||||
}
|
||||
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
package qora.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
|
||||
import data.PaymentData;
|
||||
import data.transaction.PaymentTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.Account;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.assets.Asset;
|
||||
import qora.crypto.Crypto;
|
||||
import qora.payment.Payment;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
|
||||
@ -22,33 +22,21 @@ public class PaymentTransaction extends Transaction {
|
||||
|
||||
// Processing
|
||||
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Lowest cost checks first
|
||||
|
||||
private PaymentData getPaymentData() {
|
||||
PaymentTransactionData paymentTransactionData = (PaymentTransactionData) this.transactionData;
|
||||
return new PaymentData(paymentTransactionData.getRecipient(), Asset.QORA, paymentTransactionData.getAmount());
|
||||
}
|
||||
|
||||
// Check recipient is a valid address
|
||||
if (!Crypto.isValidAddress(paymentTransactionData.getRecipient()))
|
||||
return ValidationResult.INVALID_ADDRESS;
|
||||
|
||||
// Check amount is positive
|
||||
if (paymentTransactionData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
|
||||
return ValidationResult.NEGATIVE_AMOUNT;
|
||||
|
||||
// Check fee is positive
|
||||
if (paymentTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
|
||||
return ValidationResult.NEGATIVE_FEE;
|
||||
public ValidationResult isValid() throws DataException {
|
||||
PaymentTransactionData paymentTransactionData = (PaymentTransactionData) this.transactionData;
|
||||
|
||||
// Check reference is correct
|
||||
Account sender = new PublicKeyAccount(repository, paymentTransactionData.getSenderPublicKey());
|
||||
if (!Arrays.equals(sender.getLastReference(), paymentTransactionData.getReference()))
|
||||
return ValidationResult.INVALID_REFERENCE;
|
||||
|
||||
// Check sender has enough funds
|
||||
if (sender.getConfirmedBalance(Asset.QORA).compareTo(paymentTransactionData.getAmount().add(paymentTransactionData.getFee())) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
return ValidationResult.OK;
|
||||
// Wrap and delegate final payment checks to Payment class
|
||||
return new Payment(this.repository).isValid(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee());
|
||||
}
|
||||
|
||||
public void process() throws DataException {
|
||||
@ -57,21 +45,9 @@ public class PaymentTransaction extends Transaction {
|
||||
// Save this transaction itself
|
||||
this.repository.getTransactionRepository().save(this.transactionData);
|
||||
|
||||
// Update sender's balance
|
||||
Account sender = new PublicKeyAccount(repository, paymentTransactionData.getSenderPublicKey());
|
||||
sender.setConfirmedBalance(Asset.QORA,
|
||||
sender.getConfirmedBalance(Asset.QORA).subtract(paymentTransactionData.getAmount()).subtract(paymentTransactionData.getFee()));
|
||||
|
||||
// Update recipient's balance
|
||||
Account recipient = new Account(repository, paymentTransactionData.getRecipient());
|
||||
recipient.setConfirmedBalance(Asset.QORA, recipient.getConfirmedBalance(Asset.QORA).add(paymentTransactionData.getAmount()));
|
||||
|
||||
// Update sender's reference
|
||||
sender.setLastReference(paymentTransactionData.getSignature());
|
||||
|
||||
// If recipient has no reference yet, then this is their starting reference
|
||||
if (recipient.getLastReference() == null)
|
||||
recipient.setLastReference(paymentTransactionData.getSignature());
|
||||
// Wrap and delegate payment processing to Payment class
|
||||
new Payment(this.repository).process(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee(),
|
||||
paymentTransactionData.getSignature());
|
||||
}
|
||||
|
||||
public void orphan() throws DataException {
|
||||
@ -80,24 +56,9 @@ public class PaymentTransaction extends Transaction {
|
||||
// Delete this transaction
|
||||
this.repository.getTransactionRepository().delete(this.transactionData);
|
||||
|
||||
// Update sender's balance
|
||||
Account sender = new PublicKeyAccount(repository, paymentTransactionData.getSenderPublicKey());
|
||||
sender.setConfirmedBalance(Asset.QORA,
|
||||
sender.getConfirmedBalance(Asset.QORA).add(paymentTransactionData.getAmount()).add(paymentTransactionData.getFee()));
|
||||
|
||||
// Update recipient's balance
|
||||
Account recipient = new Account(repository, paymentTransactionData.getRecipient());
|
||||
recipient.setConfirmedBalance(Asset.QORA, recipient.getConfirmedBalance(Asset.QORA).subtract(paymentTransactionData.getAmount()));
|
||||
|
||||
// Update sender's reference
|
||||
sender.setLastReference(paymentTransactionData.getReference());
|
||||
|
||||
/*
|
||||
* If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own (which would have changed
|
||||
* their last reference) thus this is their first reference so remove it.
|
||||
*/
|
||||
if (Arrays.equals(recipient.getLastReference(), paymentTransactionData.getSignature()))
|
||||
recipient.setLastReference(null);
|
||||
// Wrap and delegate payment processing to Payment class
|
||||
new Payment(this.repository).orphan(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee(),
|
||||
paymentTransactionData.getSignature(), paymentTransactionData.getReference());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -41,8 +41,10 @@ public abstract class Transaction {
|
||||
|
||||
// Validation results
|
||||
public enum ValidationResult {
|
||||
OK(1), INVALID_ADDRESS(2), NEGATIVE_AMOUNT(3), NEGATIVE_FEE(4), NO_BALANCE(5), INVALID_REFERENCE(6), INVALID_NAME_LENGTH(7), INVALID_AMOUNT(15), INVALID_DESCRIPTION_LENGTH(
|
||||
18), INVALID_DATA_LENGTH(27), INVALID_QUANTITY(28), ASSET_DOES_NOT_EXIST(29), INVALID_RETURN(30), HAVE_EQUALS_WANT(31), NEGATIVE_PRICE(35), ASSET_ALREADY_EXISTS(43), NOT_YET_RELEASED(1000);
|
||||
OK(1), INVALID_ADDRESS(2), NEGATIVE_AMOUNT(3), NEGATIVE_FEE(4), NO_BALANCE(5), INVALID_REFERENCE(6), INVALID_NAME_LENGTH(7), INVALID_AMOUNT(
|
||||
15), INVALID_DESCRIPTION_LENGTH(18), INVALID_DATA_LENGTH(27), INVALID_QUANTITY(28), ASSET_DOES_NOT_EXIST(29), INVALID_RETURN(
|
||||
30), HAVE_EQUALS_WANT(31), ORDER_DOES_NOT_EXIST(32), INVALID_ORDER_CREATOR(
|
||||
33), INVALID_PAYMENTS_COUNT(34), NEGATIVE_PRICE(35), ASSET_ALREADY_EXISTS(43), NOT_YET_RELEASED(1000);
|
||||
|
||||
public final int value;
|
||||
|
||||
|
@ -1,18 +1,14 @@
|
||||
package qora.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
|
||||
import data.assets.AssetData;
|
||||
import data.PaymentData;
|
||||
import data.transaction.TransactionData;
|
||||
import data.transaction.TransferAssetTransactionData;
|
||||
import utils.NTP;
|
||||
import qora.account.Account;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.assets.Asset;
|
||||
import qora.block.Block;
|
||||
import qora.crypto.Crypto;
|
||||
import repository.AssetRepository;
|
||||
import qora.payment.Payment;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
|
||||
@ -26,60 +22,28 @@ public class TransferAssetTransaction extends Transaction {
|
||||
|
||||
// Processing
|
||||
|
||||
private PaymentData getPaymentData() {
|
||||
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
|
||||
return new PaymentData(transferAssetTransactionData.getRecipient(), transferAssetTransactionData.getAssetId(),
|
||||
transferAssetTransactionData.getAmount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Lowest cost checks first
|
||||
|
||||
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
|
||||
|
||||
// Are IssueAssetTransactions even allowed at this point?
|
||||
if (NTP.getTime() < Block.ASSETS_RELEASE_TIMESTAMP)
|
||||
return ValidationResult.NOT_YET_RELEASED;
|
||||
|
||||
// Check recipient address is valid
|
||||
if (!Crypto.isValidAddress(transferAssetTransactionData.getRecipient()))
|
||||
return ValidationResult.INVALID_ADDRESS;
|
||||
|
||||
// Check amount is positive
|
||||
if (transferAssetTransactionData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
|
||||
return ValidationResult.NEGATIVE_FEE;
|
||||
|
||||
// Check fee is positive
|
||||
if (transferAssetTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
|
||||
return ValidationResult.NEGATIVE_FEE;
|
||||
|
||||
// Check reference is correct
|
||||
PublicKeyAccount sender = new PublicKeyAccount(this.repository, transferAssetTransactionData.getSenderPublicKey());
|
||||
|
||||
if (!Arrays.equals(sender.getLastReference(), transferAssetTransactionData.getReference()))
|
||||
return ValidationResult.INVALID_REFERENCE;
|
||||
|
||||
// Check sender has enough asset balance AFTER removing fee, in case asset is QORA
|
||||
long assetId = transferAssetTransactionData.getAssetId();
|
||||
// If asset is QORA then we need to check amount + fee in one go
|
||||
if (assetId == Asset.QORA) {
|
||||
// Check sender has enough funds for amount + fee in QORA
|
||||
if (sender.getConfirmedBalance(Asset.QORA).compareTo(transferAssetTransactionData.getAmount().add(transferAssetTransactionData.getFee())) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
} else {
|
||||
// Check sender has enough funds for amount in whatever asset
|
||||
if (sender.getConfirmedBalance(assetId).compareTo(transferAssetTransactionData.getAmount()) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
// Check sender has enough funds for fee in QORA
|
||||
// NOTE: in Gen1 pre-POWFIX-RELEASE transactions didn't have this check
|
||||
if (transferAssetTransactionData.getTimestamp() >= Block.POWFIX_RELEASE_TIMESTAMP
|
||||
&& sender.getConfirmedBalance(Asset.QORA).compareTo(transferAssetTransactionData.getFee()) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
}
|
||||
|
||||
// Check asset amount is integer if asset is not divisible
|
||||
AssetRepository assetRepository = this.repository.getAssetRepository();
|
||||
AssetData assetData = assetRepository.fromAssetId(assetId);
|
||||
if (!assetData.getIsDivisible() && transferAssetTransactionData.getAmount().stripTrailingZeros().scale() > 0)
|
||||
return ValidationResult.INVALID_AMOUNT;
|
||||
|
||||
return ValidationResult.OK;
|
||||
// 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());
|
||||
}
|
||||
|
||||
// PROCESS/ORPHAN
|
||||
@ -87,56 +51,25 @@ public class TransferAssetTransaction extends Transaction {
|
||||
@Override
|
||||
public void process() throws DataException {
|
||||
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
|
||||
long assetId = transferAssetTransactionData.getAssetId();
|
||||
|
||||
// Save this transaction itself
|
||||
this.repository.getTransactionRepository().save(this.transactionData);
|
||||
|
||||
// Update sender's balance due to amount
|
||||
Account sender = new PublicKeyAccount(this.repository, transferAssetTransactionData.getSenderPublicKey());
|
||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(transferAssetTransactionData.getAmount()));
|
||||
// Update sender's balance due to fee
|
||||
sender.setConfirmedBalance(Asset.QORA, sender.getConfirmedBalance(Asset.QORA).subtract(transferAssetTransactionData.getFee()));
|
||||
|
||||
// Update recipient's balance
|
||||
Account recipient = new Account(this.repository, transferAssetTransactionData.getRecipient());
|
||||
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(transferAssetTransactionData.getAmount()));
|
||||
|
||||
// Update sender's reference
|
||||
sender.setLastReference(transferAssetTransactionData.getSignature());
|
||||
|
||||
// For QORA amounts only: if recipient has no reference yet, then this is their starting reference
|
||||
if (assetId == Asset.QORA && recipient.getLastReference() == null)
|
||||
recipient.setLastReference(transferAssetTransactionData.getSignature());
|
||||
// Wrap asset transfer as a payment and delegate processing to Payment class
|
||||
new Payment(this.repository).process(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(),
|
||||
transferAssetTransactionData.getSignature());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orphan() throws DataException {
|
||||
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
|
||||
long assetId = transferAssetTransactionData.getAssetId();
|
||||
|
||||
// Delete this transaction itself
|
||||
this.repository.getTransactionRepository().delete(this.transactionData);
|
||||
|
||||
// Update sender's balance due to amount
|
||||
Account sender = new PublicKeyAccount(this.repository, transferAssetTransactionData.getSenderPublicKey());
|
||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).add(transferAssetTransactionData.getAmount()));
|
||||
// Update sender's balance due to fee
|
||||
sender.setConfirmedBalance(Asset.QORA, sender.getConfirmedBalance(Asset.QORA).add(transferAssetTransactionData.getFee()));
|
||||
|
||||
// Update recipient's balance
|
||||
Account recipient = new Account(this.repository, transferAssetTransactionData.getRecipient());
|
||||
recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).subtract(transferAssetTransactionData.getAmount()));
|
||||
|
||||
// Update sender's reference
|
||||
sender.setLastReference(transferAssetTransactionData.getReference());
|
||||
|
||||
/*
|
||||
* For QORA amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own (which
|
||||
* would have changed their last reference) thus this is their first reference so remove it.
|
||||
*/
|
||||
if (assetId == Asset.QORA && Arrays.equals(recipient.getLastReference(), transferAssetTransactionData.getSignature()))
|
||||
recipient.setLastReference(null);
|
||||
// Wrap asset transfer as a payment and delegate processing to Payment class
|
||||
new Payment(this.repository).orphan(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(),
|
||||
transferAssetTransactionData.getSignature(), transferAssetTransactionData.getReference());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,4 +21,8 @@ public interface AssetRepository {
|
||||
|
||||
public OrderData fromOrderId(byte[] orderId) throws DataException;
|
||||
|
||||
public void save(OrderData orderData) throws DataException;
|
||||
|
||||
public void delete(byte[] orderId) throws DataException;
|
||||
|
||||
}
|
||||
|
@ -84,7 +84,8 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
||||
public OrderData fromOrderId(byte[] orderId) throws DataException {
|
||||
try {
|
||||
ResultSet resultSet = this.repository.checkedExecute(
|
||||
"SELECT creator, have_asset_id, want_asset_id, amount, fulfilled, price, timestamp FROM AssetOrders WHERE asset_order_id = ?", orderId);
|
||||
"SELECT creator, have_asset_id, want_asset_id, amount, fulfilled, price, timestamp, is_closed FROM AssetOrders WHERE asset_order_id = ?",
|
||||
orderId);
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
@ -95,10 +96,32 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
||||
BigDecimal fulfilled = resultSet.getBigDecimal(5);
|
||||
BigDecimal price = resultSet.getBigDecimal(6);
|
||||
long timestamp = resultSet.getTimestamp(7).getTime();
|
||||
boolean isClosed = resultSet.getBoolean(8);
|
||||
|
||||
return new OrderData(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, fulfilled, price, timestamp);
|
||||
return new OrderData(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, fulfilled, price, timestamp, isClosed);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch order from repository", e);
|
||||
throw new DataException("Unable to fetch asset order from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void save(OrderData orderData) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("AssetOrders");
|
||||
saveHelper.bind("asset_order_id", orderData.getOrderId()).bind("creator", orderData.getCreatorPublicKey())
|
||||
.bind("have_asset_id", orderData.getHaveAssetId()).bind("want_asset_id", orderData.getWantAssetId()).bind("amount", orderData.getAmount())
|
||||
.bind("fulfilled", orderData.getFulfilled()).bind("price", orderData.getPrice()).bind("isClosed", orderData.getIsClosed());
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to save asset order into repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(byte[] orderId) throws DataException {
|
||||
try {
|
||||
this.repository.checkedExecute("DELETE FROM AssetOrders WHERE orderId = ?", orderId);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete asset order from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,10 +286,10 @@ public class HSQLDBDatabaseUpdates {
|
||||
// Asset Orders
|
||||
stmt.execute(
|
||||
"CREATE TABLE AssetOrders (asset_order_id AssetOrderID, creator QoraPublicKey NOT NULL, have_asset_id AssetID NOT NULL, want_asset_id AssetID NOT NULL, "
|
||||
+ "amount QoraAmount NOT NULL, fulfilled QoraAmount NOT NULL, price QoraAmount NOT NULL, ordered TIMESTAMP NOT NULL, "
|
||||
+ "amount QoraAmount NOT NULL, fulfilled QoraAmount NOT NULL, price QoraAmount NOT NULL, ordered TIMESTAMP NOT NULL, is_closed BOOLEAN NOT NULL, "
|
||||
+ "PRIMARY KEY (asset_order_id))");
|
||||
stmt.execute("CREATE INDEX AssetOrderHaveIndex on AssetOrders (have_asset_id)");
|
||||
stmt.execute("CREATE INDEX AssetOrderWantIndex on AssetOrders (want_asset_id)");
|
||||
stmt.execute("CREATE INDEX AssetOrderHaveIndex on AssetOrders (have_asset_id, is_closed)");
|
||||
stmt.execute("CREATE INDEX AssetOrderWantIndex on AssetOrders (want_asset_id, is_closed)");
|
||||
break;
|
||||
|
||||
default:
|
||||
|
75
src/transform/PaymentTransformer.java
Normal file
75
src/transform/PaymentTransformer.java
Normal file
@ -0,0 +1,75 @@
|
||||
package transform;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import data.PaymentData;
|
||||
import transform.TransformationException;
|
||||
import utils.Base58;
|
||||
import utils.Serialization;
|
||||
|
||||
public class PaymentTransformer extends Transformer {
|
||||
|
||||
// Property lengths
|
||||
private static final int RECIPIENT_LENGTH = ADDRESS_LENGTH;
|
||||
private static final int ASSET_ID_LENGTH = LONG_LENGTH;
|
||||
private static final int AMOUNT_LENGTH = BIG_DECIMAL_LENGTH;
|
||||
|
||||
private static final int TOTAL_LENGTH = RECIPIENT_LENGTH + ASSET_ID_LENGTH + AMOUNT_LENGTH;
|
||||
|
||||
public static PaymentData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
||||
if (byteBuffer.remaining() < TOTAL_LENGTH)
|
||||
throw new TransformationException("Byte data too short for payment information");
|
||||
|
||||
String recipient = Serialization.deserializeRecipient(byteBuffer);
|
||||
long assetId = byteBuffer.getLong();
|
||||
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
|
||||
|
||||
return new PaymentData(recipient, assetId, amount);
|
||||
}
|
||||
|
||||
public static int getDataLength() throws TransformationException {
|
||||
return TOTAL_LENGTH;
|
||||
}
|
||||
|
||||
public static byte[] toBytes(PaymentData paymentData) throws TransformationException {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Base58.decode(paymentData.getRecipient()));
|
||||
bytes.write(Longs.toByteArray(paymentData.getAssetId()));
|
||||
Serialization.serializeBigDecimal(bytes, paymentData.getAmount());
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException | ClassCastException e) {
|
||||
throw new TransformationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package transform.transaction;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import data.transaction.CancelOrderTransactionData;
|
||||
import transform.TransformationException;
|
||||
import utils.Base58;
|
||||
import utils.Serialization;
|
||||
|
||||
public class CancelOrderTransactionTransformer extends TransactionTransformer {
|
||||
|
||||
// Property lengths
|
||||
private static final int CREATOR_LENGTH = LONG_LENGTH;
|
||||
private static final int ORDER_ID_LENGTH = SIGNATURE_LENGTH;
|
||||
|
||||
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + CREATOR_LENGTH + ORDER_ID_LENGTH;
|
||||
|
||||
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
||||
if (byteBuffer.remaining() < TYPELESS_LENGTH)
|
||||
throw new TransformationException("Byte data too short for CancelOrderTransaction");
|
||||
|
||||
long timestamp = byteBuffer.getLong();
|
||||
|
||||
byte[] reference = new byte[REFERENCE_LENGTH];
|
||||
byteBuffer.get(reference);
|
||||
|
||||
byte[] creatorPublicKey = Serialization.deserializePublicKey(byteBuffer);
|
||||
|
||||
byte[] orderId = new byte[ORDER_ID_LENGTH];
|
||||
byteBuffer.get(orderId);
|
||||
|
||||
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
|
||||
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
byteBuffer.get(signature);
|
||||
|
||||
return new CancelOrderTransactionData(creatorPublicKey, orderId, fee, timestamp, reference, signature);
|
||||
}
|
||||
|
||||
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
||||
return TYPE_LENGTH + TYPELESS_LENGTH;
|
||||
}
|
||||
|
||||
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
|
||||
try {
|
||||
CancelOrderTransactionData cancelOrderTransactionData = (CancelOrderTransactionData) transactionData;
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(cancelOrderTransactionData.getType().value));
|
||||
bytes.write(Longs.toByteArray(cancelOrderTransactionData.getTimestamp()));
|
||||
bytes.write(cancelOrderTransactionData.getReference());
|
||||
|
||||
bytes.write(cancelOrderTransactionData.getCreatorPublicKey());
|
||||
bytes.write(cancelOrderTransactionData.getOrderId());
|
||||
|
||||
Serialization.serializeBigDecimal(bytes, cancelOrderTransactionData.getFee());
|
||||
bytes.write(cancelOrderTransactionData.getSignature());
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException | ClassCastException e) {
|
||||
throw new TransformationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
|
||||
JSONObject json = TransactionTransformer.getBaseJSON(transactionData);
|
||||
|
||||
try {
|
||||
CancelOrderTransactionData cancelOrderTransactionData = (CancelOrderTransactionData) transactionData;
|
||||
|
||||
byte[] creatorPublicKey = cancelOrderTransactionData.getCreatorPublicKey();
|
||||
|
||||
json.put("creator", PublicKeyAccount.getAddress(creatorPublicKey));
|
||||
json.put("creatorPublicKey", HashCode.fromBytes(creatorPublicKey).toString());
|
||||
|
||||
json.put("order", Base58.encode(cancelOrderTransactionData.getOrderId()));
|
||||
} catch (ClassCastException e) {
|
||||
throw new TransformationException(e);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package transform.transaction;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import data.PaymentData;
|
||||
import data.transaction.MultiPaymentTransactionData;
|
||||
import transform.PaymentTransformer;
|
||||
import transform.TransformationException;
|
||||
import utils.Serialization;
|
||||
|
||||
public class MultiPaymentTransactionTransformer extends TransactionTransformer {
|
||||
|
||||
// Property lengths
|
||||
private static final int SENDER_LENGTH = PUBLIC_KEY_LENGTH;
|
||||
private static final int PAYMENTS_COUNT_LENGTH = INT_LENGTH;
|
||||
|
||||
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + SENDER_LENGTH + PAYMENTS_COUNT_LENGTH;
|
||||
|
||||
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
||||
if (byteBuffer.remaining() < TYPELESS_LENGTH)
|
||||
throw new TransformationException("Byte data too short for PaymentTransaction");
|
||||
|
||||
long timestamp = byteBuffer.getLong();
|
||||
|
||||
byte[] reference = new byte[REFERENCE_LENGTH];
|
||||
byteBuffer.get(reference);
|
||||
|
||||
byte[] senderPublicKey = Serialization.deserializePublicKey(byteBuffer);
|
||||
int paymentsCount = byteBuffer.getInt();
|
||||
|
||||
// Check remaining buffer size
|
||||
int minRemaining = paymentsCount * PaymentTransformer.getDataLength() + FEE_LENGTH + SIGNATURE_LENGTH;
|
||||
if (byteBuffer.remaining() < minRemaining)
|
||||
throw new TransformationException("Byte data too short for PaymentTransaction");
|
||||
|
||||
List<PaymentData> payments = new ArrayList<PaymentData>();
|
||||
for (int i = 0; i < paymentsCount; ++i)
|
||||
payments.add(PaymentTransformer.fromByteBuffer(byteBuffer));
|
||||
|
||||
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
|
||||
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
byteBuffer.get(signature);
|
||||
|
||||
return new MultiPaymentTransactionData(senderPublicKey, payments, fee, timestamp, reference, signature);
|
||||
}
|
||||
|
||||
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
||||
MultiPaymentTransactionData multiPaymentTransactionData = (MultiPaymentTransactionData) transactionData;
|
||||
|
||||
return TYPE_LENGTH + TYPELESS_LENGTH + multiPaymentTransactionData.getPayments().size() * PaymentTransformer.getDataLength();
|
||||
}
|
||||
|
||||
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
|
||||
try {
|
||||
MultiPaymentTransactionData multiPaymentTransactionData = (MultiPaymentTransactionData) transactionData;
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(multiPaymentTransactionData.getType().value));
|
||||
bytes.write(Longs.toByteArray(multiPaymentTransactionData.getTimestamp()));
|
||||
bytes.write(multiPaymentTransactionData.getReference());
|
||||
|
||||
bytes.write(multiPaymentTransactionData.getSenderPublicKey());
|
||||
|
||||
List<PaymentData> payments = multiPaymentTransactionData.getPayments();
|
||||
bytes.write(Ints.toByteArray(payments.size()));
|
||||
|
||||
for (PaymentData paymentData : payments)
|
||||
PaymentTransformer.toBytes(paymentData);
|
||||
|
||||
Serialization.serializeBigDecimal(bytes, multiPaymentTransactionData.getFee());
|
||||
bytes.write(multiPaymentTransactionData.getSignature());
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException | ClassCastException e) {
|
||||
throw new TransformationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
|
||||
JSONObject json = TransactionTransformer.getBaseJSON(transactionData);
|
||||
|
||||
try {
|
||||
MultiPaymentTransactionData multiPaymentTransactionData = (MultiPaymentTransactionData) transactionData;
|
||||
|
||||
byte[] senderPublicKey = multiPaymentTransactionData.getSenderPublicKey();
|
||||
|
||||
json.put("sender", PublicKeyAccount.getAddress(senderPublicKey));
|
||||
json.put("senderPublicKey", HashCode.fromBytes(senderPublicKey).toString());
|
||||
|
||||
List<PaymentData> payments = multiPaymentTransactionData.getPayments();
|
||||
JSONArray paymentsJson = new JSONArray();
|
||||
|
||||
for (PaymentData paymentData : payments)
|
||||
paymentsJson.add(PaymentTransformer.toJSON(paymentData));
|
||||
|
||||
json.put("payments", paymentsJson);
|
||||
} catch (ClassCastException e) {
|
||||
throw new TransformationException(e);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user