mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-18 21:24:58 +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 fulfilled;
|
||||||
private BigDecimal price;
|
private BigDecimal price;
|
||||||
private long timestamp;
|
private long timestamp;
|
||||||
|
private boolean isClosed;
|
||||||
|
|
||||||
public OrderData(byte[] orderId, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal fulfilled, BigDecimal price,
|
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.orderId = orderId;
|
||||||
this.creatorPublicKey = creatorPublicKey;
|
this.creatorPublicKey = creatorPublicKey;
|
||||||
this.haveAssetId = haveAssetId;
|
this.haveAssetId = haveAssetId;
|
||||||
@ -23,10 +24,11 @@ public class OrderData implements Comparable<OrderData> {
|
|||||||
this.fulfilled = fulfilled;
|
this.fulfilled = fulfilled;
|
||||||
this.price = price;
|
this.price = price;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
|
this.isClosed = isClosed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OrderData(byte[] orderId, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal price, long timestamp) {
|
public OrderData(byte[] orderId, byte[] creatorPublicKey, long haveAssetId, long wantAssetId, BigDecimal amount, BigDecimal price, long timestamp) {
|
||||||
this(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, BigDecimal.ZERO.setScale(8), price, timestamp);
|
this(orderId, creatorPublicKey, haveAssetId, wantAssetId, amount, BigDecimal.ZERO.setScale(8), price, timestamp, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getOrderId() {
|
public byte[] getOrderId() {
|
||||||
@ -65,6 +67,14 @@ public class OrderData implements Comparable<OrderData> {
|
|||||||
return this.timestamp;
|
return this.timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getIsClosed() {
|
||||||
|
return this.isClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsClosed(boolean isClosed) {
|
||||||
|
this.isClosed = isClosed;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(OrderData orderData) {
|
public int compareTo(OrderData orderData) {
|
||||||
// Compare using prices
|
// 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
|
// Processing
|
||||||
|
|
||||||
public void process() throws DataException {
|
public void process() throws DataException {
|
||||||
|
this.repository.getAssetRepository().save(this.orderData);
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
public void orphan() throws DataException {
|
public void orphan() throws DataException {
|
||||||
// TODO
|
// 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;
|
package qora.transaction;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import data.PaymentData;
|
||||||
import data.transaction.MessageTransactionData;
|
import data.transaction.MessageTransactionData;
|
||||||
import data.transaction.TransactionData;
|
import data.transaction.TransactionData;
|
||||||
import qora.account.Account;
|
import qora.account.Account;
|
||||||
import qora.account.PublicKeyAccount;
|
import qora.account.PublicKeyAccount;
|
||||||
import qora.assets.Asset;
|
import qora.assets.Asset;
|
||||||
import qora.block.Block;
|
import qora.block.Block;
|
||||||
import qora.crypto.Crypto;
|
import qora.payment.Payment;
|
||||||
import repository.DataException;
|
import repository.DataException;
|
||||||
import repository.Repository;
|
import repository.Repository;
|
||||||
|
|
||||||
@ -24,9 +24,12 @@ public class MessageTransaction extends Transaction {
|
|||||||
|
|
||||||
// Processing
|
// Processing
|
||||||
|
|
||||||
public ValidationResult isValid() throws DataException {
|
private PaymentData getPaymentData() {
|
||||||
// Lowest cost checks first
|
MessageTransactionData messageTransactionData = (MessageTransactionData) this.transactionData;
|
||||||
|
return new PaymentData(messageTransactionData.getRecipient(), Asset.QORA, messageTransactionData.getAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationResult isValid() throws DataException {
|
||||||
MessageTransactionData messageTransactionData = (MessageTransactionData) this.transactionData;
|
MessageTransactionData messageTransactionData = (MessageTransactionData) this.transactionData;
|
||||||
|
|
||||||
// Are message transactions even allowed at this point?
|
// 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)
|
if (messageTransactionData.getData().length < 1 || messageTransactionData.getData().length > MAX_DATA_SIZE)
|
||||||
return ValidationResult.INVALID_DATA_LENGTH;
|
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
|
// Check reference is correct
|
||||||
Account sender = new PublicKeyAccount(this.repository, messageTransactionData.getSenderPublicKey());
|
Account sender = new PublicKeyAccount(this.repository, messageTransactionData.getSenderPublicKey());
|
||||||
if (!Arrays.equals(sender.getLastReference(), messageTransactionData.getReference()))
|
if (!Arrays.equals(sender.getLastReference(), messageTransactionData.getReference()))
|
||||||
return ValidationResult.INVALID_REFERENCE;
|
return ValidationResult.INVALID_REFERENCE;
|
||||||
|
|
||||||
// Does asset exist? (This test not present in gen1)
|
// Zero-amount payments (i.e. message-only) only valid for versions later than 1
|
||||||
long assetId = messageTransactionData.getAssetId();
|
boolean isZeroAmountValid = messageTransactionData.getVersion() > 1;
|
||||||
if (assetId != Asset.QORA && !this.repository.getAssetRepository().assetExists(assetId))
|
|
||||||
return ValidationResult.ASSET_DOES_NOT_EXIST;
|
|
||||||
|
|
||||||
// If asset is QORA then we need to check amount + fee in one go
|
// Wrap and delegate final payment checks to Payment class
|
||||||
if (assetId == Asset.QORA) {
|
return new Payment(this.repository).isValid(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
|
||||||
// Check sender has enough funds for amount + fee in QORA
|
isZeroAmountValid);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void process() throws DataException {
|
public void process() throws DataException {
|
||||||
MessageTransactionData messageTransactionData = (MessageTransactionData) this.transactionData;
|
MessageTransactionData messageTransactionData = (MessageTransactionData) this.transactionData;
|
||||||
long assetId = messageTransactionData.getAssetId();
|
|
||||||
|
|
||||||
// Save this transaction itself
|
// Save this transaction itself
|
||||||
this.repository.getTransactionRepository().save(this.transactionData);
|
this.repository.getTransactionRepository().save(this.transactionData);
|
||||||
|
|
||||||
// Update sender's balance due to amount
|
// Wrap and delegate payment processing to Payment class
|
||||||
Account sender = new PublicKeyAccount(this.repository, messageTransactionData.getSenderPublicKey());
|
new Payment(this.repository).process(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
|
||||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(messageTransactionData.getAmount()));
|
messageTransactionData.getSignature());
|
||||||
// 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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void orphan() throws DataException {
|
public void orphan() throws DataException {
|
||||||
MessageTransactionData messageTransactionData = (MessageTransactionData) this.transactionData;
|
MessageTransactionData messageTransactionData = (MessageTransactionData) this.transactionData;
|
||||||
long assetId = messageTransactionData.getAssetId();
|
|
||||||
|
|
||||||
// Delete this transaction itself
|
// Delete this transaction itself
|
||||||
this.repository.getTransactionRepository().delete(this.transactionData);
|
this.repository.getTransactionRepository().delete(this.transactionData);
|
||||||
|
|
||||||
// Update sender's balance due to amount
|
// Wrap and delegate payment processing to Payment class
|
||||||
Account sender = new PublicKeyAccount(this.repository, messageTransactionData.getSenderPublicKey());
|
new Payment(this.repository).orphan(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(),
|
||||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).add(messageTransactionData.getAmount()));
|
messageTransactionData.getSignature(), messageTransactionData.getReference());
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
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;
|
package qora.transaction;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import data.PaymentData;
|
||||||
import data.transaction.PaymentTransactionData;
|
import data.transaction.PaymentTransactionData;
|
||||||
import data.transaction.TransactionData;
|
import data.transaction.TransactionData;
|
||||||
import qora.account.Account;
|
import qora.account.Account;
|
||||||
import qora.account.PublicKeyAccount;
|
import qora.account.PublicKeyAccount;
|
||||||
import qora.assets.Asset;
|
import qora.assets.Asset;
|
||||||
import qora.crypto.Crypto;
|
import qora.payment.Payment;
|
||||||
import repository.DataException;
|
import repository.DataException;
|
||||||
import repository.Repository;
|
import repository.Repository;
|
||||||
|
|
||||||
@ -22,33 +22,21 @@ public class PaymentTransaction extends Transaction {
|
|||||||
|
|
||||||
// Processing
|
// Processing
|
||||||
|
|
||||||
public ValidationResult isValid() throws DataException {
|
private PaymentData getPaymentData() {
|
||||||
// Lowest cost checks first
|
|
||||||
|
|
||||||
PaymentTransactionData paymentTransactionData = (PaymentTransactionData) this.transactionData;
|
PaymentTransactionData paymentTransactionData = (PaymentTransactionData) this.transactionData;
|
||||||
|
return new PaymentData(paymentTransactionData.getRecipient(), Asset.QORA, paymentTransactionData.getAmount());
|
||||||
|
}
|
||||||
|
|
||||||
// Check recipient is a valid address
|
public ValidationResult isValid() throws DataException {
|
||||||
if (!Crypto.isValidAddress(paymentTransactionData.getRecipient()))
|
PaymentTransactionData paymentTransactionData = (PaymentTransactionData) this.transactionData;
|
||||||
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;
|
|
||||||
|
|
||||||
// Check reference is correct
|
// Check reference is correct
|
||||||
Account sender = new PublicKeyAccount(repository, paymentTransactionData.getSenderPublicKey());
|
Account sender = new PublicKeyAccount(repository, paymentTransactionData.getSenderPublicKey());
|
||||||
if (!Arrays.equals(sender.getLastReference(), paymentTransactionData.getReference()))
|
if (!Arrays.equals(sender.getLastReference(), paymentTransactionData.getReference()))
|
||||||
return ValidationResult.INVALID_REFERENCE;
|
return ValidationResult.INVALID_REFERENCE;
|
||||||
|
|
||||||
// Check sender has enough funds
|
// Wrap and delegate final payment checks to Payment class
|
||||||
if (sender.getConfirmedBalance(Asset.QORA).compareTo(paymentTransactionData.getAmount().add(paymentTransactionData.getFee())) == -1)
|
return new Payment(this.repository).isValid(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee());
|
||||||
return ValidationResult.NO_BALANCE;
|
|
||||||
|
|
||||||
return ValidationResult.OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void process() throws DataException {
|
public void process() throws DataException {
|
||||||
@ -57,21 +45,9 @@ public class PaymentTransaction extends Transaction {
|
|||||||
// Save this transaction itself
|
// Save this transaction itself
|
||||||
this.repository.getTransactionRepository().save(this.transactionData);
|
this.repository.getTransactionRepository().save(this.transactionData);
|
||||||
|
|
||||||
// Update sender's balance
|
// Wrap and delegate payment processing to Payment class
|
||||||
Account sender = new PublicKeyAccount(repository, paymentTransactionData.getSenderPublicKey());
|
new Payment(this.repository).process(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee(),
|
||||||
sender.setConfirmedBalance(Asset.QORA,
|
paymentTransactionData.getSignature());
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void orphan() throws DataException {
|
public void orphan() throws DataException {
|
||||||
@ -80,24 +56,9 @@ public class PaymentTransaction extends Transaction {
|
|||||||
// Delete this transaction
|
// Delete this transaction
|
||||||
this.repository.getTransactionRepository().delete(this.transactionData);
|
this.repository.getTransactionRepository().delete(this.transactionData);
|
||||||
|
|
||||||
// Update sender's balance
|
// Wrap and delegate payment processing to Payment class
|
||||||
Account sender = new PublicKeyAccount(repository, paymentTransactionData.getSenderPublicKey());
|
new Payment(this.repository).orphan(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee(),
|
||||||
sender.setConfirmedBalance(Asset.QORA,
|
paymentTransactionData.getSignature(), paymentTransactionData.getReference());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -41,8 +41,10 @@ public abstract class Transaction {
|
|||||||
|
|
||||||
// Validation results
|
// Validation results
|
||||||
public enum ValidationResult {
|
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(
|
OK(1), INVALID_ADDRESS(2), NEGATIVE_AMOUNT(3), NEGATIVE_FEE(4), NO_BALANCE(5), INVALID_REFERENCE(6), INVALID_NAME_LENGTH(7), INVALID_AMOUNT(
|
||||||
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);
|
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;
|
public final int value;
|
||||||
|
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
package qora.transaction;
|
package qora.transaction;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import data.assets.AssetData;
|
import data.PaymentData;
|
||||||
import data.transaction.TransactionData;
|
import data.transaction.TransactionData;
|
||||||
import data.transaction.TransferAssetTransactionData;
|
import data.transaction.TransferAssetTransactionData;
|
||||||
import utils.NTP;
|
import utils.NTP;
|
||||||
import qora.account.Account;
|
|
||||||
import qora.account.PublicKeyAccount;
|
import qora.account.PublicKeyAccount;
|
||||||
import qora.assets.Asset;
|
|
||||||
import qora.block.Block;
|
import qora.block.Block;
|
||||||
import qora.crypto.Crypto;
|
import qora.payment.Payment;
|
||||||
import repository.AssetRepository;
|
|
||||||
import repository.DataException;
|
import repository.DataException;
|
||||||
import repository.Repository;
|
import repository.Repository;
|
||||||
|
|
||||||
@ -26,60 +22,28 @@ public class TransferAssetTransaction extends Transaction {
|
|||||||
|
|
||||||
// Processing
|
// Processing
|
||||||
|
|
||||||
|
private PaymentData getPaymentData() {
|
||||||
|
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
|
||||||
|
return new PaymentData(transferAssetTransactionData.getRecipient(), transferAssetTransactionData.getAssetId(),
|
||||||
|
transferAssetTransactionData.getAmount());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult isValid() throws DataException {
|
public ValidationResult isValid() throws DataException {
|
||||||
// Lowest cost checks first
|
|
||||||
|
|
||||||
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
|
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
|
||||||
|
|
||||||
// Are IssueAssetTransactions even allowed at this point?
|
// Are IssueAssetTransactions even allowed at this point?
|
||||||
if (NTP.getTime() < Block.ASSETS_RELEASE_TIMESTAMP)
|
if (NTP.getTime() < Block.ASSETS_RELEASE_TIMESTAMP)
|
||||||
return ValidationResult.NOT_YET_RELEASED;
|
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
|
// Check reference is correct
|
||||||
PublicKeyAccount sender = new PublicKeyAccount(this.repository, transferAssetTransactionData.getSenderPublicKey());
|
PublicKeyAccount sender = new PublicKeyAccount(this.repository, transferAssetTransactionData.getSenderPublicKey());
|
||||||
|
|
||||||
if (!Arrays.equals(sender.getLastReference(), transferAssetTransactionData.getReference()))
|
if (!Arrays.equals(sender.getLastReference(), transferAssetTransactionData.getReference()))
|
||||||
return ValidationResult.INVALID_REFERENCE;
|
return ValidationResult.INVALID_REFERENCE;
|
||||||
|
|
||||||
// Check sender has enough asset balance AFTER removing fee, in case asset is QORA
|
// Wrap asset transfer as a payment and delegate final payment checks to Payment class
|
||||||
long assetId = transferAssetTransactionData.getAssetId();
|
return new Payment(this.repository).isValid(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee());
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PROCESS/ORPHAN
|
// PROCESS/ORPHAN
|
||||||
@ -87,56 +51,25 @@ public class TransferAssetTransaction extends Transaction {
|
|||||||
@Override
|
@Override
|
||||||
public void process() throws DataException {
|
public void process() throws DataException {
|
||||||
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
|
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
|
||||||
long assetId = transferAssetTransactionData.getAssetId();
|
|
||||||
|
|
||||||
// Save this transaction itself
|
// Save this transaction itself
|
||||||
this.repository.getTransactionRepository().save(this.transactionData);
|
this.repository.getTransactionRepository().save(this.transactionData);
|
||||||
|
|
||||||
// Update sender's balance due to amount
|
// Wrap asset transfer as a payment and delegate processing to Payment class
|
||||||
Account sender = new PublicKeyAccount(this.repository, transferAssetTransactionData.getSenderPublicKey());
|
new Payment(this.repository).process(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(),
|
||||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(transferAssetTransactionData.getAmount()));
|
transferAssetTransactionData.getSignature());
|
||||||
// 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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void orphan() throws DataException {
|
public void orphan() throws DataException {
|
||||||
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
|
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
|
||||||
long assetId = transferAssetTransactionData.getAssetId();
|
|
||||||
|
|
||||||
// Delete this transaction itself
|
// Delete this transaction itself
|
||||||
this.repository.getTransactionRepository().delete(this.transactionData);
|
this.repository.getTransactionRepository().delete(this.transactionData);
|
||||||
|
|
||||||
// Update sender's balance due to amount
|
// Wrap asset transfer as a payment and delegate processing to Payment class
|
||||||
Account sender = new PublicKeyAccount(this.repository, transferAssetTransactionData.getSenderPublicKey());
|
new Payment(this.repository).orphan(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(),
|
||||||
sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).add(transferAssetTransactionData.getAmount()));
|
transferAssetTransactionData.getSignature(), transferAssetTransactionData.getReference());
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,4 +21,8 @@ public interface AssetRepository {
|
|||||||
|
|
||||||
public OrderData fromOrderId(byte[] orderId) throws DataException;
|
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 {
|
public OrderData fromOrderId(byte[] orderId) throws DataException {
|
||||||
try {
|
try {
|
||||||
ResultSet resultSet = this.repository.checkedExecute(
|
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)
|
if (resultSet == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -95,10 +96,32 @@ public class HSQLDBAssetRepository implements AssetRepository {
|
|||||||
BigDecimal fulfilled = resultSet.getBigDecimal(5);
|
BigDecimal fulfilled = resultSet.getBigDecimal(5);
|
||||||
BigDecimal price = resultSet.getBigDecimal(6);
|
BigDecimal price = resultSet.getBigDecimal(6);
|
||||||
long timestamp = resultSet.getTimestamp(7).getTime();
|
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) {
|
} 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
|
// Asset Orders
|
||||||
stmt.execute(
|
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, "
|
"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))");
|
+ "PRIMARY KEY (asset_order_id))");
|
||||||
stmt.execute("CREATE INDEX AssetOrderHaveIndex on AssetOrders (have_asset_id)");
|
stmt.execute("CREATE INDEX AssetOrderHaveIndex on AssetOrders (have_asset_id, is_closed)");
|
||||||
stmt.execute("CREATE INDEX AssetOrderWantIndex on AssetOrders (want_asset_id)");
|
stmt.execute("CREATE INDEX AssetOrderWantIndex on AssetOrders (want_asset_id, is_closed)");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
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