forked from Qortal/qortal
Added SellNameTransactions + tests
This commit is contained in:
parent
c79bec90bc
commit
0fc17d76ae
42
src/data/transaction/SellNameTransactionData.java
Normal file
42
src/data/transaction/SellNameTransactionData.java
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package data.transaction;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import qora.transaction.Transaction.TransactionType;
|
||||||
|
|
||||||
|
public class SellNameTransactionData extends TransactionData {
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
private byte[] ownerPublicKey;
|
||||||
|
private String name;
|
||||||
|
private BigDecimal amount;
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
public SellNameTransactionData(byte[] ownerPublicKey, String name, BigDecimal amount, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) {
|
||||||
|
super(TransactionType.SELL_NAME, fee, ownerPublicKey, timestamp, reference, signature);
|
||||||
|
|
||||||
|
this.ownerPublicKey = ownerPublicKey;
|
||||||
|
this.name = name;
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SellNameTransactionData(byte[] ownerPublicKey, String name, BigDecimal amount, BigDecimal fee, long timestamp, byte[] reference) {
|
||||||
|
this(ownerPublicKey, name, amount, fee, timestamp, reference, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters / setters
|
||||||
|
|
||||||
|
public byte[] getOwnerPublicKey() {
|
||||||
|
return this.ownerPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getAmount() {
|
||||||
|
return this.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,6 +2,7 @@ package qora.naming;
|
|||||||
|
|
||||||
import data.naming.NameData;
|
import data.naming.NameData;
|
||||||
import data.transaction.RegisterNameTransactionData;
|
import data.transaction.RegisterNameTransactionData;
|
||||||
|
import data.transaction.SellNameTransactionData;
|
||||||
import data.transaction.TransactionData;
|
import data.transaction.TransactionData;
|
||||||
import data.transaction.UpdateNameTransactionData;
|
import data.transaction.UpdateNameTransactionData;
|
||||||
import repository.DataException;
|
import repository.DataException;
|
||||||
@ -99,4 +100,22 @@ public class Name {
|
|||||||
this.repository.getNameRepository().save(this.nameData);
|
this.repository.getNameRepository().save(this.nameData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sell(SellNameTransactionData sellNameTransactionData) throws DataException {
|
||||||
|
// Mark as for-sale and set price
|
||||||
|
this.nameData.setIsForSale(true);
|
||||||
|
this.nameData.setSalePrice(sellNameTransactionData.getAmount());
|
||||||
|
|
||||||
|
// Save sale info into repository
|
||||||
|
this.repository.getNameRepository().save(this.nameData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unsell(SellNameTransactionData sellNameTransactionData) throws DataException {
|
||||||
|
// Mark not for-sale and unset price
|
||||||
|
this.nameData.setIsForSale(false);
|
||||||
|
this.nameData.setSalePrice(null);
|
||||||
|
|
||||||
|
// Save no-sale info into repository
|
||||||
|
this.repository.getNameRepository().save(this.nameData);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
151
src/qora/transaction/SellNameTransaction.java
Normal file
151
src/qora/transaction/SellNameTransaction.java
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package qora.transaction;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import data.naming.NameData;
|
||||||
|
import data.transaction.SellNameTransactionData;
|
||||||
|
import data.transaction.TransactionData;
|
||||||
|
import qora.account.Account;
|
||||||
|
import qora.account.PublicKeyAccount;
|
||||||
|
import qora.assets.Asset;
|
||||||
|
import qora.block.BlockChain;
|
||||||
|
import qora.naming.Name;
|
||||||
|
import repository.DataException;
|
||||||
|
import repository.Repository;
|
||||||
|
|
||||||
|
public class SellNameTransaction extends Transaction {
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
private SellNameTransactionData sellNameTransactionData;
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
public SellNameTransaction(Repository repository, TransactionData transactionData) {
|
||||||
|
super(repository, transactionData);
|
||||||
|
|
||||||
|
this.sellNameTransactionData = (SellNameTransactionData) this.transactionData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// More information
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Account> getRecipientAccounts() {
|
||||||
|
return new ArrayList<Account>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInvolved(Account account) throws DataException {
|
||||||
|
String address = account.getAddress();
|
||||||
|
|
||||||
|
if (address.equals(this.getOwner().getAddress()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getAmount(Account account) throws DataException {
|
||||||
|
String address = account.getAddress();
|
||||||
|
BigDecimal amount = BigDecimal.ZERO.setScale(8);
|
||||||
|
|
||||||
|
if (address.equals(this.getOwner().getAddress()))
|
||||||
|
amount = amount.subtract(this.transactionData.getFee());
|
||||||
|
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
|
||||||
|
public Account getOwner() throws DataException {
|
||||||
|
return new PublicKeyAccount(this.repository, this.sellNameTransactionData.getOwnerPublicKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processing
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValidationResult isValid() throws DataException {
|
||||||
|
// Check name size bounds
|
||||||
|
if (sellNameTransactionData.getName().length() < 1 || sellNameTransactionData.getName().length() > Name.MAX_NAME_SIZE)
|
||||||
|
return ValidationResult.INVALID_NAME_LENGTH;
|
||||||
|
|
||||||
|
// Check name is lowercase
|
||||||
|
if (!sellNameTransactionData.getName().equals(sellNameTransactionData.getName().toLowerCase()))
|
||||||
|
return ValidationResult.NAME_NOT_LOWER_CASE;
|
||||||
|
|
||||||
|
NameData nameData = this.repository.getNameRepository().fromName(sellNameTransactionData.getName());
|
||||||
|
|
||||||
|
// Check name exists
|
||||||
|
if (nameData == null)
|
||||||
|
return ValidationResult.NAME_DOES_NOT_EXIST;
|
||||||
|
|
||||||
|
// Check name isn't currently for sale
|
||||||
|
if (nameData.getIsForSale())
|
||||||
|
return ValidationResult.NAME_ALREADY_FOR_SALE;
|
||||||
|
|
||||||
|
// Check transaction's public key matches name's current owner
|
||||||
|
Account owner = new PublicKeyAccount(this.repository, sellNameTransactionData.getOwnerPublicKey());
|
||||||
|
if (!owner.getAddress().equals(nameData.getOwner()))
|
||||||
|
return ValidationResult.INVALID_NAME_OWNER;
|
||||||
|
|
||||||
|
// Check amount is positive
|
||||||
|
if (sellNameTransactionData.getAmount().compareTo(BigDecimal.ZERO) <= 0)
|
||||||
|
return ValidationResult.NEGATIVE_AMOUNT;
|
||||||
|
|
||||||
|
// Check amount within bounds
|
||||||
|
if (sellNameTransactionData.getAmount().compareTo(BlockChain.MAX_BALANCE) > 0)
|
||||||
|
return ValidationResult.INVALID_AMOUNT;
|
||||||
|
|
||||||
|
// Check fee is positive
|
||||||
|
if (sellNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
|
||||||
|
return ValidationResult.NEGATIVE_FEE;
|
||||||
|
|
||||||
|
// Check reference is correct
|
||||||
|
if (!Arrays.equals(owner.getLastReference(), sellNameTransactionData.getReference()))
|
||||||
|
return ValidationResult.INVALID_REFERENCE;
|
||||||
|
|
||||||
|
// Check issuer has enough funds
|
||||||
|
if (owner.getConfirmedBalance(Asset.QORA).compareTo(sellNameTransactionData.getFee()) == -1)
|
||||||
|
return ValidationResult.NO_BALANCE;
|
||||||
|
|
||||||
|
return ValidationResult.OK;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() throws DataException {
|
||||||
|
// Update Name
|
||||||
|
Name name = new Name(this.repository, sellNameTransactionData.getName());
|
||||||
|
name.sell(sellNameTransactionData);
|
||||||
|
|
||||||
|
// Save this transaction, now with updated "name reference" to previous transaction that updated name
|
||||||
|
this.repository.getTransactionRepository().save(sellNameTransactionData);
|
||||||
|
|
||||||
|
// Update owner's balance
|
||||||
|
Account owner = new PublicKeyAccount(this.repository, sellNameTransactionData.getOwnerPublicKey());
|
||||||
|
owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).subtract(sellNameTransactionData.getFee()));
|
||||||
|
|
||||||
|
// Update owner's reference
|
||||||
|
owner.setLastReference(sellNameTransactionData.getSignature());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void orphan() throws DataException {
|
||||||
|
// Revert name
|
||||||
|
Name name = new Name(this.repository, sellNameTransactionData.getName());
|
||||||
|
name.unsell(sellNameTransactionData);
|
||||||
|
|
||||||
|
// Delete this transaction itself
|
||||||
|
this.repository.getTransactionRepository().delete(sellNameTransactionData);
|
||||||
|
|
||||||
|
// Update owner's balance
|
||||||
|
Account owner = new PublicKeyAccount(this.repository, sellNameTransactionData.getOwnerPublicKey());
|
||||||
|
owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).add(sellNameTransactionData.getFee()));
|
||||||
|
|
||||||
|
// Update owner's reference
|
||||||
|
owner.setLastReference(sellNameTransactionData.getReference());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -111,6 +111,9 @@ public abstract class Transaction {
|
|||||||
case UPDATE_NAME:
|
case UPDATE_NAME:
|
||||||
return new UpdateNameTransaction(repository, transactionData);
|
return new UpdateNameTransaction(repository, transactionData);
|
||||||
|
|
||||||
|
case SELL_NAME:
|
||||||
|
return new SellNameTransaction(repository, transactionData);
|
||||||
|
|
||||||
case CREATE_POLL:
|
case CREATE_POLL:
|
||||||
return new CreatePollTransaction(repository, transactionData);
|
return new CreatePollTransaction(repository, transactionData);
|
||||||
|
|
||||||
@ -136,7 +139,7 @@ public abstract class Transaction {
|
|||||||
return new MessageTransaction(repository, transactionData);
|
return new MessageTransaction(repository, transactionData);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
throw new IllegalStateException("Unsupported transaction type [" + transactionData.getType().value + "] during fetch from repository");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
package repository.hsqldb.transaction;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import data.transaction.SellNameTransactionData;
|
||||||
|
import data.transaction.TransactionData;
|
||||||
|
import repository.DataException;
|
||||||
|
import repository.hsqldb.HSQLDBRepository;
|
||||||
|
import repository.hsqldb.HSQLDBSaver;
|
||||||
|
|
||||||
|
public class HSQLDBSellNameTransactionRepository extends HSQLDBTransactionRepository {
|
||||||
|
|
||||||
|
public HSQLDBSellNameTransactionRepository(HSQLDBRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionData fromBase(byte[] signature, byte[] reference, byte[] ownerPublicKey, long timestamp, BigDecimal fee) throws DataException {
|
||||||
|
try {
|
||||||
|
ResultSet rs = this.repository.checkedExecute("SELECT name, amount FROM SellNameTransactions WHERE signature = ?", signature);
|
||||||
|
if (rs == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
String name = rs.getString(1);
|
||||||
|
BigDecimal amount = rs.getBigDecimal(2);
|
||||||
|
|
||||||
|
return new SellNameTransactionData(ownerPublicKey, name, amount, fee, timestamp, reference, signature);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to fetch sell name transaction from repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(TransactionData transactionData) throws DataException {
|
||||||
|
SellNameTransactionData sellNameTransactionData = (SellNameTransactionData) transactionData;
|
||||||
|
|
||||||
|
HSQLDBSaver saveHelper = new HSQLDBSaver("SellNameTransactions");
|
||||||
|
|
||||||
|
saveHelper.bind("signature", sellNameTransactionData.getSignature()).bind("owner", sellNameTransactionData.getOwnerPublicKey())
|
||||||
|
.bind("name", sellNameTransactionData.getName()).bind("amount", sellNameTransactionData.getAmount());
|
||||||
|
|
||||||
|
try {
|
||||||
|
saveHelper.execute(this.repository);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to save sell name transaction into repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -23,6 +23,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
private HSQLDBPaymentTransactionRepository paymentTransactionRepository;
|
private HSQLDBPaymentTransactionRepository paymentTransactionRepository;
|
||||||
private HSQLDBRegisterNameTransactionRepository registerNameTransactionRepository;
|
private HSQLDBRegisterNameTransactionRepository registerNameTransactionRepository;
|
||||||
private HSQLDBUpdateNameTransactionRepository updateNameTransactionRepository;
|
private HSQLDBUpdateNameTransactionRepository updateNameTransactionRepository;
|
||||||
|
private HSQLDBSellNameTransactionRepository sellNameTransactionRepository;
|
||||||
private HSQLDBCreatePollTransactionRepository createPollTransactionRepository;
|
private HSQLDBCreatePollTransactionRepository createPollTransactionRepository;
|
||||||
private HSQLDBVoteOnPollTransactionRepository voteOnPollTransactionRepository;
|
private HSQLDBVoteOnPollTransactionRepository voteOnPollTransactionRepository;
|
||||||
private HSQLDBIssueAssetTransactionRepository issueAssetTransactionRepository;
|
private HSQLDBIssueAssetTransactionRepository issueAssetTransactionRepository;
|
||||||
@ -38,6 +39,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
this.paymentTransactionRepository = new HSQLDBPaymentTransactionRepository(repository);
|
this.paymentTransactionRepository = new HSQLDBPaymentTransactionRepository(repository);
|
||||||
this.registerNameTransactionRepository = new HSQLDBRegisterNameTransactionRepository(repository);
|
this.registerNameTransactionRepository = new HSQLDBRegisterNameTransactionRepository(repository);
|
||||||
this.updateNameTransactionRepository = new HSQLDBUpdateNameTransactionRepository(repository);
|
this.updateNameTransactionRepository = new HSQLDBUpdateNameTransactionRepository(repository);
|
||||||
|
this.sellNameTransactionRepository = new HSQLDBSellNameTransactionRepository(repository);
|
||||||
this.createPollTransactionRepository = new HSQLDBCreatePollTransactionRepository(repository);
|
this.createPollTransactionRepository = new HSQLDBCreatePollTransactionRepository(repository);
|
||||||
this.voteOnPollTransactionRepository = new HSQLDBVoteOnPollTransactionRepository(repository);
|
this.voteOnPollTransactionRepository = new HSQLDBVoteOnPollTransactionRepository(repository);
|
||||||
this.issueAssetTransactionRepository = new HSQLDBIssueAssetTransactionRepository(repository);
|
this.issueAssetTransactionRepository = new HSQLDBIssueAssetTransactionRepository(repository);
|
||||||
@ -102,6 +104,9 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
case UPDATE_NAME:
|
case UPDATE_NAME:
|
||||||
return this.updateNameTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
return this.updateNameTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
||||||
|
|
||||||
|
case SELL_NAME:
|
||||||
|
return this.sellNameTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
||||||
|
|
||||||
case CREATE_POLL:
|
case CREATE_POLL:
|
||||||
return this.createPollTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
return this.createPollTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
||||||
|
|
||||||
@ -127,7 +132,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
return this.messageTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
return this.messageTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
throw new DataException("Unsupported transaction type [" + type.value + "] during fetch from HSQLDB repository");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,6 +243,10 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
this.updateNameTransactionRepository.save(transactionData);
|
this.updateNameTransactionRepository.save(transactionData);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SELL_NAME:
|
||||||
|
this.sellNameTransactionRepository.save(transactionData);
|
||||||
|
break;
|
||||||
|
|
||||||
case CREATE_POLL:
|
case CREATE_POLL:
|
||||||
this.createPollTransactionRepository.save(transactionData);
|
this.createPollTransactionRepository.save(transactionData);
|
||||||
break;
|
break;
|
||||||
@ -271,7 +280,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new DataException("Unsupported transaction type during save into repository");
|
throw new DataException("Unsupported transaction type [" + transactionData.getType().value + "] during save into HSQLDB repository");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,11 +59,14 @@ public class LoadTests extends Common {
|
|||||||
if (transactionData == null)
|
if (transactionData == null)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
if (transactionData.getType() != TransactionType.PAYMENT)
|
||||||
|
break;
|
||||||
|
|
||||||
PaymentTransactionData paymentTransactionData = (PaymentTransactionData) transactionData;
|
PaymentTransactionData paymentTransactionData = (PaymentTransactionData) transactionData;
|
||||||
System.out.println(PublicKeyAccount.getAddress(paymentTransactionData.getSenderPublicKey()) + " sent " + paymentTransactionData.getAmount()
|
System.out.println(PublicKeyAccount.getAddress(paymentTransactionData.getSenderPublicKey()) + " sent " + paymentTransactionData.getAmount()
|
||||||
+ " QORA to " + paymentTransactionData.getRecipient());
|
+ " QORA to " + paymentTransactionData.getRecipient());
|
||||||
|
|
||||||
signature = paymentTransactionData.getReference();
|
signature = transactionData.getReference();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,11 @@ public class SerializationTests extends Common {
|
|||||||
testSpecificBlockTransactions(673, TransactionType.UPDATE_NAME);
|
testSpecificBlockTransactions(673, TransactionType.UPDATE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSellNameSerialization() throws TransformationException, DataException {
|
||||||
|
testSpecificBlockTransactions(673, TransactionType.SELL_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreatePollSerialization() throws TransformationException, DataException {
|
public void testCreatePollSerialization() throws TransformationException, DataException {
|
||||||
// Block 10537 has only create poll transactions
|
// Block 10537 has only create poll transactions
|
||||||
|
@ -19,6 +19,7 @@ import data.naming.NameData;
|
|||||||
import data.transaction.CreatePollTransactionData;
|
import data.transaction.CreatePollTransactionData;
|
||||||
import data.transaction.PaymentTransactionData;
|
import data.transaction.PaymentTransactionData;
|
||||||
import data.transaction.RegisterNameTransactionData;
|
import data.transaction.RegisterNameTransactionData;
|
||||||
|
import data.transaction.SellNameTransactionData;
|
||||||
import data.transaction.UpdateNameTransactionData;
|
import data.transaction.UpdateNameTransactionData;
|
||||||
import data.transaction.VoteOnPollTransactionData;
|
import data.transaction.VoteOnPollTransactionData;
|
||||||
import data.voting.PollData;
|
import data.voting.PollData;
|
||||||
@ -33,6 +34,7 @@ import qora.block.BlockChain;
|
|||||||
import qora.transaction.CreatePollTransaction;
|
import qora.transaction.CreatePollTransaction;
|
||||||
import qora.transaction.PaymentTransaction;
|
import qora.transaction.PaymentTransaction;
|
||||||
import qora.transaction.RegisterNameTransaction;
|
import qora.transaction.RegisterNameTransaction;
|
||||||
|
import qora.transaction.SellNameTransaction;
|
||||||
import qora.transaction.Transaction;
|
import qora.transaction.Transaction;
|
||||||
import qora.transaction.Transaction.ValidationResult;
|
import qora.transaction.Transaction.ValidationResult;
|
||||||
import qora.transaction.UpdateNameTransaction;
|
import qora.transaction.UpdateNameTransaction;
|
||||||
@ -208,7 +210,7 @@ public class TransactionTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateNamesTransaction() throws DataException {
|
public void testUpdateNameTransaction() throws DataException {
|
||||||
// Register name using another test
|
// Register name using another test
|
||||||
testRegisterNameTransaction();
|
testRegisterNameTransaction();
|
||||||
|
|
||||||
@ -256,6 +258,51 @@ public class TransactionTests {
|
|||||||
assertEquals(originalNameData.getData(), actualNameData.getData());
|
assertEquals(originalNameData.getData(), actualNameData.getData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSellNameTransaction() throws DataException {
|
||||||
|
// Register name using another test
|
||||||
|
testRegisterNameTransaction();
|
||||||
|
|
||||||
|
String name = "test name";
|
||||||
|
|
||||||
|
// Sale price
|
||||||
|
BigDecimal amount = BigDecimal.valueOf(1234L).setScale(8);
|
||||||
|
|
||||||
|
BigDecimal fee = BigDecimal.ONE;
|
||||||
|
long timestamp = parentBlockData.getTimestamp() + 2_000;
|
||||||
|
SellNameTransactionData sellNameTransactionData = new SellNameTransactionData(sender.getPublicKey(), name, amount, fee, timestamp, reference);
|
||||||
|
|
||||||
|
Transaction sellNameTransaction = new SellNameTransaction(repository, sellNameTransactionData);
|
||||||
|
sellNameTransaction.calcSignature(sender);
|
||||||
|
assertTrue(sellNameTransaction.isSignatureValid());
|
||||||
|
assertEquals(ValidationResult.OK, sellNameTransaction.isValid());
|
||||||
|
|
||||||
|
// Forge new block with transaction
|
||||||
|
Block block = new Block(repository, parentBlockData, generator, null, null);
|
||||||
|
block.addTransaction(sellNameTransactionData);
|
||||||
|
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);
|
||||||
|
assertTrue(actualNameData.getIsForSale());
|
||||||
|
assertEquals(amount, actualNameData.getSalePrice());
|
||||||
|
|
||||||
|
// Now orphan block
|
||||||
|
block.orphan();
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
// Check name has been reverted correctly
|
||||||
|
actualNameData = this.repository.getNameRepository().fromName(name);
|
||||||
|
assertFalse(actualNameData.getIsForSale());
|
||||||
|
assertNull(actualNameData.getSalePrice());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreatePollTransaction() throws DataException {
|
public void testCreatePollTransaction() throws DataException {
|
||||||
// This test requires GenesisBlock's timestamp is set to something after BlockChain.VOTING_RELEASE_TIMESTAMP
|
// This test requires GenesisBlock's timestamp is set to something after BlockChain.VOTING_RELEASE_TIMESTAMP
|
||||||
|
111
src/transform/transaction/SellNameTransactionTransformer.java
Normal file
111
src/transform/transaction/SellNameTransactionTransformer.java
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
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.SellNameTransactionData;
|
||||||
|
import data.transaction.TransactionData;
|
||||||
|
import qora.account.PublicKeyAccount;
|
||||||
|
import qora.naming.Name;
|
||||||
|
import transform.TransformationException;
|
||||||
|
import utils.Serialization;
|
||||||
|
|
||||||
|
public class SellNameTransactionTransformer extends TransactionTransformer {
|
||||||
|
|
||||||
|
// Property lengths
|
||||||
|
private static final int OWNER_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 TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + AMOUNT_LENGTH;
|
||||||
|
|
||||||
|
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
||||||
|
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH)
|
||||||
|
throw new TransformationException("Byte data too short for SellNameTransaction");
|
||||||
|
|
||||||
|
long timestamp = byteBuffer.getLong();
|
||||||
|
|
||||||
|
byte[] reference = new byte[REFERENCE_LENGTH];
|
||||||
|
byteBuffer.get(reference);
|
||||||
|
|
||||||
|
byte[] ownerPublicKey = 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 + FEE_LENGTH + SIGNATURE_LENGTH)
|
||||||
|
throw new TransformationException("Byte data too short for SellNameTransaction");
|
||||||
|
|
||||||
|
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
|
||||||
|
|
||||||
|
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
|
||||||
|
|
||||||
|
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||||
|
byteBuffer.get(signature);
|
||||||
|
|
||||||
|
return new SellNameTransactionData(ownerPublicKey, name, amount, fee, timestamp, reference, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
||||||
|
SellNameTransactionData sellNameTransactionData = (SellNameTransactionData) transactionData;
|
||||||
|
|
||||||
|
int dataLength = TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + sellNameTransactionData.getName().length();
|
||||||
|
|
||||||
|
return dataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
|
||||||
|
try {
|
||||||
|
SellNameTransactionData sellNameTransactionData = (SellNameTransactionData) transactionData;
|
||||||
|
|
||||||
|
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
bytes.write(Ints.toByteArray(sellNameTransactionData.getType().value));
|
||||||
|
bytes.write(Longs.toByteArray(sellNameTransactionData.getTimestamp()));
|
||||||
|
bytes.write(sellNameTransactionData.getReference());
|
||||||
|
|
||||||
|
bytes.write(sellNameTransactionData.getOwnerPublicKey());
|
||||||
|
Serialization.serializeSizedString(bytes, sellNameTransactionData.getName());
|
||||||
|
Serialization.serializeBigDecimal(bytes, sellNameTransactionData.getAmount());
|
||||||
|
|
||||||
|
Serialization.serializeBigDecimal(bytes, sellNameTransactionData.getFee());
|
||||||
|
|
||||||
|
if (sellNameTransactionData.getSignature() != null)
|
||||||
|
bytes.write(sellNameTransactionData.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 {
|
||||||
|
SellNameTransactionData sellNameTransactionData = (SellNameTransactionData) transactionData;
|
||||||
|
|
||||||
|
byte[] ownerPublicKey = sellNameTransactionData.getOwnerPublicKey();
|
||||||
|
|
||||||
|
json.put("owner", PublicKeyAccount.getAddress(ownerPublicKey));
|
||||||
|
json.put("ownerPublicKey", HashCode.fromBytes(ownerPublicKey).toString());
|
||||||
|
|
||||||
|
json.put("name", sellNameTransactionData.getName());
|
||||||
|
json.put("amount", sellNameTransactionData.getAmount().toPlainString());
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new TransformationException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -43,6 +43,9 @@ public class TransactionTransformer extends Transformer {
|
|||||||
case UPDATE_NAME:
|
case UPDATE_NAME:
|
||||||
return UpdateNameTransactionTransformer.fromByteBuffer(byteBuffer);
|
return UpdateNameTransactionTransformer.fromByteBuffer(byteBuffer);
|
||||||
|
|
||||||
|
case SELL_NAME:
|
||||||
|
return SellNameTransactionTransformer.fromByteBuffer(byteBuffer);
|
||||||
|
|
||||||
case CREATE_POLL:
|
case CREATE_POLL:
|
||||||
return CreatePollTransactionTransformer.fromByteBuffer(byteBuffer);
|
return CreatePollTransactionTransformer.fromByteBuffer(byteBuffer);
|
||||||
|
|
||||||
@ -68,7 +71,7 @@ public class TransactionTransformer extends Transformer {
|
|||||||
return MessageTransactionTransformer.fromByteBuffer(byteBuffer);
|
return MessageTransactionTransformer.fromByteBuffer(byteBuffer);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new TransformationException("Unsupported transaction type");
|
throw new TransformationException("Unsupported transaction type [" + type.value + "] during conversion from bytes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +89,9 @@ public class TransactionTransformer extends Transformer {
|
|||||||
case UPDATE_NAME:
|
case UPDATE_NAME:
|
||||||
return UpdateNameTransactionTransformer.getDataLength(transactionData);
|
return UpdateNameTransactionTransformer.getDataLength(transactionData);
|
||||||
|
|
||||||
|
case SELL_NAME:
|
||||||
|
return SellNameTransactionTransformer.getDataLength(transactionData);
|
||||||
|
|
||||||
case CREATE_POLL:
|
case CREATE_POLL:
|
||||||
return CreatePollTransactionTransformer.getDataLength(transactionData);
|
return CreatePollTransactionTransformer.getDataLength(transactionData);
|
||||||
|
|
||||||
@ -111,7 +117,7 @@ public class TransactionTransformer extends Transformer {
|
|||||||
return MessageTransactionTransformer.getDataLength(transactionData);
|
return MessageTransactionTransformer.getDataLength(transactionData);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new TransformationException("Unsupported transaction type");
|
throw new TransformationException("Unsupported transaction type [" + transactionData.getType().value + "] when requesting byte length");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,6 +135,9 @@ public class TransactionTransformer extends Transformer {
|
|||||||
case UPDATE_NAME:
|
case UPDATE_NAME:
|
||||||
return UpdateNameTransactionTransformer.toBytes(transactionData);
|
return UpdateNameTransactionTransformer.toBytes(transactionData);
|
||||||
|
|
||||||
|
case SELL_NAME:
|
||||||
|
return SellNameTransactionTransformer.toBytes(transactionData);
|
||||||
|
|
||||||
case CREATE_POLL:
|
case CREATE_POLL:
|
||||||
return CreatePollTransactionTransformer.toBytes(transactionData);
|
return CreatePollTransactionTransformer.toBytes(transactionData);
|
||||||
|
|
||||||
@ -154,7 +163,7 @@ public class TransactionTransformer extends Transformer {
|
|||||||
return MessageTransactionTransformer.toBytes(transactionData);
|
return MessageTransactionTransformer.toBytes(transactionData);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new TransformationException("Unsupported transaction type");
|
throw new TransformationException("Unsupported transaction type [" + transactionData.getType().value + "] during conversion to bytes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,6 +181,9 @@ public class TransactionTransformer extends Transformer {
|
|||||||
case UPDATE_NAME:
|
case UPDATE_NAME:
|
||||||
return UpdateNameTransactionTransformer.toJSON(transactionData);
|
return UpdateNameTransactionTransformer.toJSON(transactionData);
|
||||||
|
|
||||||
|
case SELL_NAME:
|
||||||
|
return SellNameTransactionTransformer.toJSON(transactionData);
|
||||||
|
|
||||||
case CREATE_POLL:
|
case CREATE_POLL:
|
||||||
return CreatePollTransactionTransformer.toJSON(transactionData);
|
return CreatePollTransactionTransformer.toJSON(transactionData);
|
||||||
|
|
||||||
@ -197,7 +209,7 @@ public class TransactionTransformer extends Transformer {
|
|||||||
return MessageTransactionTransformer.toJSON(transactionData);
|
return MessageTransactionTransformer.toJSON(transactionData);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new TransformationException("Unsupported transaction type");
|
throw new TransformationException("Unsupported transaction type [" + transactionData.getType().value + "] during conversion to JSON");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user