Added BuyNameTransaction support

Fixed IssueAssetTransactions not being constructed with signature.
Fixed incorrect MessageTransactionData constructors.

Refactored various transactions to remove duplicate code.
e.g. in CancelOrderTransaction.process() use getCreator() instead of explicit repository call.

Added name_reference to BuyNameTransactions HSQLDB table.

Fixed incorrect SQL in HSQLDBMultiPaymentTransactionRepository.

More unit tests!

Fixed wrong data length in CancelOrderTransactionTransformer.
Fixed wrong data length in CreateOrderTransactionTransformer.

Fixed missing payment bytes in MultiPaymentTransactionTransformer.toBytes();
This commit is contained in:
catbref 2018-07-03 11:40:24 +01:00
parent c8be77e7cc
commit 2bfb0227ae
43 changed files with 790 additions and 111 deletions

View File

@ -0,0 +1,68 @@
package data.transaction;
import java.math.BigDecimal;
import qora.transaction.Transaction.TransactionType;
public class BuyNameTransactionData extends TransactionData {
// Properties
private byte[] buyerPublicKey;
private String name;
private BigDecimal amount;
private String seller;
private byte[] nameReference;
// Constructors
public BuyNameTransactionData(byte[] buyerPublicKey, String name, BigDecimal amount, String seller, byte[] nameReference, BigDecimal fee, long timestamp,
byte[] reference, byte[] signature) {
super(TransactionType.BUY_NAME, fee, buyerPublicKey, timestamp, reference, signature);
this.buyerPublicKey = buyerPublicKey;
this.name = name;
this.amount = amount;
this.seller = seller;
}
public BuyNameTransactionData(byte[] buyerPublicKey, String name, BigDecimal amount, String seller, BigDecimal fee, long timestamp, byte[] reference,
byte[] signature) {
this(buyerPublicKey, name, amount, seller, null, fee, timestamp, reference, signature);
}
public BuyNameTransactionData(byte[] buyerPublicKey, String name, BigDecimal amount, String seller, byte[] nameReference, BigDecimal fee, long timestamp,
byte[] reference) {
this(buyerPublicKey, name, amount, seller, nameReference, fee, timestamp, reference, null);
}
public BuyNameTransactionData(byte[] buyerPublicKey, String name, BigDecimal amount, String seller, BigDecimal fee, long timestamp, byte[] reference) {
this(buyerPublicKey, name, amount, seller, null, fee, timestamp, reference, null);
}
// Getters / setters
public byte[] getBuyerPublicKey() {
return this.buyerPublicKey;
}
public String getName() {
return this.name;
}
public BigDecimal getAmount() {
return this.amount;
}
public String getSeller() {
return this.seller;
}
public byte[] getNameReference() {
return this.nameReference;
}
public void setNameReference(byte[] nameReference) {
this.nameReference = nameReference;
}
}

View File

