From 4de2caaa28f45761b93c755cfa5e52eadcac75bb Mon Sep 17 00:00:00 2001 From: catbref Date: Sun, 1 Jul 2018 09:34:57 +0100 Subject: [PATCH] Added RegisterNameTransactions Fixed CreatePollTransaction transformer bugs --- src/data/naming/NameData.java | 44 ++++++ .../RegisterNameTransactionData.java | 49 ++++++ src/qora/naming/Name.java | 54 +++++++ .../transaction/CreatePollTransaction.java | 4 +- .../transaction/RegisterNameTransaction.java | 147 ++++++++++++++++++ src/qora/transaction/Transaction.java | 13 +- src/repository/NameRepository.java | 15 ++ src/repository/Repository.java | 2 + .../hsqldb/HSQLDBNameRepository.java | 68 ++++++++ src/repository/hsqldb/HSQLDBRepository.java | 6 + ...QLDBRegisterNameTransactionRepository.java | 52 +++++++ .../HSQLDBTransactionRepository.java | 9 ++ src/test/SerializationTests.java | 37 ++++- .../CreatePollTransactionTransformer.java | 9 +- .../RegisterNameTransactionTransformer.java | 116 ++++++++++++++ .../transaction/TransactionTransformer.java | 12 ++ 16 files changed, 625 insertions(+), 12 deletions(-) create mode 100644 src/data/naming/NameData.java create mode 100644 src/data/transaction/RegisterNameTransactionData.java create mode 100644 src/qora/naming/Name.java create mode 100644 src/qora/transaction/RegisterNameTransaction.java create mode 100644 src/repository/NameRepository.java create mode 100644 src/repository/hsqldb/HSQLDBNameRepository.java create mode 100644 src/repository/hsqldb/transaction/HSQLDBRegisterNameTransactionRepository.java create mode 100644 src/transform/transaction/RegisterNameTransactionTransformer.java diff --git a/src/data/naming/NameData.java b/src/data/naming/NameData.java new file mode 100644 index 00000000..9de92465 --- /dev/null +++ b/src/data/naming/NameData.java @@ -0,0 +1,44 @@ +package data.naming; + +public class NameData { + + // Properties + private byte[] registrantPublicKey; + private String owner; + private String name; + private String value; + private long registered; + + // Constructors + + public NameData(byte[] registrantPublicKey, String owner, String name, String value, long timestamp) { + this.registrantPublicKey = registrantPublicKey; + this.owner = owner; + this.name = name; + this.value = value; + this.registered = timestamp; + } + + // Getters / setters + + public byte[] getRegistrantPublicKey() { + return this.registrantPublicKey; + } + + public String getOwner() { + return this.owner; + } + + public String getName() { + return this.name; + } + + public String getData() { + return this.value; + } + + public long getRegistered() { + return this.registered; + } + +} diff --git a/src/data/transaction/RegisterNameTransactionData.java b/src/data/transaction/RegisterNameTransactionData.java new file mode 100644 index 00000000..6e1e1904 --- /dev/null +++ b/src/data/transaction/RegisterNameTransactionData.java @@ -0,0 +1,49 @@ +package data.transaction; + +import java.math.BigDecimal; + +import qora.transaction.Transaction.TransactionType; + +public class RegisterNameTransactionData extends TransactionData { + + // Properties + private byte[] registrantPublicKey; + private String owner; + private String name; + private String data; + + // Constructors + + public RegisterNameTransactionData(byte[] registrantPublicKey, String owner, String name, String data, BigDecimal fee, long timestamp, byte[] reference, + byte[] signature) { + super(TransactionType.REGISTER_NAME, fee, registrantPublicKey, timestamp, reference, signature); + + this.registrantPublicKey = registrantPublicKey; + this.owner = owner; + this.name = name; + this.data = data; + } + + public RegisterNameTransactionData(byte[] registrantPublicKey, String owner, String name, String data, BigDecimal fee, long timestamp, byte[] reference) { + this(registrantPublicKey, owner, name, data, fee, timestamp, reference, null); + } + + // Getters / setters + + public byte[] getRegistrantPublicKey() { + return this.registrantPublicKey; + } + + public String getOwner() { + return this.owner; + } + + public String getName() { + return this.name; + } + + public String getData() { + return this.data; + } + +} diff --git a/src/qora/naming/Name.java b/src/qora/naming/Name.java new file mode 100644 index 00000000..6604c639 --- /dev/null +++ b/src/qora/naming/Name.java @@ -0,0 +1,54 @@ +package qora.naming; + +import data.naming.NameData; +import data.transaction.RegisterNameTransactionData; +import repository.DataException; +import repository.Repository; + +public class Name { + + // Properties + private Repository repository; + private NameData nameData; + + // Useful constants + public static final int MAX_NAME_SIZE = 400; + public static final int MAX_VALUE_SIZE = 4000; + + // Constructors + + /** + * Construct Name business object using info from register name transaction. + * + * @param repository + * @param registerNameTransactionData + */ + public Name(Repository repository, RegisterNameTransactionData registerNameTransactionData) { + this.repository = repository; + this.nameData = new NameData(registerNameTransactionData.getRegistrantPublicKey(), registerNameTransactionData.getOwner(), + registerNameTransactionData.getName(), registerNameTransactionData.getData(), registerNameTransactionData.getTimestamp()); + } + + /** + * Construct Name business object using existing name in repository. + * + * @param repository + * @param name + * @throws DataException + */ + public Name(Repository repository, String name) throws DataException { + this.repository = repository; + this.nameData = this.repository.getNameRepository().fromName(name); + } + + // Processing + + public void register() throws DataException { + this.repository.getNameRepository().save(this.nameData); + } + + public void unregister() throws DataException { + this.repository.getNameRepository().delete(this.nameData.getName()); + } + +} diff --git a/src/qora/transaction/CreatePollTransaction.java b/src/qora/transaction/CreatePollTransaction.java index 93f2efe5..eaea8ac8 100644 --- a/src/qora/transaction/CreatePollTransaction.java +++ b/src/qora/transaction/CreatePollTransaction.java @@ -167,11 +167,11 @@ public class CreatePollTransaction extends Transaction { // Delete this transaction itself this.repository.getTransactionRepository().delete(createPollTransactionData); - // Update issuer's balance + // Update creator's balance Account creator = new PublicKeyAccount(this.repository, createPollTransactionData.getCreatorPublicKey()); creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).add(createPollTransactionData.getFee())); - // Update issuer's reference + // Update creator's reference creator.setLastReference(createPollTransactionData.getReference()); } diff --git a/src/qora/transaction/RegisterNameTransaction.java b/src/qora/transaction/RegisterNameTransaction.java new file mode 100644 index 00000000..cdc86f8e --- /dev/null +++ b/src/qora/transaction/RegisterNameTransaction.java @@ -0,0 +1,147 @@ +package qora.transaction; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import data.transaction.RegisterNameTransactionData; +import data.transaction.TransactionData; +import qora.account.Account; +import qora.account.PublicKeyAccount; +import qora.assets.Asset; +import qora.crypto.Crypto; +import qora.naming.Name; +import repository.DataException; +import repository.Repository; + +public class RegisterNameTransaction extends Transaction { + + // Properties + private RegisterNameTransactionData registerNameTransactionData; + + // Constructors + + public RegisterNameTransaction(Repository repository, TransactionData transactionData) { + super(repository, transactionData); + + this.registerNameTransactionData = (RegisterNameTransactionData) this.transactionData; + } + + // More information + + @Override + public List getRecipientAccounts() throws DataException { + return Collections.singletonList(getOwner()); + } + + @Override + public boolean isInvolved(Account account) throws DataException { + String address = account.getAddress(); + + if (address.equals(this.getRegistrant().getAddress())) + return true; + + if (address.equals(this.getOwner().getAddress())) + return true; + + return false; + } + + @Override + public BigDecimal getAmount(Account account) throws DataException { + String address = account.getAddress(); + BigDecimal amount = BigDecimal.ZERO.setScale(8); + + if (address.equals(this.getRegistrant().getAddress())) + amount = amount.subtract(this.transactionData.getFee()); + + return amount; + } + + // Navigation + + public Account getRegistrant() throws DataException { + return new PublicKeyAccount(this.repository, this.registerNameTransactionData.getRegistrantPublicKey()); + } + + public Account getOwner() throws DataException { + return new Account(this.repository, this.registerNameTransactionData.getOwner()); + } + + // Processing + + @Override + public ValidationResult isValid() throws DataException { + // Check owner address is valid + if (!Crypto.isValidAddress(registerNameTransactionData.getOwner())) + return ValidationResult.INVALID_ADDRESS; + + // Check name size bounds + if (registerNameTransactionData.getName().length() < 1 || registerNameTransactionData.getName().length() > Name.MAX_NAME_SIZE) + return ValidationResult.INVALID_NAME_LENGTH; + + // Check value size bounds + if (registerNameTransactionData.getData().length() < 1 || registerNameTransactionData.getData().length() > Name.MAX_VALUE_SIZE) + return ValidationResult.INVALID_DATA_LENGTH; + + // Check name is lowercase + if (!registerNameTransactionData.getName().equals(registerNameTransactionData.getName().toLowerCase())) + return ValidationResult.NAME_NOT_LOWER_CASE; + + // Check the name isn't already taken + if (this.repository.getNameRepository().nameExists(registerNameTransactionData.getName())) + return ValidationResult.NAME_ALREADY_REGISTERED; + + // Check fee is positive + if (registerNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) + return ValidationResult.NEGATIVE_FEE; + + // Check reference is correct + PublicKeyAccount registrant = new PublicKeyAccount(this.repository, registerNameTransactionData.getRegistrantPublicKey()); + + if (!Arrays.equals(registrant.getLastReference(), registerNameTransactionData.getReference())) + return ValidationResult.INVALID_REFERENCE; + + // Check issuer has enough funds + if (registrant.getConfirmedBalance(Asset.QORA).compareTo(registerNameTransactionData.getFee()) == -1) + return ValidationResult.NO_BALANCE; + + return ValidationResult.OK; + } + + @Override + public void process() throws DataException { + // Register Name + Name name = new Name(this.repository, registerNameTransactionData); + name.register(); + + // Save this transaction + this.repository.getTransactionRepository().save(registerNameTransactionData); + + // Update registrant's balance + Account registrant = new PublicKeyAccount(this.repository, registerNameTransactionData.getRegistrantPublicKey()); + registrant.setConfirmedBalance(Asset.QORA, registrant.getConfirmedBalance(Asset.QORA).subtract(registerNameTransactionData.getFee())); + + // Update registrant's reference + registrant.setLastReference(registerNameTransactionData.getSignature()); + } + + @Override + public void orphan() throws DataException { + // Unregister name + Name name = new Name(this.repository, registerNameTransactionData.getName()); + name.unregister(); + + // Delete this transaction itself + this.repository.getTransactionRepository().delete(registerNameTransactionData); + + // Update registrant's balance + Account registrant = new PublicKeyAccount(this.repository, registerNameTransactionData.getRegistrantPublicKey()); + registrant.setConfirmedBalance(Asset.QORA, registrant.getConfirmedBalance(Asset.QORA).add(registerNameTransactionData.getFee())); + + // Update registrant's reference + registrant.setLastReference(registerNameTransactionData.getReference()); + } + +} diff --git a/src/qora/transaction/Transaction.java b/src/qora/transaction/Transaction.java index dc072fe3..e370277e 100644 --- a/src/qora/transaction/Transaction.java +++ b/src/qora/transaction/Transaction.java @@ -43,11 +43,11 @@ public abstract class Transaction { // Validation results public enum ValidationResult { - OK(1), INVALID_ADDRESS(2), NEGATIVE_AMOUNT(3), NEGATIVE_FEE(4), NO_BALANCE(5), INVALID_REFERENCE(6), INVALID_NAME_LENGTH(7), INVALID_AMOUNT( - 15), 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( + 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), 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); public final int value; @@ -104,6 +104,9 @@ public abstract class Transaction { case PAYMENT: return new PaymentTransaction(repository, transactionData); + case REGISTER_NAME: + return new RegisterNameTransaction(repository, transactionData); + case CREATE_POLL: return new CreatePollTransaction(repository, transactionData); diff --git a/src/repository/NameRepository.java b/src/repository/NameRepository.java new file mode 100644 index 00000000..38615907 --- /dev/null +++ b/src/repository/NameRepository.java @@ -0,0 +1,15 @@ +package repository; + +import data.naming.NameData; + +public interface NameRepository { + + public NameData fromName(String name) throws DataException; + + public boolean nameExists(String name) throws DataException; + + public void save(NameData nameData) throws DataException; + + public void delete(String name) throws DataException; + +} diff --git a/src/repository/Repository.java b/src/repository/Repository.java index 656a5d06..b9af67cd 100644 --- a/src/repository/Repository.java +++ b/src/repository/Repository.java @@ -8,6 +8,8 @@ public interface Repository extends AutoCloseable { public BlockRepository getBlockRepository(); + public NameRepository getNameRepository(); + public TransactionRepository getTransactionRepository(); public VotingRepository getVotingRepository(); diff --git a/src/repository/hsqldb/HSQLDBNameRepository.java b/src/repository/hsqldb/HSQLDBNameRepository.java new file mode 100644 index 00000000..ea9072cb --- /dev/null +++ b/src/repository/hsqldb/HSQLDBNameRepository.java @@ -0,0 +1,68 @@ +package repository.hsqldb; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import data.naming.NameData; +import repository.NameRepository; +import repository.DataException; + +public class HSQLDBNameRepository implements NameRepository { + + protected HSQLDBRepository repository; + + public HSQLDBNameRepository(HSQLDBRepository repository) { + this.repository = repository; + } + + @Override + public NameData fromName(String name) throws DataException { + try { + ResultSet resultSet = this.repository.checkedExecute("SELECT registrant, owner, name, registered FROM Names WHERE name = ?", name); + if (resultSet == null) + return null; + + byte[] registrantPublicKey = resultSet.getBytes(1); + String owner = resultSet.getString(2); + String data = resultSet.getString(3); + long timestamp = resultSet.getLong(4); + + return new NameData(registrantPublicKey, owner, name, data, timestamp); + } catch (SQLException e) { + throw new DataException("Unable to fetch name info from repository", e); + } + } + + @Override + public boolean nameExists(String name) throws DataException { + try { + return this.repository.exists("Names", "name = ?", name); + } catch (SQLException e) { + throw new DataException("Unable to check for name in repository", e); + } + } + + @Override + public void save(NameData nameData) throws DataException { + HSQLDBSaver saveHelper = new HSQLDBSaver("Names"); + + saveHelper.bind("registrant", nameData.getRegistrantPublicKey()).bind("owner", nameData.getOwner()).bind("name", nameData.getName()) + .bind("data", nameData.getData()).bind("registered", nameData.getRegistered()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save name info into repository", e); + } + } + + @Override + public void delete(String name) throws DataException { + try { + this.repository.delete("Names", "name = ?", name); + } catch (SQLException e) { + throw new DataException("Unable to delete name info from repository", e); + } + } + +} diff --git a/src/repository/hsqldb/HSQLDBRepository.java b/src/repository/hsqldb/HSQLDBRepository.java index 3a49ae83..ebceafdb 100644 --- a/src/repository/hsqldb/HSQLDBRepository.java +++ b/src/repository/hsqldb/HSQLDBRepository.java @@ -11,6 +11,7 @@ import repository.AccountRepository; import repository.AssetRepository; import repository.BlockRepository; import repository.DataException; +import repository.NameRepository; import repository.Repository; import repository.TransactionRepository; import repository.VotingRepository; @@ -40,6 +41,11 @@ public class HSQLDBRepository implements Repository { return new HSQLDBBlockRepository(this); } + @Override + public NameRepository getNameRepository() { + return new HSQLDBNameRepository(this); + } + @Override public TransactionRepository getTransactionRepository() { return new HSQLDBTransactionRepository(this); diff --git a/src/repository/hsqldb/transaction/HSQLDBRegisterNameTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBRegisterNameTransactionRepository.java new file mode 100644 index 00000000..3c11b0b3 --- /dev/null +++ b/src/repository/hsqldb/transaction/HSQLDBRegisterNameTransactionRepository.java @@ -0,0 +1,52 @@ +package repository.hsqldb.transaction; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; + +import data.transaction.RegisterNameTransactionData; +import data.transaction.TransactionData; +import repository.DataException; +import repository.hsqldb.HSQLDBRepository; +import repository.hsqldb.HSQLDBSaver; + +public class HSQLDBRegisterNameTransactionRepository extends HSQLDBTransactionRepository { + + public HSQLDBRegisterNameTransactionRepository(HSQLDBRepository repository) { + this.repository = repository; + } + + TransactionData fromBase(byte[] signature, byte[] reference, byte[] registrantPublicKey, long timestamp, BigDecimal fee) throws DataException { + try { + ResultSet rs = this.repository.checkedExecute("SELECT owner, name, data FROM RegisterNameTransactions WHERE signature = ?", signature); + if (rs == null) + return null; + + String owner = rs.getString(1); + String name = rs.getString(2); + String data = rs.getString(3); + + return new RegisterNameTransactionData(registrantPublicKey, owner, name, data, fee, timestamp, reference, signature); + } catch (SQLException e) { + throw new DataException("Unable to fetch register name transaction from repository", e); + } + } + + @Override + public void save(TransactionData transactionData) throws DataException { + RegisterNameTransactionData registerNameTransactionData = (RegisterNameTransactionData) transactionData; + + HSQLDBSaver saveHelper = new HSQLDBSaver("RegisterNameTransactions"); + + saveHelper.bind("signature", registerNameTransactionData.getSignature()).bind("registrant", registerNameTransactionData.getRegistrantPublicKey()) + .bind("owner", registerNameTransactionData.getOwner()).bind("name", registerNameTransactionData.getName()) + .bind("data", registerNameTransactionData.getData()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save register name transaction into repository", e); + } + } + +} diff --git a/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java index e7aa1468..70596f1a 100644 --- a/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java @@ -21,6 +21,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { protected HSQLDBRepository repository; private HSQLDBGenesisTransactionRepository genesisTransactionRepository; private HSQLDBPaymentTransactionRepository paymentTransactionRepository; + private HSQLDBRegisterNameTransactionRepository registerNameTransactionRepository; private HSQLDBCreatePollTransactionRepository createPollTransactionRepository; private HSQLDBVoteOnPollTransactionRepository voteOnPollTransactionRepository; private HSQLDBIssueAssetTransactionRepository issueAssetTransactionRepository; @@ -34,6 +35,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { this.repository = repository; this.genesisTransactionRepository = new HSQLDBGenesisTransactionRepository(repository); this.paymentTransactionRepository = new HSQLDBPaymentTransactionRepository(repository); + this.registerNameTransactionRepository = new HSQLDBRegisterNameTransactionRepository(repository); this.createPollTransactionRepository = new HSQLDBCreatePollTransactionRepository(repository); this.voteOnPollTransactionRepository = new HSQLDBVoteOnPollTransactionRepository(repository); this.issueAssetTransactionRepository = new HSQLDBIssueAssetTransactionRepository(repository); @@ -92,6 +94,9 @@ public class HSQLDBTransactionRepository implements TransactionRepository { case PAYMENT: return this.paymentTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); + case REGISTER_NAME: + return this.registerNameTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); + case CREATE_POLL: return this.createPollTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); @@ -220,6 +225,10 @@ public class HSQLDBTransactionRepository implements TransactionRepository { this.paymentTransactionRepository.save(transactionData); break; + case REGISTER_NAME: + this.registerNameTransactionRepository.save(transactionData); + break; + case CREATE_POLL: this.createPollTransactionRepository.save(transactionData); break; diff --git a/src/test/SerializationTests.java b/src/test/SerializationTests.java index db7a4748..88ab025b 100644 --- a/src/test/SerializationTests.java +++ b/src/test/SerializationTests.java @@ -2,7 +2,6 @@ package test; import static org.junit.Assert.*; -import java.sql.SQLException; import java.util.Arrays; import java.util.List; @@ -85,9 +84,43 @@ public class SerializationTests extends Common { } @Test - public void testMessageSerialization() throws SQLException, TransformationException { + public void testMessageSerialization() throws TransformationException { // Message transactions went live block 99000 // Some transactions to be found in block 99001/2/5/6 } + @Test + public void testRegisterNameSerialization() throws TransformationException, DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + // Block 120 has only name registration transactions + BlockData blockData = repository.getBlockRepository().fromHeight(120); + assertNotNull("Block 120 is required for this test", blockData); + + Block block = new Block(repository, blockData); + + List transactions = block.getTransactions(); + assertNotNull(transactions); + + for (Transaction transaction : transactions) + testGenericSerialization(transaction.getTransactionData()); + } + } + + @Test + public void testCreatePollSerialization() throws TransformationException, DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + // Block 10537 has only create poll transactions + BlockData blockData = repository.getBlockRepository().fromHeight(10537); + assertNotNull("Block 10537 is required for this test", blockData); + + Block block = new Block(repository, blockData); + + List transactions = block.getTransactions(); + assertNotNull(transactions); + + for (Transaction transaction : transactions) + testGenericSerialization(transaction.getTransactionData()); + } + } + } \ No newline at end of file diff --git a/src/transform/transaction/CreatePollTransactionTransformer.java b/src/transform/transaction/CreatePollTransactionTransformer.java index 98631646..f8584aa7 100644 --- a/src/transform/transaction/CreatePollTransactionTransformer.java +++ b/src/transform/transaction/CreatePollTransactionTransformer.java @@ -26,12 +26,14 @@ import utils.Serialization; public class CreatePollTransactionTransformer extends TransactionTransformer { // Property lengths + private static final int CREATOR_LENGTH = PUBLIC_KEY_LENGTH; private static final int OWNER_LENGTH = ADDRESS_LENGTH; private static final int NAME_SIZE_LENGTH = INT_LENGTH; private static final int DESCRIPTION_SIZE_LENGTH = INT_LENGTH; private static final int OPTIONS_SIZE_LENGTH = INT_LENGTH; - private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + DESCRIPTION_SIZE_LENGTH; + private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + CREATOR_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + DESCRIPTION_SIZE_LENGTH + + OPTIONS_SIZE_LENGTH; static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException { if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH) @@ -79,11 +81,12 @@ public class CreatePollTransactionTransformer extends TransactionTransformer { public static int getDataLength(TransactionData transactionData) throws TransformationException { CreatePollTransactionData createPollTransactionData = (CreatePollTransactionData) transactionData; - int dataLength = TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + createPollTransactionData.getPollName().length(); + int dataLength = TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + createPollTransactionData.getPollName().length() + + createPollTransactionData.getDescription().length(); // Add lengths for each poll options for (PollOptionData pollOptionData : createPollTransactionData.getPollOptions()) - dataLength += OPTIONS_SIZE_LENGTH + pollOptionData.getOptionName().length(); + dataLength += INT_LENGTH + pollOptionData.getOptionName().length(); return dataLength; } diff --git a/src/transform/transaction/RegisterNameTransactionTransformer.java b/src/transform/transaction/RegisterNameTransactionTransformer.java new file mode 100644 index 00000000..026d0c70 --- /dev/null +++ b/src/transform/transaction/RegisterNameTransactionTransformer.java @@ -0,0 +1,116 @@ +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.RegisterNameTransactionData; +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 { + + // Property lengths + private static final int REGISTRANT_LENGTH = PUBLIC_KEY_LENGTH; + private static final int OWNER_LENGTH = ADDRESS_LENGTH; + private static final int NAME_SIZE_LENGTH = INT_LENGTH; + private static final int VALUE_SIZE_LENGTH = INT_LENGTH; + + private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + REGISTRANT_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + VALUE_SIZE_LENGTH; + + static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException { + if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH) + throw new TransformationException("Byte data too short for RegisterNameTransaction"); + + long timestamp = byteBuffer.getLong(); + + byte[] reference = new byte[REFERENCE_LENGTH]; + byteBuffer.get(reference); + + byte[] registrantPublicKey = Serialization.deserializePublicKey(byteBuffer); + + String owner = Serialization.deserializeRecipient(byteBuffer); + + String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE); + String value = Serialization.deserializeSizedString(byteBuffer, Name.MAX_VALUE_SIZE); + + // Still need to make sure there are enough bytes left for remaining fields + if (byteBuffer.remaining() < FEE_LENGTH + SIGNATURE_LENGTH) + throw new TransformationException("Byte data too short for RegisterNameTransaction"); + + BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer); + + byte[] signature = new byte[SIGNATURE_LENGTH]; + byteBuffer.get(signature); + + return new RegisterNameTransactionData(registrantPublicKey, owner, name, value, fee, timestamp, reference, signature); + } + + public static int getDataLength(TransactionData transactionData) throws TransformationException { + RegisterNameTransactionData registerNameTransactionData = (RegisterNameTransactionData) transactionData; + + int dataLength = TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + registerNameTransactionData.getName().length() + registerNameTransactionData.getData().length(); + + return dataLength; + } + + public static byte[] toBytes(TransactionData transactionData) throws TransformationException { + try { + RegisterNameTransactionData registerNameTransactionData = (RegisterNameTransactionData) transactionData; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + bytes.write(Ints.toByteArray(registerNameTransactionData.getType().value)); + bytes.write(Longs.toByteArray(registerNameTransactionData.getTimestamp())); + bytes.write(registerNameTransactionData.getReference()); + + bytes.write(registerNameTransactionData.getRegistrantPublicKey()); + bytes.write(Base58.decode(registerNameTransactionData.getOwner())); + Serialization.serializeSizedString(bytes, registerNameTransactionData.getName()); + Serialization.serializeSizedString(bytes, registerNameTransactionData.getData()); + + Serialization.serializeBigDecimal(bytes, registerNameTransactionData.getFee()); + + if (registerNameTransactionData.getSignature() != null) + bytes.write(registerNameTransactionData.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 { + RegisterNameTransactionData registerNameTransactionData = (RegisterNameTransactionData) transactionData; + + byte[] registrantPublicKey = registerNameTransactionData.getRegistrantPublicKey(); + + json.put("registrant", PublicKeyAccount.getAddress(registrantPublicKey)); + json.put("registrantPublicKey", HashCode.fromBytes(registrantPublicKey).toString()); + + json.put("owner", registerNameTransactionData.getOwner()); + json.put("name", registerNameTransactionData.getName()); + json.put("value", registerNameTransactionData.getData()); + } catch (ClassCastException e) { + throw new TransformationException(e); + } + + return json; + } + +} diff --git a/src/transform/transaction/TransactionTransformer.java b/src/transform/transaction/TransactionTransformer.java index 423f7677..71c52d68 100644 --- a/src/transform/transaction/TransactionTransformer.java +++ b/src/transform/transaction/TransactionTransformer.java @@ -37,6 +37,9 @@ public class TransactionTransformer extends Transformer { case PAYMENT: return PaymentTransactionTransformer.fromByteBuffer(byteBuffer); + case REGISTER_NAME: + return RegisterNameTransactionTransformer.fromByteBuffer(byteBuffer); + case CREATE_POLL: return CreatePollTransactionTransformer.fromByteBuffer(byteBuffer); @@ -74,6 +77,9 @@ public class TransactionTransformer extends Transformer { case PAYMENT: return PaymentTransactionTransformer.getDataLength(transactionData); + case REGISTER_NAME: + return RegisterNameTransactionTransformer.getDataLength(transactionData); + case CREATE_POLL: return CreatePollTransactionTransformer.getDataLength(transactionData); @@ -111,6 +117,9 @@ public class TransactionTransformer extends Transformer { case PAYMENT: return PaymentTransactionTransformer.toBytes(transactionData); + case REGISTER_NAME: + return RegisterNameTransactionTransformer.toBytes(transactionData); + case CREATE_POLL: return CreatePollTransactionTransformer.toBytes(transactionData); @@ -148,6 +157,9 @@ public class TransactionTransformer extends Transformer { case PAYMENT: return PaymentTransactionTransformer.toJSON(transactionData); + case REGISTER_NAME: + return RegisterNameTransactionTransformer.toJSON(transactionData); + case CREATE_POLL: return CreatePollTransactionTransformer.toJSON(transactionData);