@ -19,6 +19,10 @@ public class CancelOrderTransactionData extends TransactionData {
this.orderId = orderId;
}
public CancelOrderTransactionData(byte[] creatorPublicKey, byte[] orderId, BigDecimal fee, long timestamp, byte[] reference) {
this(creatorPublicKey, orderId, fee, timestamp, reference, null);
}
// Getters/Setters
public byte[] getCreatorPublicKey() {

View File

@ -20,7 +20,7 @@ public class IssueAssetTransactionData extends TransactionData {
public IssueAssetTransactionData(Long assetId, byte[] issuerPublicKey, String owner, String assetName, String description, long quantity,
boolean isDivisible, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) {
super(TransactionType.ISSUE_ASSET, fee, issuerPublicKey, timestamp, reference);
super(TransactionType.ISSUE_ASSET, fee, issuerPublicKey, timestamp, reference, signature);
this.assetId = assetId;
this.issuerPublicKey = issuerPublicKey;

View File

@ -18,8 +18,9 @@ public class MessageTransactionData extends TransactionData {
protected boolean isEncrypted;
// Constructors
public MessageTransactionData(int version, byte[] senderPublicKey, String recipient, Long assetId, BigDecimal amount, BigDecimal fee, byte[] data,
boolean isText, boolean isEncrypted, long timestamp, byte[] reference, byte[] signature) {
public MessageTransactionData(int version, byte[] senderPublicKey, String recipient, Long assetId, BigDecimal amount, byte[] data, boolean isText,
boolean isEncrypted, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) {
super(TransactionType.MESSAGE, fee, senderPublicKey, timestamp, reference, signature);
this.version = version;
@ -37,6 +38,11 @@ public class MessageTransactionData extends TransactionData {
this.isEncrypted = isEncrypted;
}
public MessageTransactionData(int version, byte[] senderPublicKey, String recipient, Long assetId, BigDecimal amount, byte[] data, boolean isText,
boolean isEncrypted, BigDecimal fee, long timestamp, byte[] reference) {
this(version, senderPublicKey, recipient, assetId, amount, data, isText, isEncrypted, fee, timestamp, reference, null);
}
// Getters/Setters
public int getVersion() {

View File

@ -26,6 +26,11 @@ public class UpdateNameTransactionData extends TransactionData {
this.nameReference = nameReference;
}
public UpdateNameTransactionData(byte[] ownerPublicKey, String newOwner, String name, String newData, BigDecimal fee, long timestamp, byte[] reference,
byte[] signature) {
this(ownerPublicKey, newOwner, name, newData, null, fee, timestamp, reference, signature);
}
public UpdateNameTransactionData(byte[] ownerPublicKey, String newOwner, String name, String newData, byte[] nameReference, BigDecimal fee, long timestamp,
byte[] reference) {
this(ownerPublicKey, newOwner, name, newData, nameReference, fee, timestamp, reference, null);

View File

@ -1,11 +1,14 @@
package qora.naming;
import data.naming.NameData;
import data.transaction.BuyNameTransactionData;
import data.transaction.CancelSellNameTransactionData;
import data.transaction.RegisterNameTransactionData;
import data.transaction.SellNameTransactionData;
import data.transaction.TransactionData;
import data.transaction.UpdateNameTransactionData;
import qora.account.Account;
import qora.account.PublicKeyAccount;
import repository.DataException;
import repository.Repository;
@ -56,6 +59,35 @@ public class Name {
this.repository.getNameRepository().delete(this.nameData.getName());
}
private void revert() throws DataException {
TransactionData previousTransactionData = this.repository.getTransactionRepository().fromSignature(this.nameData.getReference());
if (previousTransactionData == null)
throw new DataException("Unable to revert name transaction as referenced transaction not found in repository");
switch (previousTransactionData.getType()) {
case REGISTER_NAME:
RegisterNameTransactionData previousRegisterNameTransactionData = (RegisterNameTransactionData) previousTransactionData;
this.nameData.setOwner(previousRegisterNameTransactionData.getOwner());
this.nameData.setData(previousRegisterNameTransactionData.getData());
break;
case UPDATE_NAME:
UpdateNameTransactionData previousUpdateNameTransactionData = (UpdateNameTransactionData) previousTransactionData;
this.nameData.setData(previousUpdateNameTransactionData.getNewData());
this.nameData.setOwner(previousUpdateNameTransactionData.getNewOwner());
break;
case BUY_NAME:
BuyNameTransactionData previousBuyNameTransactionData = (BuyNameTransactionData) previousTransactionData;
Account buyer = new PublicKeyAccount(this.repository, previousBuyNameTransactionData.getBuyerPublicKey());
this.nameData.setOwner(buyer.getAddress());
break;
default:
throw new IllegalStateException("Unable to revert name transaction due to unsupported referenced transaction");
}
}
public void update(UpdateNameTransactionData updateNameTransactionData) throws DataException {
// Update reference in transaction data
updateNameTransactionData.setNameReference(this.nameData.getReference());
@ -76,26 +108,7 @@ public class Name {
this.nameData.setReference(updateNameTransactionData.getNameReference());
// Previous Name's owner and/or data taken from referenced transaction
TransactionData previousTransactionData = this.repository.getTransactionRepository().fromSignature(this.nameData.getReference());
if (previousTransactionData == null)
throw new DataException("Unable to un-update name as referenced transaction not found in repository");
switch (previousTransactionData.getType()) {
case REGISTER_NAME:
RegisterNameTransactionData previousRegisterNameTransactionData = (RegisterNameTransactionData) previousTransactionData;
this.nameData.setOwner(previousRegisterNameTransactionData.getOwner());
this.nameData.setData(previousRegisterNameTransactionData.getData());
break;
case UPDATE_NAME:
UpdateNameTransactionData previousUpdateNameTransactionData = (UpdateNameTransactionData) previousTransactionData;
this.nameData.setData(previousUpdateNameTransactionData.getNewData());
this.nameData.setOwner(previousUpdateNameTransactionData.getNewOwner());
break;
default:
throw new IllegalStateException("Unable to revert update name transaction due to unsupported referenced transaction");
}
this.revert();
// Save reverted name data
this.repository.getNameRepository().save(this.nameData);
@ -135,4 +148,36 @@ public class Name {
this.repository.getNameRepository().save(this.nameData);
}
public void buy(BuyNameTransactionData buyNameTransactionData) throws DataException {
// Mark not for-sale but leave price in case we want to orphan
this.nameData.setIsForSale(false);
// Set new owner
Account buyer = new PublicKeyAccount(this.repository, buyNameTransactionData.getBuyerPublicKey());
this.nameData.setOwner(buyer.getAddress());
// Update reference in transaction data
buyNameTransactionData.setNameReference(this.nameData.getReference());
// New name reference is this transaction's signature
this.nameData.setReference(buyNameTransactionData.getSignature());
// Save updated name data
this.repository.getNameRepository().save(this.nameData);
}
public void unbuy(BuyNameTransactionData buyNameTransactionData) throws DataException {
// Mark as for-sale using existing price
this.nameData.setIsForSale(true);
// Previous name reference is taken from this transaction's cached copy
this.nameData.setReference(buyNameTransactionData.getNameReference());
// Previous Name's owner and/or data taken from referenced transaction
this.revert();
// Save reverted name data
this.repository.getNameRepository().save(this.nameData);
}
}

View File

@ -0,0 +1,155 @@
package qora.transaction;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.google.common.base.Utf8;
import data.naming.NameData;
import data.transaction.BuyNameTransactionData;
import data.transaction.TransactionData;
import qora.account.Account;
import qora.account.PublicKeyAccount;
import qora.assets.Asset;
import qora.naming.Name;
import repository.DataException;
import repository.Repository;
public class BuyNameTransaction extends Transaction {
// Properties
private BuyNameTransactionData buyNameTransactionData;
// Constructors
public BuyNameTransaction(Repository repository, TransactionData transactionData) {
super(repository, transactionData);
this.buyNameTransactionData = (BuyNameTransactionData) this.transactionData;
}
// More information
@Override
public List<Account> getRecipientAccounts() throws DataException {
return Collections.singletonList(new Account(this.repository, this.buyNameTransactionData.getSeller()));
}
@Override
public boolean isInvolved(Account account) throws DataException {
String address = account.getAddress();
if (address.equals(this.getBuyer().getAddress()))
return true;
if (address.equals(this.buyNameTransactionData.getSeller()))
return true;
return false;
}
@Override
public BigDecimal getAmount(Account account) throws DataException {
String address = account.getAddress();
BigDecimal amount = BigDecimal.ZERO.setScale(8);
if (address.equals(this.getBuyer().getAddress()))
amount = amount.subtract(this.transactionData.getFee());
return amount;
}
// Navigation
public Account getBuyer() throws DataException {
return new PublicKeyAccount(this.repository, this.buyNameTransactionData.getBuyerPublicKey());
}
// Processing
@Override
public ValidationResult isValid() throws DataException {
// Check name size bounds
int nameLength = Utf8.encodedLength(buyNameTransactionData.getName());
if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH;
// Check name is lowercase
if (!buyNameTransactionData.getName().equals(buyNameTransactionData.getName().toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE;
NameData nameData = this.repository.getNameRepository().fromName(buyNameTransactionData.getName());
// Check name exists
if (nameData == null)
return ValidationResult.NAME_DOES_NOT_EXIST;
// Check name is currently for sale
if (!nameData.getIsForSale())
return ValidationResult.NAME_NOT_FOR_SALE;
// Check buyer isn't trying to buy own name
Account buyer = getBuyer();
if (buyer.getAddress().equals(nameData.getOwner()))
return ValidationResult.BUYER_ALREADY_OWNER;
// Check expected seller currently owns name
if (!buyNameTransactionData.getSeller().equals(nameData.getOwner()))
return ValidationResult.INVALID_SELLER;
// Check amounts agree
if (buyNameTransactionData.getAmount().compareTo(nameData.getSalePrice()) != 0)
return ValidationResult.INVALID_AMOUNT;
// Check fee is positive
if (buyNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
return ValidationResult.NEGATIVE_FEE;
// Check reference is correct
if (!Arrays.equals(buyer.getLastReference(), buyNameTransactionData.getReference()))
return ValidationResult.INVALID_REFERENCE;
// Check issuer has enough funds
if (buyer.getConfirmedBalance(Asset.QORA).compareTo(buyNameTransactionData.getFee()) == -1)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
}
@Override
public void process() throws DataException {
// Update Name
Name name = new Name(this.repository, buyNameTransactionData.getName());
name.buy(buyNameTransactionData);
// Save this transaction, now with updated "name reference" to previous transaction that updated name
this.repository.getTransactionRepository().save(buyNameTransactionData);
// Update buyer's balance
Account buyer = getBuyer();
buyer.setConfirmedBalance(Asset.QORA, buyer.getConfirmedBalance(Asset.QORA).subtract(buyNameTransactionData.getFee()));
// Update buyer's reference
buyer.setLastReference(buyNameTransactionData.getSignature());
}
@Override
public void orphan() throws DataException {
// Revert name
Name name = new Name(this.repository, buyNameTransactionData.getName());
name.unbuy(buyNameTransactionData);
// Delete this transaction itself
this.repository.getTransactionRepository().delete(buyNameTransactionData);
// Update buyer's balance
Account buyer = getBuyer();
buyer.setConfirmedBalance(Asset.QORA, buyer.getConfirmedBalance(Asset.QORA).add(buyNameTransactionData.getFee()));
// Update buyer's reference
buyer.setLastReference(buyNameTransactionData.getReference());
}
}

View File

@ -49,6 +49,12 @@ public class CancelOrderTransaction extends Transaction {
return amount;
}
// Navigation
public Account getCreator() throws DataException {
return new PublicKeyAccount(this.repository, cancelOrderTransactionData.getCreatorPublicKey());
}
// Processing
@Override
@ -65,7 +71,7 @@ public class CancelOrderTransaction extends Transaction {
if (orderData == null)
return ValidationResult.ORDER_DOES_NOT_EXIST;
Account creator = new PublicKeyAccount(this.repository, cancelOrderTransactionData.getCreatorPublicKey());
Account creator = getCreator();
// Check creator's public key results in valid address
if (!Crypto.isValidAddress(creator.getAddress()))
@ -89,7 +95,7 @@ public class CancelOrderTransaction extends Transaction {
@Override
public void process() throws DataException {
Account creator = new PublicKeyAccount(this.repository, cancelOrderTransactionData.getCreatorPublicKey());
Account creator = getCreator();
// Save this transaction itself
this.repository.getTransactionRepository().save(this.transactionData);
@ -111,7 +117,7 @@ public class CancelOrderTransaction extends Transaction {
@Override
public void orphan() throws DataException {
Account creator = new PublicKeyAccount(this.repository, cancelOrderTransactionData.getCreatorPublicKey());
Account creator = getCreator();
// Save this transaction itself
this.repository.getTransactionRepository().delete(this.transactionData);

View File

@ -88,7 +88,7 @@ public class CancelSellNameTransaction extends Transaction {
return ValidationResult.NAME_NOT_FOR_SALE;
// Check transaction's public key matches name's current owner
Account owner = new PublicKeyAccount(this.repository, cancelSellNameTransactionData.getOwnerPublicKey());
Account owner = getOwner();
if (!owner.getAddress().equals(nameData.getOwner()))
return ValidationResult.INVALID_NAME_OWNER;
@ -118,7 +118,7 @@ public class CancelSellNameTransaction extends Transaction {
this.repository.getTransactionRepository().save(cancelSellNameTransactionData);
// Update owner's balance
Account owner = new PublicKeyAccount(this.repository, cancelSellNameTransactionData.getOwnerPublicKey());
Account owner = getOwner();
owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).subtract(cancelSellNameTransactionData.getFee()));
// Update owner's reference
@ -135,7 +135,7 @@ public class CancelSellNameTransaction extends Transaction {
this.repository.getTransactionRepository().delete(cancelSellNameTransactionData);
// Update owner's balance
Account owner = new PublicKeyAccount(this.repository, cancelSellNameTransactionData.getOwnerPublicKey());
Account owner = getOwner();
owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).add(cancelSellNameTransactionData.getFee()));
// Update owner's reference

View File

@ -52,6 +52,10 @@ public class CreateOrderTransaction extends Transaction {
// Navigation
public Account getCreator() throws DataException {
return new PublicKeyAccount(this.repository, createOrderTransactionData.getCreatorPublicKey());
}
public Order getOrder() throws DataException {
// orderId is the transaction signature
OrderData orderData = this.repository.getAssetRepository().fromOrderId(this.createOrderTransactionData.getSignature());
@ -92,7 +96,7 @@ public class CreateOrderTransaction extends Transaction {
if (wantAssetData == null)
return ValidationResult.ASSET_DOES_NOT_EXIST;
Account creator = new PublicKeyAccount(this.repository, createOrderTransactionData.getCreatorPublicKey());
Account creator = getCreator();
// Check reference is correct
if (!Arrays.equals(creator.getLastReference(), createOrderTransactionData.getReference()))
@ -129,7 +133,7 @@ public class CreateOrderTransaction extends Transaction {
}
public void process() throws DataException {
Account creator = new PublicKeyAccount(this.repository, createOrderTransactionData.getCreatorPublicKey());
Account creator = getCreator();
// Update creator's balance due to fee
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(createOrderTransactionData.getFee()));
@ -152,7 +156,7 @@ public class CreateOrderTransaction extends Transaction {
}
public void orphan() throws DataException {
Account creator = new PublicKeyAccount(this.repository, createOrderTransactionData.getCreatorPublicKey());
Account creator = getCreator();
// Update creator's balance due to fee
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).add(createOrderTransactionData.getFee()));

View File

@ -131,7 +131,7 @@ public class CreatePollTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check reference is correct
PublicKeyAccount creator = new PublicKeyAccount(this.repository, createPollTransactionData.getCreatorPublicKey());
Account creator = getCreator();
if (!Arrays.equals(creator.getLastReference(), createPollTransactionData.getReference()))
return ValidationResult.INVALID_REFERENCE;
@ -153,7 +153,7 @@ public class CreatePollTransaction extends Transaction {
this.repository.getTransactionRepository().save(createPollTransactionData);
// Update creator's balance
Account creator = new PublicKeyAccount(this.repository, createPollTransactionData.getCreatorPublicKey());
Account creator = getCreator();
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(createPollTransactionData.getFee()));
// Update creator's reference
@ -170,7 +170,7 @@ public class CreatePollTransaction extends Transaction {
this.repository.getTransactionRepository().delete(createPollTransactionData);
// Update creator's balance
Account creator = new PublicKeyAccount(this.repository, createPollTransactionData.getCreatorPublicKey());
Account creator = getCreator();
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).add(createPollTransactionData.getFee()));
// Update creator's reference

View File

@ -106,7 +106,7 @@ public class IssueAssetTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check reference is correct
PublicKeyAccount issuer = new PublicKeyAccount(this.repository, issueAssetTransactionData.getIssuerPublicKey());
Account issuer = getIssuer();
if (!Arrays.equals(issuer.getLastReference(), issueAssetTransactionData.getReference()))
return ValidationResult.INVALID_REFERENCE;
@ -134,20 +134,20 @@ public class IssueAssetTransaction extends Transaction {
this.repository.getTransactionRepository().save(issueAssetTransactionData);
// Update issuer's balance
Account issuer = new PublicKeyAccount(this.repository, issueAssetTransactionData.getIssuerPublicKey());
Account issuer = getIssuer();
issuer.setConfirmedBalance(Asset.QORA, issuer.getConfirmedBalance(Asset.QORA).subtract(issueAssetTransactionData.getFee()));
// Update issuer's reference
issuer.setLastReference(issueAssetTransactionData.getSignature());
// Add asset to owner
Account owner = new Account(this.repository, issueAssetTransactionData.getOwner());
Account owner = getOwner();
owner.setConfirmedBalance(issueAssetTransactionData.getAssetId(), BigDecimal.valueOf(issueAssetTransactionData.getQuantity()).setScale(8));
}
public void orphan() throws DataException {
// Remove asset from owner
Account owner = new Account(this.repository, issueAssetTransactionData.getOwner());
Account owner = getOwner();
owner.deleteBalance(issueAssetTransactionData.getAssetId());
// Issue asset
@ -158,7 +158,7 @@ public class IssueAssetTransaction extends Transaction {
this.repository.getTransactionRepository().delete(issueAssetTransactionData);
// Update issuer's balance
Account issuer = new PublicKeyAccount(this.repository, issueAssetTransactionData.getIssuerPublicKey());
Account issuer = getIssuer();
issuer.setConfirmedBalance(Asset.QORA, issuer.getConfirmedBalance(Asset.QORA).add(issueAssetTransactionData.getFee()));
// Update issuer's reference

View File

@ -98,7 +98,7 @@ public class MessageTransaction extends Transaction {
return ValidationResult.INVALID_DATA_LENGTH;
// Check reference is correct
Account sender = new PublicKeyAccount(this.repository, messageTransactionData.getSenderPublicKey());
Account sender = getSender();
if (!Arrays.equals(sender.getLastReference(), messageTransactionData.getReference()))
return ValidationResult.INVALID_REFERENCE;

View File

@ -98,7 +98,7 @@ public class MultiPaymentTransaction extends Transaction {
return ValidationResult.INVALID_PAYMENTS_COUNT;
// Check reference is correct
PublicKeyAccount sender = new PublicKeyAccount(this.repository, multiPaymentTransactionData.getSenderPublicKey());
Account sender = getSender();
if (!Arrays.equals(sender.getLastReference(), multiPaymentTransactionData.getReference()))
return ValidationResult.INVALID_REFERENCE;

View File

@ -74,7 +74,7 @@ public class PaymentTransaction extends Transaction {
public ValidationResult isValid() throws DataException {
// Check reference is correct
Account sender = new PublicKeyAccount(repository, paymentTransactionData.getSenderPublicKey());
Account sender = getSender();
if (!Arrays.equals(sender.getLastReference(), paymentTransactionData.getReference()))
return ValidationResult.INVALID_REFERENCE;

View File

@ -102,7 +102,7 @@ public class RegisterNameTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check reference is correct
PublicKeyAccount registrant = new PublicKeyAccount(this.repository, registerNameTransactionData.getRegistrantPublicKey());
Account registrant = getRegistrant();
if (!Arrays.equals(registrant.getLastReference(), registerNameTransactionData.getReference()))
return ValidationResult.INVALID_REFERENCE;
@ -124,7 +124,7 @@ public class RegisterNameTransaction extends Transaction {
this.repository.getTransactionRepository().save(registerNameTransactionData);
// Update registrant's balance
Account registrant = new PublicKeyAccount(this.repository, registerNameTransactionData.getRegistrantPublicKey());
Account registrant = getRegistrant();
registrant.setConfirmedBalance(Asset.QORA, registrant.getConfirmedBalance(Asset.QORA).subtract(registerNameTransactionData.getFee()));
// Update registrant's reference
@ -141,7 +141,7 @@ public class RegisterNameTransaction extends Transaction {
this.repository.getTransactionRepository().delete(registerNameTransactionData);
// Update registrant's balance
Account registrant = new PublicKeyAccount(this.repository, registerNameTransactionData.getRegistrantPublicKey());
Account registrant = getRegistrant();
registrant.setConfirmedBalance(Asset.QORA, registrant.getConfirmedBalance(Asset.QORA).add(registerNameTransactionData.getFee()));
// Update registrant's reference

View File

@ -89,7 +89,7 @@ public class SellNameTransaction extends Transaction {
return ValidationResult.NAME_ALREADY_FOR_SALE;
// Check transaction's public key matches name's current owner
Account owner = new PublicKeyAccount(this.repository, sellNameTransactionData.getOwnerPublicKey());
Account owner = getOwner();
if (!owner.getAddress().equals(nameData.getOwner()))
return ValidationResult.INVALID_NAME_OWNER;
@ -114,7 +114,6 @@ public class SellNameTransaction extends Transaction {
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
}
@Override
@ -127,7 +126,7 @@ public class SellNameTransaction extends Transaction {
this.repository.getTransactionRepository().save(sellNameTransactionData);
// Update owner's balance
Account owner = new PublicKeyAccount(this.repository, sellNameTransactionData.getOwnerPublicKey());
Account owner = getOwner();
owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).subtract(sellNameTransactionData.getFee()));
// Update owner's reference
@ -144,7 +143,7 @@ public class SellNameTransaction extends Transaction {
this.repository.getTransactionRepository().delete(sellNameTransactionData);
// Update owner's balance
Account owner = new PublicKeyAccount(this.repository, sellNameTransactionData.getOwnerPublicKey());
Account owner = getOwner();
owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).add(sellNameTransactionData.getFee()));
// Update owner's reference

View File

@ -44,12 +44,13 @@ 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_VALUE_LENGTH(
8), NAME_ALREADY_REGISTERED(9), NAME_DOES_NOT_EXIST(10), INVALID_NAME_OWNER(11), NAME_ALREADY_FOR_SALE(12), NAME_NOT_FOR_SALE(13), INVALID_AMOUNT(
15), NAME_NOT_LOWER_CASE(17), INVALID_DESCRIPTION_LENGTH(18), INVALID_OPTIONS_COUNT(19), INVALID_OPTION_LENGTH(20), DUPLICATE_OPTION(
21), POLL_ALREADY_EXISTS(22), POLL_DOES_NOT_EXIST(24), POLL_OPTION_DOES_NOT_EXIST(25), ALREADY_VOTED_FOR_THAT_OPTION(
26), 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);
8), NAME_ALREADY_REGISTERED(9), NAME_DOES_NOT_EXIST(10), INVALID_NAME_OWNER(11), NAME_ALREADY_FOR_SALE(12), NAME_NOT_FOR_SALE(
13), BUYER_ALREADY_OWNER(14), INVALID_AMOUNT(15), INVALID_SELLER(16), NAME_NOT_LOWER_CASE(17), INVALID_DESCRIPTION_LENGTH(
18), INVALID_OPTIONS_COUNT(19), INVALID_OPTION_LENGTH(20), DUPLICATE_OPTION(21), POLL_ALREADY_EXISTS(22), POLL_DOES_NOT_EXIST(
24), POLL_OPTION_DOES_NOT_EXIST(25), ALREADY_VOTED_FOR_THAT_OPTION(26), 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;
@ -117,6 +118,9 @@ public abstract class Transaction {
case CANCEL_SELL_NAME:
return new CancelSellNameTransaction(repository, transactionData);
case BUY_NAME:
return new BuyNameTransaction(repository, transactionData);
case CREATE_POLL:
return new CreatePollTransaction(repository, transactionData);

View File

@ -89,7 +89,7 @@ public class TransferAssetTransaction extends Transaction {
return ValidationResult.NOT_YET_RELEASED;
// Check reference is correct
PublicKeyAccount sender = new PublicKeyAccount(this.repository, transferAssetTransactionData.getSenderPublicKey());
Account sender = getSender();
if (!Arrays.equals(sender.getLastReference(), transferAssetTransactionData.getReference()))
return ValidationResult.INVALID_REFERENCE;

View File

@ -105,7 +105,7 @@ public class UpdateNameTransaction extends Transaction {
return ValidationResult.NAME_ALREADY_FOR_SALE;
// Check transaction's public key matches name's current owner
Account owner = new PublicKeyAccount(this.repository, updateNameTransactionData.getOwnerPublicKey());
Account owner = getOwner();
if (!owner.getAddress().equals(nameData.getOwner()))
return ValidationResult.INVALID_NAME_OWNER;
@ -134,7 +134,7 @@ public class UpdateNameTransaction extends Transaction {
this.repository.getTransactionRepository().save(updateNameTransactionData);
// Update owner's balance
Account owner = new PublicKeyAccount(this.repository, updateNameTransactionData.getOwnerPublicKey());
Account owner = getOwner();
owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).subtract(updateNameTransactionData.getFee()));
// Update owner's reference
@ -151,7 +151,7 @@ public class UpdateNameTransaction extends Transaction {
this.repository.getTransactionRepository().delete(updateNameTransactionData);
// Update owner's balance
Account owner = new PublicKeyAccount(this.repository, updateNameTransactionData.getOwnerPublicKey());
Account owner = getOwner();
owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).add(updateNameTransactionData.getFee()));
// Update owner's reference

View File

@ -56,6 +56,12 @@ public class VoteOnPollTransaction extends Transaction {
return amount;
}
// Navigation
public Account getVoter() throws DataException {
return new PublicKeyAccount(this.repository, voteOnPollTransactionData.getVoterPublicKey());
}
// Processing
@Override
@ -98,13 +104,13 @@ public class VoteOnPollTransaction extends Transaction {
return ValidationResult.NEGATIVE_FEE;
// Check reference is correct
PublicKeyAccount creator = new PublicKeyAccount(this.repository, voteOnPollTransactionData.getCreatorPublicKey());
Account voter = getVoter();
if (!Arrays.equals(creator.getLastReference(), voteOnPollTransactionData.getReference()))
if (!Arrays.equals(voter.getLastReference(), voteOnPollTransactionData.getReference()))
return ValidationResult.INVALID_REFERENCE;
// Check issuer has enough funds
if (creator.getConfirmedBalance(Asset.QORA).compareTo(voteOnPollTransactionData.getFee()) == -1)
// Check voter has enough funds
if (voter.getConfirmedBalance(Asset.QORA).compareTo(voteOnPollTransactionData.getFee()) == -1)
return ValidationResult.NO_BALANCE;
return ValidationResult.OK;
@ -113,7 +119,7 @@ public class VoteOnPollTransaction extends Transaction {
@Override
public void process() throws DataException {
// Update voter's balance
Account voter = new PublicKeyAccount(this.repository, voteOnPollTransactionData.getVoterPublicKey());
Account voter = getVoter();
voter.setConfirmedBalance(Asset.QORA, voter.getConfirmedBalance(Asset.QORA).subtract(voteOnPollTransactionData.getFee()));
// Update vote's reference
@ -138,11 +144,11 @@ public class VoteOnPollTransaction extends Transaction {
@Override
public void orphan() throws DataException {
// Update issuer's balance
Account voter = new PublicKeyAccount(this.repository, voteOnPollTransactionData.getVoterPublicKey());
// Update voter's balance
Account voter = getVoter();
voter.setConfirmedBalance(Asset.QORA, voter.getConfirmedBalance(Asset.QORA).add(voteOnPollTransactionData.getFee()));
// Update issuer's reference
// Update voter's reference
voter.setLastReference(voteOnPollTransactionData.getReference());
// Does this transaction have previous vote info?

View File

@ -190,7 +190,7 @@ public class HSQLDBDatabaseUpdates {
case 9:
// Buy Name Transactions
stmt.execute("CREATE TABLE BuyNameTransactions (signature Signature, buyer QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "seller QoraAddress NOT NULL, amount QoraAmount NOT NULL, "
+ "seller QoraAddress NOT NULL, amount QoraAmount NOT NULL, name_reference Signature NOT NULL, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break;

View File

@ -0,0 +1,54 @@
package repository.hsqldb.transaction;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import data.transaction.BuyNameTransactionData;
import data.transaction.TransactionData;
import repository.DataException;
import repository.hsqldb.HSQLDBRepository;
import repository.hsqldb.HSQLDBSaver;
public class HSQLDBBuyNameTransactionRepository extends HSQLDBTransactionRepository {
public HSQLDBBuyNameTransactionRepository(HSQLDBRepository repository) {
this.repository = repository;
}
TransactionData fromBase(byte[] signature, byte[] reference, byte[] buyerPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT name, amount, seller, name_reference FROM BuyNameTransactions WHERE signature = ?",
signature);
if (rs == null)
return null;
String name = rs.getString(1);
BigDecimal amount = rs.getBigDecimal(2);
String seller = rs.getString(3);
byte[] nameReference = rs.getBytes(4);
return new BuyNameTransactionData(buyerPublicKey, name, amount, seller, nameReference, fee, timestamp, reference, signature);
} catch (SQLException e) {
throw new DataException("Unable to fetch buy name transaction from repository", e);
}
}
@Override
public void save(TransactionData transactionData) throws DataException {
BuyNameTransactionData buyNameTransactionData = (BuyNameTransactionData) transactionData;
HSQLDBSaver saveHelper = new HSQLDBSaver("BuyNameTransactions");
saveHelper.bind("signature", buyNameTransactionData.getSignature()).bind("buyer", buyNameTransactionData.getBuyerPublicKey())
.bind("name", buyNameTransactionData.getName()).bind("amount", buyNameTransactionData.getAmount())
.bind("seller", buyNameTransactionData.getSeller()).bind("name_reference", buyNameTransactionData.getNameReference());
try {
saveHelper.execute(this.repository);
} catch (SQLException e) {
throw new DataException("Unable to save buy name transaction into repository", e);
}
}
}

View File

@ -32,7 +32,7 @@ public class HSQLDBMessageTransactionRepository extends HSQLDBTransactionReposit
Long assetId = rs.getLong(7);
byte[] data = rs.getBytes(8);
return new MessageTransactionData(version, senderPublicKey, recipient, assetId, amount, fee, data, isText, isEncrypted, timestamp, reference,
return new MessageTransactionData(version, senderPublicKey, recipient, assetId, amount, data, isText, isEncrypted, fee, timestamp, reference,
signature);
} catch (SQLException e) {
throw new DataException("Unable to fetch message transaction from repository", e);

View File

@ -20,7 +20,7 @@ public class HSQLDBMultiPaymentTransactionRepository extends HSQLDBTransactionRe
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
try {
ResultSet rs = this.repository.checkedExecute("SELECT sender MultiPaymentTransactions WHERE signature = ?", signature);
ResultSet rs = this.repository.checkedExecute("SELECT sender from MultiPaymentTransactions WHERE signature = ?", signature);
if (rs == null)
return null;

View File

@ -25,6 +25,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
private HSQLDBUpdateNameTransactionRepository updateNameTransactionRepository;
private HSQLDBSellNameTransactionRepository sellNameTransactionRepository;
private HSQLDBCancelSellNameTransactionRepository cancelSellNameTransactionRepository;
private HSQLDBBuyNameTransactionRepository buyNameTransactionRepository;
private HSQLDBCreatePollTransactionRepository createPollTransactionRepository;
private HSQLDBVoteOnPollTransactionRepository voteOnPollTransactionRepository;
private HSQLDBIssueAssetTransactionRepository issueAssetTransactionRepository;
@ -42,6 +43,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
this.updateNameTransactionRepository = new HSQLDBUpdateNameTransactionRepository(repository);
this.sellNameTransactionRepository = new HSQLDBSellNameTransactionRepository(repository);
this.cancelSellNameTransactionRepository = new HSQLDBCancelSellNameTransactionRepository(repository);
this.buyNameTransactionRepository = new HSQLDBBuyNameTransactionRepository(repository);
this.createPollTransactionRepository = new HSQLDBCreatePollTransactionRepository(repository);
this.voteOnPollTransactionRepository = new HSQLDBVoteOnPollTransactionRepository(repository);
this.issueAssetTransactionRepository = new HSQLDBIssueAssetTransactionRepository(repository);
@ -112,6 +114,9 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
case CANCEL_SELL_NAME:
return this.cancelSellNameTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
case BUY_NAME:
return this.buyNameTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
case CREATE_POLL:
return this.createPollTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
@ -256,6 +261,10 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
this.cancelSellNameTransactionRepository.save(transactionData);
break;
case BUY_NAME:
this.buyNameTransactionRepository.save(transactionData);
break;
case CREATE_POLL:
this.createPollTransactionRepository.save(transactionData);
break;

View File

@ -61,9 +61,9 @@ public class SerializationTests extends Common {
TransactionData parsedTransactionData = TransactionTransformer.fromBytes(bytes);
assertTrue(Arrays.equals(transactionData.getSignature(), parsedTransactionData.getSignature()));
assertTrue("Transaction signature mismatch", Arrays.equals(transactionData.getSignature(), parsedTransactionData.getSignature()));
assertEquals(TransactionTransformer.getDataLength(transactionData), bytes.length);
assertEquals("Data length mismatch", TransactionTransformer.getDataLength(transactionData), bytes.length);
}
private void testSpecificBlockTransactions(int height, TransactionType type) throws DataException, TransformationException {
@ -84,13 +84,11 @@ public class SerializationTests extends Common {
@Test
public void testPaymentSerialization() throws TransformationException, DataException {
// Blocks 390 & 754 have only payment transactions
testSpecificBlockTransactions(754, TransactionType.PAYMENT);
}
@Test
public void testRegisterNameSerialization() throws TransformationException, DataException {
// Block 120 has only name registration transactions
testSpecificBlockTransactions(120, TransactionType.REGISTER_NAME);
}
@ -109,12 +107,46 @@ public class SerializationTests extends Common {
testSpecificBlockTransactions(741, TransactionType.CANCEL_SELL_NAME);
}
@Test
public void testBuyNameSerialization() throws TransformationException, DataException {
testSpecificBlockTransactions(973, TransactionType.BUY_NAME);
}
@Test
public void testCreatePollSerialization() throws TransformationException, DataException {
// Block 10537 has only create poll transactions
testSpecificBlockTransactions(10537, TransactionType.CREATE_POLL);
}
@Test
public void testVoteOnPollSerialization() throws TransformationException, DataException {
testSpecificBlockTransactions(10540, TransactionType.CREATE_POLL);
}
@Test
public void testIssueAssetSerialization() throws TransformationException, DataException {
testSpecificBlockTransactions(33661, TransactionType.ISSUE_ASSET);
}
@Test
public void testTransferAssetSerialization() throws TransformationException, DataException {
testSpecificBlockTransactions(39039, TransactionType.TRANSFER_ASSET);
}
@Test
public void testCreateAssetOrderSerialization() throws TransformationException, DataException {
testSpecificBlockTransactions(35611, TransactionType.CREATE_ASSET_ORDER);
}
@Test
public void testCancelAssetOrderSerialization() throws TransformationException, DataException {
testSpecificBlockTransactions(36176, TransactionType.CANCEL_ASSET_ORDER);
}
@Test
public void testMultiPaymentSerialization() throws TransformationException, DataException {
testSpecificBlockTransactions(34500, TransactionType.MULTIPAYMENT);
}
@Test
public void testMessageSerialization() throws TransformationException, DataException {
// Message transactions went live block 99000

View File

@ -2,6 +2,7 @@ package test;
import static org.junit.Assert.*;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
@ -16,8 +17,10 @@ import data.account.AccountBalanceData;
import data.account.AccountData;
import data.block.BlockData;
import data.naming.NameData;
import data.transaction.BuyNameTransactionData;
import data.transaction.CancelSellNameTransactionData;
import data.transaction.CreatePollTransactionData;
import data.transaction.MessageTransactionData;
import data.transaction.PaymentTransactionData;
import data.transaction.RegisterNameTransactionData;
import data.transaction.SellNameTransactionData;
@ -32,8 +35,10 @@ import qora.account.PublicKeyAccount;
import qora.assets.Asset;
import qora.block.Block;
import qora.block.BlockChain;
import qora.transaction.BuyNameTransaction;
import qora.transaction.CancelSellNameTransaction;
import qora.transaction.CreatePollTransaction;
import qora.transaction.MessageTransaction;
import qora.transaction.PaymentTransaction;
import qora.transaction.RegisterNameTransaction;
import qora.transaction.SellNameTransaction;
@ -116,6 +121,19 @@ public class TransactionTests {
RepositoryManager.closeRepositoryFactory();
}
private Transaction createPayment(PrivateKeyAccount sender, String recipient) throws DataException {
// Make a new payment transaction
BigDecimal amount = BigDecimal.valueOf(1_000L);
BigDecimal fee = BigDecimal.ONE;
long timestamp = parentBlockData.getTimestamp() + 1_000;
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(sender.getPublicKey(), recipient, amount, fee, timestamp, reference);
Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData);
paymentTransaction.calcSignature(sender);
return paymentTransaction;
}
@Test
public void testPaymentTransaction() throws DataException {
createTestAccounts(null);
@ -360,6 +378,70 @@ public class TransactionTests {
parentBlockData = block.getBlockData();
}
@Test
public void testBuyNameTransaction() throws DataException {
// Register and sell name using another test
testSellNameTransaction();
String name = "test name";
NameData originalNameData = this.repository.getNameRepository().fromName(name);
String seller = originalNameData.getOwner();
// Buyer
PrivateKeyAccount buyer = new PrivateKeyAccount(repository, recipientSeed);
byte[] nameReference = reference;
// Send buyer some funds so they have a reference
Transaction somePaymentTransaction = createPayment(sender, buyer.getAddress());
byte[] buyersReference = somePaymentTransaction.getTransactionData().getSignature();
// Forge new block with transaction
Block block = new Block(repository, parentBlockData, generator, null, null);
block.addTransaction(somePaymentTransaction.getTransactionData());
block.sign();
block.process();
repository.saveChanges();
parentBlockData = block.getBlockData();
BigDecimal fee = BigDecimal.ONE;
long timestamp = parentBlockData.getTimestamp() + 1_000;
BuyNameTransactionData buyNameTransactionData = new BuyNameTransactionData(buyer.getPublicKey(), name, originalNameData.getSalePrice(), seller,
nameReference, fee, timestamp, buyersReference);
Transaction buyNameTransaction = new BuyNameTransaction(repository, buyNameTransactionData);
buyNameTransaction.calcSignature(buyer);
assertTrue(buyNameTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, buyNameTransaction.isValid());
// Forge new block with transaction
block = new Block(repository, parentBlockData, generator, null, null);
block.addTransaction(buyNameTransactionData);
block.sign();
assertTrue("Block signatures invalid", block.isSignatureValid());
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
block.process();
repository.saveChanges();
// Check name was updated
NameData actualNameData = this.repository.getNameRepository().fromName(name);
assertFalse(actualNameData.getIsForSale());
assertEquals(originalNameData.getSalePrice(), actualNameData.getSalePrice());
assertEquals(buyer.getAddress(), actualNameData.getOwner());
// Now orphan block
block.orphan();
repository.saveChanges();
// Check name has been reverted correctly
actualNameData = this.repository.getNameRepository().fromName(name);
assertTrue(actualNameData.getIsForSale());
assertEquals(originalNameData.getSalePrice(), actualNameData.getSalePrice());
assertEquals(originalNameData.getOwner(), actualNameData.getOwner());
}
@Test
public void testCreatePollTransaction() throws DataException {
// This test requires GenesisBlock's timestamp is set to something after BlockChain.VOTING_RELEASE_TIMESTAMP
@ -491,4 +573,78 @@ public class TransactionTests {
assertTrue("Wrong voter public key", Arrays.equals(sender.getPublicKey(), votes.get(0).getVoterPublicKey()));
}
@Test
public void testIssueAssetTransaction() throws DataException {
// TODO
}
@Test
public void testTransferAssetTransaction() throws DataException {
// TODO
}
@Test
public void testCreateAssetOrderTransaction() throws DataException {
// TODO
}
@Test
public void testCancelAssetOrderTransaction() throws DataException {
// TODO
}
@Test
public void testMultiPaymentTransaction() throws DataException {
// TODO
}
@Test
public void testMessageTransaction() throws DataException, UnsupportedEncodingException {
createTestAccounts(null);
// Make a new message transaction
Account recipient = new PublicKeyAccount(repository, recipientSeed);
BigDecimal amount = BigDecimal.valueOf(1_000L);
BigDecimal fee = BigDecimal.ONE;
long timestamp = parentBlockData.getTimestamp() + 1_000;
int version = Transaction.getVersionByTimestamp(timestamp);
byte[] data = "test".getBytes("UTF-8");
boolean isText = true;
boolean isEncrypted = false;
MessageTransactionData messageTransactionData = new MessageTransactionData(version, sender.getPublicKey(), recipient.getAddress(), Asset.QORA, amount,
data, isText, isEncrypted, fee, timestamp, reference);
Transaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
messageTransaction.calcSignature(sender);
assertTrue(messageTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, messageTransaction.isValid());
// Forge new block with message transaction
Block block = new Block(repository, parentBlockData, generator, null, null);
block.addTransaction(messageTransactionData);
block.sign();
assertTrue("Block signatures invalid", block.isSignatureValid());
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
block.process();
repository.saveChanges();
// Check sender's balance
BigDecimal expectedBalance = senderBalance.subtract(amount).subtract(fee);
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
// Fee should be in generator's balance
expectedBalance = generatorBalance.add(fee);
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
// Amount should be in recipient's balance
expectedBalance = amount;
actualBalance = accountRepository.getBalance(recipient.getAddress(), Asset.QORA).getBalance();
assertTrue("Recipient's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
}
}

View File

@ -11,7 +11,6 @@ import com.google.common.primitives.Longs;
import data.PaymentData;
import transform.TransformationException;
import utils.Base58;
import utils.Serialization;
public class PaymentTransformer extends Transformer {
@ -27,7 +26,7 @@ public class PaymentTransformer extends Transformer {
if (byteBuffer.remaining() < TOTAL_LENGTH)
throw new TransformationException("Byte data too short for payment information");
String recipient = Serialization.deserializeRecipient(byteBuffer);
String recipient = Serialization.deserializeAddress(byteBuffer);
long assetId = byteBuffer.getLong();
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
@ -42,7 +41,7 @@ public class PaymentTransformer extends Transformer {
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bytes.write(Base58.decode(paymentData.getRecipient()));
Serialization.serializeAddress(bytes, paymentData.getRecipient());
bytes.write(Longs.toByteArray(paymentData.getAssetId()));
Serialization.serializeBigDecimal(bytes, paymentData.getAmount());

View File

@ -0,0 +1,118 @@
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.base.Utf8;
import com.google.common.hash.HashCode;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import data.transaction.BuyNameTransactionData;
import data.transaction.TransactionData;
import qora.account.PublicKeyAccount;
import qora.naming.Name;
import transform.TransformationException;
import utils.Serialization;
public class BuyNameTransactionTransformer extends TransactionTransformer {
// Property lengths
private static final int BUYER_LENGTH = PUBLIC_KEY_LENGTH;
private static final int NAME_SIZE_LENGTH = INT_LENGTH;
private static final int AMOUNT_LENGTH = BIG_DECIMAL_LENGTH;
private static final int SELLER_LENGTH = ADDRESS_LENGTH;
private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + BUYER_LENGTH + NAME_SIZE_LENGTH + AMOUNT_LENGTH + SELLER_LENGTH;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH)
throw new TransformationException("Byte data too short for BuyNameTransaction");
long timestamp = byteBuffer.getLong();
byte[] reference = new byte[REFERENCE_LENGTH];
byteBuffer.get(reference);
byte[] buyerPublicKey = Serialization.deserializePublicKey(byteBuffer);
String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE);
// Still need to make sure there are enough bytes left for remaining fields
if (byteBuffer.remaining() < AMOUNT_LENGTH + SELLER_LENGTH + FEE_LENGTH + SIGNATURE_LENGTH)
throw new TransformationException("Byte data too short for BuyNameTransaction");
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
String seller = Serialization.deserializeAddress(byteBuffer);
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
byte[] signature = new byte[SIGNATURE_LENGTH];
byteBuffer.get(signature);
return new BuyNameTransactionData(buyerPublicKey, name, amount, seller, fee, timestamp, reference, signature);
}
public static int getDataLength(TransactionData transactionData) throws TransformationException {
BuyNameTransactionData buyNameTransactionData = (BuyNameTransactionData) transactionData;
int dataLength = TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + Utf8.encodedLength(buyNameTransactionData.getName());
return dataLength;
}
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
try {
BuyNameTransactionData buyNameTransactionData = (BuyNameTransactionData) transactionData;
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bytes.write(Ints.toByteArray(buyNameTransactionData.getType().value));
bytes.write(Longs.toByteArray(buyNameTransactionData.getTimestamp()));
bytes.write(buyNameTransactionData.getReference());
bytes.write(buyNameTransactionData.getBuyerPublicKey());
Serialization.serializeSizedString(bytes, buyNameTransactionData.getName());
Serialization.serializeBigDecimal(bytes, buyNameTransactionData.getAmount());
Serialization.serializeAddress(bytes, buyNameTransactionData.getSeller());
Serialization.serializeBigDecimal(bytes, buyNameTransactionData.getFee());
if (buyNameTransactionData.getSignature() != null)
bytes.write(buyNameTransactionData.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 {
BuyNameTransactionData buyNameTransactionData = (BuyNameTransactionData) transactionData;
byte[] buyerPublicKey = buyNameTransactionData.getBuyerPublicKey();
json.put("buyer", PublicKeyAccount.getAddress(buyerPublicKey));
json.put("buyerPublicKey", HashCode.fromBytes(buyerPublicKey).toString());
json.put("name", buyNameTransactionData.getName());
json.put("amount", buyNameTransactionData.getAmount().toPlainString());
json.put("seller", buyNameTransactionData.getSeller());
} catch (ClassCastException e) {
throw new TransformationException(e);
}
return json;
}
}

View File

@ -21,7 +21,7 @@ import utils.Serialization;
public class CancelOrderTransactionTransformer extends TransactionTransformer {
// Property lengths
private static final int CREATOR_LENGTH = LONG_LENGTH;
private static final int CREATOR_LENGTH = PUBLIC_KEY_LENGTH;
private static final int ORDER_ID_LENGTH = SIGNATURE_LENGTH;
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + CREATOR_LENGTH + ORDER_ID_LENGTH;

View File

@ -20,10 +20,11 @@ import utils.Serialization;
public class CreateOrderTransactionTransformer extends TransactionTransformer {
// Property lengths
private static final int CREATOR_LENGTH = PUBLIC_KEY_LENGTH;
private static final int ASSET_ID_LENGTH = LONG_LENGTH;
private static final int AMOUNT_LENGTH = 12; // Not standard BIG_DECIMAL_LENGTH
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + (ASSET_ID_LENGTH + AMOUNT_LENGTH) * 2;
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + CREATOR_LENGTH + (ASSET_ID_LENGTH + AMOUNT_LENGTH) * 2;
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_LENGTH)

View File

@ -21,7 +21,6 @@ import data.voting.PollOptionData;
import qora.account.PublicKeyAccount;
import qora.voting.Poll;
import transform.TransformationException;
import utils.Base58;
import utils.Serialization;
public class CreatePollTransactionTransformer extends TransactionTransformer {
@ -47,7 +46,7 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
byte[] creatorPublicKey = Serialization.deserializePublicKey(byteBuffer);
String owner = Serialization.deserializeRecipient(byteBuffer);
String owner = Serialization.deserializeAddress(byteBuffer);
String pollName = Serialization.deserializeSizedString(byteBuffer, Poll.MAX_NAME_SIZE);
String description = Serialization.deserializeSizedString(byteBuffer, Poll.MAX_DESCRIPTION_SIZE);
@ -103,7 +102,7 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
bytes.write(createPollTransactionData.getReference());
bytes.write(createPollTransactionData.getCreatorPublicKey());
bytes.write(Base58.decode(createPollTransactionData.getOwner()));
Serialization.serializeAddress(bytes, createPollTransactionData.getOwner());
Serialization.serializeSizedString(bytes, createPollTransactionData.getPollName());
Serialization.serializeSizedString(bytes, createPollTransactionData.getDescription());

View File

@ -13,7 +13,6 @@ import com.google.common.primitives.Longs;
import data.transaction.TransactionData;
import data.transaction.GenesisTransactionData;
import transform.TransformationException;
import utils.Base58;
import utils.Serialization;
public class GenesisTransactionTransformer extends TransactionTransformer {
@ -30,7 +29,7 @@ public class GenesisTransactionTransformer extends TransactionTransformer {
throw new TransformationException("Byte data too short for GenesisTransaction");
long timestamp = byteBuffer.getLong();
String recipient = Serialization.deserializeRecipient(byteBuffer);
String recipient = Serialization.deserializeAddress(byteBuffer);
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
return new GenesisTransactionData(recipient, amount, timestamp);
@ -49,7 +48,7 @@ public class GenesisTransactionTransformer extends TransactionTransformer {
bytes.write(Ints.toByteArray(genesisTransactionData.getType().value));
bytes.write(Longs.toByteArray(genesisTransactionData.getTimestamp()));
bytes.write(Base58.decode(genesisTransactionData.getRecipient()));
Serialization.serializeAddress(bytes, genesisTransactionData.getRecipient());
Serialization.serializeBigDecimal(bytes, genesisTransactionData.getAmount());
return bytes.toByteArray();

View File

@ -17,7 +17,6 @@ import qora.account.PublicKeyAccount;
import qora.transaction.IssueAssetTransaction;
import data.transaction.IssueAssetTransactionData;
import transform.TransformationException;
import utils.Base58;
import utils.Serialization;
public class IssueAssetTransactionTransformer extends TransactionTransformer {
@ -43,7 +42,7 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
byteBuffer.get(reference);
byte[] issuerPublicKey = Serialization.deserializePublicKey(byteBuffer);
String owner = Serialization.deserializeRecipient(byteBuffer);
String owner = Serialization.deserializeAddress(byteBuffer);
String assetName = Serialization.deserializeSizedString(byteBuffer, IssueAssetTransaction.MAX_NAME_SIZE);
String description = Serialization.deserializeSizedString(byteBuffer, IssueAssetTransaction.MAX_DESCRIPTION_SIZE);
@ -81,7 +80,7 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
bytes.write(issueAssetTransactionData.getReference());
bytes.write(issueAssetTransactionData.getIssuerPublicKey());
bytes.write(Base58.decode(issueAssetTransactionData.getOwner()));
Serialization.serializeAddress(bytes, issueAssetTransactionData.getOwner());
Serialization.serializeSizedString(bytes, issueAssetTransactionData.getAssetName());
Serialization.serializeSizedString(bytes, issueAssetTransactionData.getDescription());
bytes.write(Longs.toByteArray(issueAssetTransactionData.getQuantity()));

View File

@ -18,7 +18,6 @@ import qora.assets.Asset;
import qora.transaction.MessageTransaction;
import data.transaction.MessageTransactionData;
import transform.TransformationException;
import utils.Base58;
import utils.Serialization;
public class MessageTransactionTransformer extends TransactionTransformer {
@ -54,7 +53,7 @@ public class MessageTransactionTransformer extends TransactionTransformer {
byteBuffer.get(reference);
byte[] senderPublicKey = Serialization.deserializePublicKey(byteBuffer);
String recipient = Serialization.deserializeRecipient(byteBuffer);
String recipient = Serialization.deserializeAddress(byteBuffer);
long assetId;
if (version == 1)
@ -84,7 +83,7 @@ public class MessageTransactionTransformer extends TransactionTransformer {
byte[] signature = new byte[SIGNATURE_LENGTH];
byteBuffer.get(signature);
return new MessageTransactionData(version, senderPublicKey, recipient, assetId, amount, fee, data, isText, isEncrypted, timestamp, reference,
return new MessageTransactionData(version, senderPublicKey, recipient, assetId, amount, data, isText, isEncrypted, fee, timestamp, reference,
signature);
}
@ -108,7 +107,7 @@ public class MessageTransactionTransformer extends TransactionTransformer {
bytes.write(messageTransactionData.getReference());
bytes.write(messageTransactionData.getSenderPublicKey());
bytes.write(Base58.decode(messageTransactionData.getRecipient()));
Serialization.serializeAddress(bytes, messageTransactionData.getRecipient());
if (messageTransactionData.getVersion() != 1)
bytes.write(Longs.toByteArray(messageTransactionData.getAssetId()));

View File

@ -32,7 +32,7 @@ public class MultiPaymentTransactionTransformer extends TransactionTransformer {
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
if (byteBuffer.remaining() < TYPELESS_LENGTH)
throw new TransformationException("Byte data too short for PaymentTransaction");
throw new TransformationException("Byte data too short for MultiPaymentTransaction");
long timestamp = byteBuffer.getLong();
@ -45,7 +45,7 @@ public class MultiPaymentTransactionTransformer extends TransactionTransformer {
// 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");
throw new TransformationException("Byte data too short for MultiPaymentTransaction");
List<PaymentData> payments = new ArrayList<PaymentData>();
for (int i = 0; i < paymentsCount; ++i)
@ -81,7 +81,7 @@ public class MultiPaymentTransactionTransformer extends TransactionTransformer {
bytes.write(Ints.toByteArray(payments.size()));
for (PaymentData paymentData : payments)
PaymentTransformer.toBytes(paymentData);
bytes.write(PaymentTransformer.toBytes(paymentData));
Serialization.serializeBigDecimal(bytes, multiPaymentTransactionData.getFee());

View File

@ -15,7 +15,6 @@ import data.transaction.TransactionData;
import qora.account.PublicKeyAccount;
import data.transaction.PaymentTransactionData;
import transform.TransformationException;
import utils.Base58;
import utils.Serialization;
public class PaymentTransactionTransformer extends TransactionTransformer {
@ -37,7 +36,7 @@ public class PaymentTransactionTransformer extends TransactionTransformer {
byteBuffer.get(reference);
byte[] senderPublicKey = Serialization.deserializePublicKey(byteBuffer);
String recipient = Serialization.deserializeRecipient(byteBuffer);
String recipient = Serialization.deserializeAddress(byteBuffer);
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
@ -63,7 +62,7 @@ public class PaymentTransactionTransformer extends TransactionTransformer {
bytes.write(paymentTransactionData.getReference());
bytes.write(paymentTransactionData.getSenderPublicKey());
bytes.write(Base58.decode(paymentTransactionData.getRecipient()));
Serialization.serializeAddress(bytes, paymentTransactionData.getRecipient());
Serialization.serializeBigDecimal(bytes, paymentTransactionData.getAmount());
Serialization.serializeBigDecimal(bytes, paymentTransactionData.getFee());

View File

@ -17,7 +17,6 @@ import data.transaction.TransactionData;
import qora.account.PublicKeyAccount;
import qora.naming.Name;
import transform.TransformationException;
import utils.Base58;
import utils.Serialization;
public class RegisterNameTransactionTransformer extends TransactionTransformer {
@ -41,7 +40,7 @@ public class RegisterNameTransactionTransformer extends TransactionTransformer {
byte[] registrantPublicKey = Serialization.deserializePublicKey(byteBuffer);
String owner = Serialization.deserializeRecipient(byteBuffer);
String owner = Serialization.deserializeAddress(byteBuffer);
String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE);
String data = Serialization.deserializeSizedString(byteBuffer, Name.MAX_DATA_SIZE);
@ -78,7 +77,7 @@ public class RegisterNameTransactionTransformer extends TransactionTransformer {
bytes.write(registerNameTransactionData.getReference());
bytes.write(registerNameTransactionData.getRegistrantPublicKey());
bytes.write(Base58.decode(registerNameTransactionData.getOwner()));
Serialization.serializeAddress(bytes, registerNameTransactionData.getOwner());
Serialization.serializeSizedString(bytes, registerNameTransactionData.getName());
Serialization.serializeSizedString(bytes, registerNameTransactionData.getData());

View File

@ -49,6 +49,9 @@ public class TransactionTransformer extends Transformer {
case CANCEL_SELL_NAME:
return CancelSellNameTransactionTransformer.fromByteBuffer(byteBuffer);
case BUY_NAME:
return BuyNameTransactionTransformer.fromByteBuffer(byteBuffer);
case CREATE_POLL:
return CreatePollTransactionTransformer.fromByteBuffer(byteBuffer);
@ -98,6 +101,9 @@ public class TransactionTransformer extends Transformer {
case CANCEL_SELL_NAME:
return CancelSellNameTransactionTransformer.getDataLength(transactionData);
case BUY_NAME:
return BuyNameTransactionTransformer.getDataLength(transactionData);
case CREATE_POLL:
return CreatePollTransactionTransformer.getDataLength(transactionData);
@ -147,6 +153,9 @@ public class TransactionTransformer extends Transformer {
case CANCEL_SELL_NAME:
return CancelSellNameTransactionTransformer.toBytes(transactionData);
case BUY_NAME:
return BuyNameTransactionTransformer.toBytes(transactionData);
case CREATE_POLL:
return CreatePollTransactionTransformer.toBytes(transactionData);
@ -196,6 +205,9 @@ public class TransactionTransformer extends Transformer {
case CANCEL_SELL_NAME:
return CancelSellNameTransactionTransformer.toJSON(transactionData);
case BUY_NAME:
return BuyNameTransactionTransformer.toJSON(transactionData);
case CREATE_POLL:
return CreatePollTransactionTransformer.toJSON(transactionData);

View File

@ -15,7 +15,6 @@ import data.transaction.TransactionData;
import data.transaction.TransferAssetTransactionData;
import qora.account.PublicKeyAccount;
import transform.TransformationException;
import utils.Base58;
import utils.Serialization;
public class TransferAssetTransactionTransformer extends TransactionTransformer {
@ -38,7 +37,7 @@ public class TransferAssetTransactionTransformer extends TransactionTransformer
byteBuffer.get(reference);
byte[] senderPublicKey = Serialization.deserializePublicKey(byteBuffer);
String recipient = Serialization.deserializeRecipient(byteBuffer);
String recipient = Serialization.deserializeAddress(byteBuffer);
long assetId = byteBuffer.getLong();
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer, AMOUNT_LENGTH);
@ -65,7 +64,7 @@ public class TransferAssetTransactionTransformer extends TransactionTransformer
bytes.write(transferAssetTransactionData.getReference());
bytes.write(transferAssetTransactionData.getSenderPublicKey());
bytes.write(Base58.decode(transferAssetTransactionData.getRecipient()));
Serialization.serializeAddress(bytes, transferAssetTransactionData.getRecipient());
bytes.write(Longs.toByteArray(transferAssetTransactionData.getAssetId()));
Serialization.serializeBigDecimal(bytes, transferAssetTransactionData.getAmount(), AMOUNT_LENGTH);

View File

@ -17,7 +17,6 @@ import data.transaction.TransactionData;
import qora.account.PublicKeyAccount;
import qora.naming.Name;
import transform.TransformationException;
import utils.Base58;
import utils.Serialization;
public class UpdateNameTransactionTransformer extends TransactionTransformer {
@ -41,7 +40,7 @@ public class UpdateNameTransactionTransformer extends TransactionTransformer {
byte[] ownerPublicKey = Serialization.deserializePublicKey(byteBuffer);
String newOwner = Serialization.deserializeRecipient(byteBuffer);
String newOwner = Serialization.deserializeAddress(byteBuffer);
String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE);
String newData = Serialization.deserializeSizedString(byteBuffer, Name.MAX_DATA_SIZE);
@ -55,7 +54,7 @@ public class UpdateNameTransactionTransformer extends TransactionTransformer {
byte[] signature = new byte[SIGNATURE_LENGTH];
byteBuffer.get(signature);
return new UpdateNameTransactionData(ownerPublicKey, newOwner, name, newData, null, fee, timestamp, reference, signature);
return new UpdateNameTransactionData(ownerPublicKey, newOwner, name, newData, fee, timestamp, reference, signature);
}
public static int getDataLength(TransactionData transactionData) throws TransformationException {
@ -78,7 +77,7 @@ public class UpdateNameTransactionTransformer extends TransactionTransformer {
bytes.write(updateNameTransactionData.getReference());
bytes.write(updateNameTransactionData.getOwnerPublicKey());
bytes.write(Base58.decode(updateNameTransactionData.getNewOwner()));
Serialization.serializeAddress(bytes, updateNameTransactionData.getNewOwner());
Serialization.serializeSizedString(bytes, updateNameTransactionData.getName());
Serialization.serializeSizedString(bytes, updateNameTransactionData.getNewData());

View File

@ -50,7 +50,11 @@ public class Serialization {
return Serialization.deserializeBigDecimal(byteBuffer, 8);
}
public static String deserializeRecipient(ByteBuffer byteBuffer) {
public static void serializeAddress(ByteArrayOutputStream bytes, String address) throws IOException {
bytes.write(Base58.decode(address));
}
public static String deserializeAddress(ByteBuffer byteBuffer) {
byte[] bytes = new byte[Transformer.ADDRESS_LENGTH];
byteBuffer.get(bytes);
return Base58.encode(bytes);