diff --git a/src/data/naming/NameData.java b/src/data/naming/NameData.java index 9de92465..286506ce 100644 --- a/src/data/naming/NameData.java +++ b/src/data/naming/NameData.java @@ -1,22 +1,37 @@ package data.naming; +import java.math.BigDecimal; + public class NameData { // Properties private byte[] registrantPublicKey; private String owner; private String name; - private String value; + private String data; private long registered; + private Long updated; + private byte[] reference; + private boolean isForSale; + private BigDecimal salePrice; // Constructors - public NameData(byte[] registrantPublicKey, String owner, String name, String value, long timestamp) { + public NameData(byte[] registrantPublicKey, String owner, String name, String data, long registered, Long updated, byte[] reference, boolean isForSale, + BigDecimal salePrice) { this.registrantPublicKey = registrantPublicKey; this.owner = owner; this.name = name; - this.value = value; - this.registered = timestamp; + this.data = data; + this.registered = registered; + this.updated = updated; + this.reference = reference; + this.isForSale = isForSale; + this.salePrice = salePrice; + } + + public NameData(byte[] registrantPublicKey, String owner, String name, String data, long registered, byte[] reference) { + this(registrantPublicKey, owner, name, data, registered, null, reference, false, null); } // Getters / setters @@ -29,16 +44,56 @@ public class NameData { return this.owner; } + public void setOwner(String owner) { + this.owner = owner; + } + public String getName() { return this.name; } public String getData() { - return this.value; + return this.data; + } + + public void setData(String data) { + this.data = data; } public long getRegistered() { return this.registered; } + public Long getUpdated() { + return this.updated; + } + + public void setUpdated(Long updated) { + this.updated = updated; + } + + public byte[] getReference() { + return this.reference; + } + + public void setReference(byte[] reference) { + this.reference = reference; + } + + public boolean getIsForSale() { + return this.isForSale; + } + + public void setIsForSale(boolean isForSale) { + this.isForSale = isForSale; + } + + public BigDecimal getSalePrice() { + return this.salePrice; + } + + public void setSalePrice(BigDecimal salePrice) { + this.salePrice = salePrice; + } + } diff --git a/src/data/transaction/UpdateNameTransactionData.java b/src/data/transaction/UpdateNameTransactionData.java new file mode 100644 index 00000000..519087f0 --- /dev/null +++ b/src/data/transaction/UpdateNameTransactionData.java @@ -0,0 +1,60 @@ +package data.transaction; + +import java.math.BigDecimal; + +import qora.transaction.Transaction.TransactionType; + +public class UpdateNameTransactionData extends TransactionData { + + // Properties + private byte[] ownerPublicKey; + private String newOwner; + private String name; + private String newData; + private byte[] nameReference; + + // Constructors + + public UpdateNameTransactionData(byte[] ownerPublicKey, String newOwner, String name, String newData, byte[] nameReference, BigDecimal fee, long timestamp, + byte[] reference, byte[] signature) { + super(TransactionType.UPDATE_NAME, fee, ownerPublicKey, timestamp, reference, signature); + + this.ownerPublicKey = ownerPublicKey; + this.newOwner = newOwner; + this.name = name; + this.newData = newData; + this.nameReference = nameReference; + } + + 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); + } + + // Getters / setters + + public byte[] getOwnerPublicKey() { + return this.ownerPublicKey; + } + + public String getNewOwner() { + return this.newOwner; + } + + public String getName() { + return this.name; + } + + public String getNewData() { + return this.newData; + } + + public byte[] getNameReference() { + return this.nameReference; + } + + public void setNameReference(byte[] nameReference) { + this.nameReference = nameReference; + } + +} diff --git a/src/qora/block/Block.java b/src/qora/block/Block.java index dc3de66b..9d42c5fc 100644 --- a/src/qora/block/Block.java +++ b/src/qora/block/Block.java @@ -28,6 +28,7 @@ import repository.Repository; import transform.TransformationException; import transform.block.BlockTransformer; import transform.transaction.TransactionTransformer; +import utils.Base58; import utils.NTP; /* @@ -554,6 +555,9 @@ public class Block { transaction.process(); } catch (Exception e) { // LOGGER.error("Exception during transaction processing, tx " + Base58.encode(transaction.getSignature()), e); + System.err.println("Exception during transaction processing, tx " + Base58.encode(transaction.getTransactionData().getSignature()) + ": " + + e.getMessage()); + e.printStackTrace(); return ValidationResult.TRANSACTION_PROCESSING_FAILED; } } @@ -565,8 +569,8 @@ public class Block { this.repository.discardChanges(); } catch (DataException e) { /* - * Discard failure most likely due to prior DataException, so catch discardChanges' DataException and discard. - * Prior DataException propagates to caller. Successful completion of try-block continues on after discard. + * Discard failure most likely due to prior DataException, so catch discardChanges' DataException and discard. Prior DataException propagates to + * caller. Successful completion of try-block continues on after discard. */ } } diff --git a/src/qora/naming/Name.java b/src/qora/naming/Name.java index 6604c639..7cc837fe 100644 --- a/src/qora/naming/Name.java +++ b/src/qora/naming/Name.java @@ -2,6 +2,8 @@ package qora.naming; import data.naming.NameData; import data.transaction.RegisterNameTransactionData; +import data.transaction.TransactionData; +import data.transaction.UpdateNameTransactionData; import repository.DataException; import repository.Repository; @@ -13,7 +15,7 @@ public class Name { // Useful constants public static final int MAX_NAME_SIZE = 400; - public static final int MAX_VALUE_SIZE = 4000; + public static final int MAX_DATA_SIZE = 4000; // Constructors @@ -26,7 +28,8 @@ public class Name { public Name(Repository repository, RegisterNameTransactionData registerNameTransactionData) { this.repository = repository; this.nameData = new NameData(registerNameTransactionData.getRegistrantPublicKey(), registerNameTransactionData.getOwner(), - registerNameTransactionData.getName(), registerNameTransactionData.getData(), registerNameTransactionData.getTimestamp()); + registerNameTransactionData.getName(), registerNameTransactionData.getData(), registerNameTransactionData.getTimestamp(), + registerNameTransactionData.getSignature()); } /** @@ -51,4 +54,49 @@ public class Name { this.repository.getNameRepository().delete(this.nameData.getName()); } + public void update(UpdateNameTransactionData updateNameTransactionData) throws DataException { + // Update reference in transaction data + updateNameTransactionData.setNameReference(this.nameData.getReference()); + + // New name reference is this transaction's signature + this.nameData.setReference(updateNameTransactionData.getSignature()); + + // Update Name's owner and data + this.nameData.setOwner(updateNameTransactionData.getNewOwner()); + this.nameData.setData(updateNameTransactionData.getNewData()); + + // Save updated name data + this.repository.getNameRepository().save(this.nameData); + } + + public void revert(UpdateNameTransactionData updateNameTransactionData) throws DataException { + // Previous name reference is taken from this transaction's cached copy + 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"); + } + + // Save reverted name data + this.repository.getNameRepository().save(this.nameData); + } + } diff --git a/src/qora/transaction/RegisterNameTransaction.java b/src/qora/transaction/RegisterNameTransaction.java index cdc86f8e..bd6affeb 100644 --- a/src/qora/transaction/RegisterNameTransaction.java +++ b/src/qora/transaction/RegisterNameTransaction.java @@ -82,7 +82,7 @@ public class RegisterNameTransaction extends Transaction { return ValidationResult.INVALID_NAME_LENGTH; // Check value size bounds - if (registerNameTransactionData.getData().length() < 1 || registerNameTransactionData.getData().length() > Name.MAX_VALUE_SIZE) + if (registerNameTransactionData.getData().length() < 1 || registerNameTransactionData.getData().length() > Name.MAX_DATA_SIZE) return ValidationResult.INVALID_DATA_LENGTH; // Check name is lowercase diff --git a/src/qora/transaction/Transaction.java b/src/qora/transaction/Transaction.java index e370277e..daedae62 100644 --- a/src/qora/transaction/Transaction.java +++ b/src/qora/transaction/Transaction.java @@ -44,11 +44,12 @@ 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), 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), 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; @@ -107,6 +108,9 @@ public abstract class Transaction { case REGISTER_NAME: return new RegisterNameTransaction(repository, transactionData); + case UPDATE_NAME: + return new UpdateNameTransaction(repository, transactionData); + case CREATE_POLL: return new CreatePollTransaction(repository, transactionData); diff --git a/src/qora/transaction/UpdateNameTransaction.java b/src/qora/transaction/UpdateNameTransaction.java new file mode 100644 index 00000000..4492769f --- /dev/null +++ b/src/qora/transaction/UpdateNameTransaction.java @@ -0,0 +1,157 @@ +package qora.transaction; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import data.transaction.UpdateNameTransactionData; +import data.naming.NameData; +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 UpdateNameTransaction extends Transaction { + + // Properties + private UpdateNameTransactionData updateNameTransactionData; + + // Constructors + + public UpdateNameTransaction(Repository repository, TransactionData transactionData) { + super(repository, transactionData); + + this.updateNameTransactionData = (UpdateNameTransactionData) this.transactionData; + } + + // More information + + @Override + public List getRecipientAccounts() throws DataException { + return Collections.singletonList(getNewOwner()); + } + + @Override + public boolean isInvolved(Account account) throws DataException { + String address = account.getAddress(); + + if (address.equals(this.getOwner().getAddress())) + return true; + + if (address.equals(this.getNewOwner().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.updateNameTransactionData.getOwnerPublicKey()); + } + + public Account getNewOwner() throws DataException { + return new Account(this.repository, this.updateNameTransactionData.getNewOwner()); + } + + // Processing + + @Override + public ValidationResult isValid() throws DataException { + // Check new owner address is valid + if (!Crypto.isValidAddress(updateNameTransactionData.getNewOwner())) + return ValidationResult.INVALID_ADDRESS; + + // Check name size bounds + if (updateNameTransactionData.getName().length() < 1 || updateNameTransactionData.getName().length() > Name.MAX_NAME_SIZE) + return ValidationResult.INVALID_NAME_LENGTH; + + // Check value size bounds + if (updateNameTransactionData.getNewData().length() < 1 || updateNameTransactionData.getNewData().length() > Name.MAX_DATA_SIZE) + return ValidationResult.INVALID_DATA_LENGTH; + + // Check name is lowercase + if (!updateNameTransactionData.getName().equals(updateNameTransactionData.getName().toLowerCase())) + return ValidationResult.NAME_NOT_LOWER_CASE; + + NameData nameData = this.repository.getNameRepository().fromName(updateNameTransactionData.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, updateNameTransactionData.getOwnerPublicKey()); + if (!owner.getAddress().equals(nameData.getOwner())) + return ValidationResult.INVALID_NAME_OWNER; + + // Check fee is positive + if (updateNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) + return ValidationResult.NEGATIVE_FEE; + + // Check reference is correct + if (!Arrays.equals(owner.getLastReference(), updateNameTransactionData.getReference())) + return ValidationResult.INVALID_REFERENCE; + + // Check issuer has enough funds + if (owner.getConfirmedBalance(Asset.QORA).compareTo(updateNameTransactionData.getFee()) == -1) + return ValidationResult.NO_BALANCE; + + return ValidationResult.OK; + } + + @Override + public void process() throws DataException { + // Update Name + Name name = new Name(this.repository, updateNameTransactionData.getName()); + name.update(updateNameTransactionData); + + // Save this transaction, now with updated "name reference" to previous transaction that updated name + this.repository.getTransactionRepository().save(updateNameTransactionData); + + // Update owner's balance + Account owner = new PublicKeyAccount(this.repository, updateNameTransactionData.getOwnerPublicKey()); + owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).subtract(updateNameTransactionData.getFee())); + + // Update owner's reference + owner.setLastReference(updateNameTransactionData.getSignature()); + } + + @Override + public void orphan() throws DataException { + // Revert name + Name name = new Name(this.repository, updateNameTransactionData.getName()); + name.revert(updateNameTransactionData); + + // Delete this transaction itself + this.repository.getTransactionRepository().delete(updateNameTransactionData); + + // Update owner's balance + Account owner = new PublicKeyAccount(this.repository, updateNameTransactionData.getOwnerPublicKey()); + owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).add(updateNameTransactionData.getFee())); + + // Update owner's reference + owner.setLastReference(updateNameTransactionData.getReference()); + } + +} diff --git a/src/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/repository/hsqldb/HSQLDBDatabaseUpdates.java index d0aae1f1..3fb9d368 100644 --- a/src/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -171,7 +171,7 @@ public class HSQLDBDatabaseUpdates { case 6: // Update Name Transactions stmt.execute("CREATE TABLE UpdateNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, " - + "new_owner QoraAddress NOT NULL, new_data NameData NOT NULL, " + + "new_owner QoraAddress NOT NULL, new_data NameData NOT NULL, name_reference Signature NOT NULL, " + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); break; @@ -324,6 +324,14 @@ public class HSQLDBDatabaseUpdates { stmt.execute("CREATE INDEX PollOwnerIndex on Polls (owner)"); break; + case 25: + // Registered Names + stmt.execute( + "CREATE TABLE Names (name RegisteredName, data VARCHAR(4000) NOT NULL, registrant QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, " + + "registered TIMESTAMP NOT NULL, updated TIMESTAMP, reference Signature, is_for_sale BOOLEAN NOT NULL, sale_price QoraAmount, " + + "PRIMARY KEY (name))"); + break; + default: // nothing to do return false; diff --git a/src/repository/hsqldb/HSQLDBNameRepository.java b/src/repository/hsqldb/HSQLDBNameRepository.java index ea9072cb..6737dfd4 100644 --- a/src/repository/hsqldb/HSQLDBNameRepository.java +++ b/src/repository/hsqldb/HSQLDBNameRepository.java @@ -1,7 +1,9 @@ package repository.hsqldb; +import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Timestamp; import data.naming.NameData; import repository.NameRepository; @@ -18,16 +20,25 @@ public class HSQLDBNameRepository implements NameRepository { @Override public NameData fromName(String name) throws DataException { try { - ResultSet resultSet = this.repository.checkedExecute("SELECT registrant, owner, name, registered FROM Names WHERE name = ?", name); + ResultSet resultSet = this.repository + .checkedExecute("SELECT registrant, owner, data, registered, updated, reference, is_for_sale, sale_price 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); + long registered = resultSet.getTimestamp(4).getTime(); - return new NameData(registrantPublicKey, owner, name, data, timestamp); + // Special handling for possibly-NULL "updated" column + Timestamp updatedTimestamp = resultSet.getTimestamp(5); + Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime(); + + byte[] reference = resultSet.getBytes(6); + boolean isForSale = resultSet.getBoolean(7); + BigDecimal salePrice = resultSet.getBigDecimal(8); + + return new NameData(registrantPublicKey, owner, name, data, registered, updated, reference, isForSale, salePrice); } catch (SQLException e) { throw new DataException("Unable to fetch name info from repository", e); } @@ -46,8 +57,13 @@ public class HSQLDBNameRepository implements NameRepository { public void save(NameData nameData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("Names"); + // Special handling for "updated" timestamp + Long updated = nameData.getUpdated(); + Timestamp updatedTimestamp = updated == null ? null : new Timestamp(updated); + saveHelper.bind("registrant", nameData.getRegistrantPublicKey()).bind("owner", nameData.getOwner()).bind("name", nameData.getName()) - .bind("data", nameData.getData()).bind("registered", nameData.getRegistered()); + .bind("data", nameData.getData()).bind("registered", new Timestamp(nameData.getRegistered())).bind("updated", updatedTimestamp) + .bind("reference", nameData.getReference()).bind("is_for_sale", nameData.getIsForSale()).bind("sale_price", nameData.getSalePrice()); try { saveHelper.execute(this.repository); diff --git a/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java index 70596f1a..ff1c59d6 100644 --- a/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java @@ -22,6 +22,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { private HSQLDBGenesisTransactionRepository genesisTransactionRepository; private HSQLDBPaymentTransactionRepository paymentTransactionRepository; private HSQLDBRegisterNameTransactionRepository registerNameTransactionRepository; + private HSQLDBUpdateNameTransactionRepository updateNameTransactionRepository; private HSQLDBCreatePollTransactionRepository createPollTransactionRepository; private HSQLDBVoteOnPollTransactionRepository voteOnPollTransactionRepository; private HSQLDBIssueAssetTransactionRepository issueAssetTransactionRepository; @@ -36,6 +37,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { this.genesisTransactionRepository = new HSQLDBGenesisTransactionRepository(repository); this.paymentTransactionRepository = new HSQLDBPaymentTransactionRepository(repository); this.registerNameTransactionRepository = new HSQLDBRegisterNameTransactionRepository(repository); + this.updateNameTransactionRepository = new HSQLDBUpdateNameTransactionRepository(repository); this.createPollTransactionRepository = new HSQLDBCreatePollTransactionRepository(repository); this.voteOnPollTransactionRepository = new HSQLDBVoteOnPollTransactionRepository(repository); this.issueAssetTransactionRepository = new HSQLDBIssueAssetTransactionRepository(repository); @@ -97,6 +99,9 @@ public class HSQLDBTransactionRepository implements TransactionRepository { case REGISTER_NAME: return this.registerNameTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); + case UPDATE_NAME: + return this.updateNameTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); + case CREATE_POLL: return this.createPollTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); @@ -229,6 +234,10 @@ public class HSQLDBTransactionRepository implements TransactionRepository { this.registerNameTransactionRepository.save(transactionData); break; + case UPDATE_NAME: + this.updateNameTransactionRepository.save(transactionData); + break; + case CREATE_POLL: this.createPollTransactionRepository.save(transactionData); break; diff --git a/src/repository/hsqldb/transaction/HSQLDBUpdateNameTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBUpdateNameTransactionRepository.java new file mode 100644 index 00000000..11f0cb9f --- /dev/null +++ b/src/repository/hsqldb/transaction/HSQLDBUpdateNameTransactionRepository.java @@ -0,0 +1,53 @@ +package repository.hsqldb.transaction; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; + +import data.transaction.UpdateNameTransactionData; +import data.transaction.TransactionData; +import repository.DataException; +import repository.hsqldb.HSQLDBRepository; +import repository.hsqldb.HSQLDBSaver; + +public class HSQLDBUpdateNameTransactionRepository extends HSQLDBTransactionRepository { + + public HSQLDBUpdateNameTransactionRepository(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 new_owner, name, new_data, name_reference FROM UpdateNameTransactions WHERE signature = ?", signature); + if (rs == null) + return null; + + String newOwner = rs.getString(1); + String name = rs.getString(2); + String newData = rs.getString(3); + byte[] nameReference = rs.getBytes(4); + + return new UpdateNameTransactionData(ownerPublicKey, newOwner, name, newData, nameReference, fee, timestamp, reference, signature); + } catch (SQLException e) { + throw new DataException("Unable to fetch update name transaction from repository", e); + } + } + + @Override + public void save(TransactionData transactionData) throws DataException { + UpdateNameTransactionData updateNameTransactionData = (UpdateNameTransactionData) transactionData; + + HSQLDBSaver saveHelper = new HSQLDBSaver("UpdateNameTransactions"); + + saveHelper.bind("signature", updateNameTransactionData.getSignature()).bind("owner", updateNameTransactionData.getOwnerPublicKey()) + .bind("new_owner", updateNameTransactionData.getNewOwner()).bind("name", updateNameTransactionData.getName()) + .bind("new_data", updateNameTransactionData.getNewData()).bind("name_reference", updateNameTransactionData.getNameReference()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save update name transaction into repository", e); + } + } + +} diff --git a/src/test/SerializationTests.java b/src/test/SerializationTests.java index 88ab025b..d27857ff 100644 --- a/src/test/SerializationTests.java +++ b/src/test/SerializationTests.java @@ -14,6 +14,7 @@ import qora.block.Block; import qora.block.GenesisBlock; import qora.transaction.GenesisTransaction; import qora.transaction.Transaction; +import qora.transaction.Transaction.TransactionType; import repository.DataException; import repository.Repository; import repository.RepositoryManager; @@ -65,13 +66,10 @@ public class SerializationTests extends Common { assertEquals(TransactionTransformer.getDataLength(transactionData), bytes.length); } - @Test - public void testPaymentSerialization() throws TransformationException, DataException { + private void testSpecificBlockTransactions(int height, TransactionType type) throws DataException, TransformationException { try (final Repository repository = RepositoryManager.getRepository()) { - // Block 949 has lots of varied transactions - // Blocks 390 & 754 have only payment transactions - BlockData blockData = repository.getBlockRepository().fromHeight(754); - assertNotNull("Block 754 is required for this test", blockData); + BlockData blockData = repository.getBlockRepository().fromHeight(height); + assertNotNull("Block " + height + " is required for this test", blockData); Block block = new Block(repository, blockData); @@ -79,48 +77,39 @@ public class SerializationTests extends Common { assertNotNull(transactions); for (Transaction transaction : transactions) - testGenericSerialization(transaction.getTransactionData()); + if (transaction.getTransactionData().getType() == type) + testGenericSerialization(transaction.getTransactionData()); } } @Test - public void testMessageSerialization() throws TransformationException { - // Message transactions went live block 99000 - // Some transactions to be found in block 99001/2/5/6 + public void testPaymentSerialization() throws TransformationException, DataException { + // Blocks 390 & 754 have only payment transactions + testSpecificBlockTransactions(754, TransactionType.PAYMENT); } @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 120 has only name registration transactions + testSpecificBlockTransactions(120, TransactionType.REGISTER_NAME); + } - Block block = new Block(repository, blockData); - - List transactions = block.getTransactions(); - assertNotNull(transactions); - - for (Transaction transaction : transactions) - testGenericSerialization(transaction.getTransactionData()); - } + @Test + public void testUpdateNameSerialization() throws TransformationException, DataException { + testSpecificBlockTransactions(673, TransactionType.UPDATE_NAME); } @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 10537 has only create poll transactions + testSpecificBlockTransactions(10537, TransactionType.CREATE_POLL); + } - Block block = new Block(repository, blockData); - - List transactions = block.getTransactions(); - assertNotNull(transactions); - - for (Transaction transaction : transactions) - testGenericSerialization(transaction.getTransactionData()); - } + @Test + public void testMessageSerialization() throws TransformationException, DataException { + // Message transactions went live block 99000 + // Some transactions to be found in block 99001/2/5/6 + testSpecificBlockTransactions(99001, TransactionType.MESSAGE); } } \ No newline at end of file diff --git a/src/test/TransactionTests.java b/src/test/TransactionTests.java index eac05879..a20bbb02 100644 --- a/src/test/TransactionTests.java +++ b/src/test/TransactionTests.java @@ -15,8 +15,11 @@ import com.google.common.hash.HashCode; import data.account.AccountBalanceData; import data.account.AccountData; import data.block.BlockData; +import data.naming.NameData; import data.transaction.CreatePollTransactionData; import data.transaction.PaymentTransactionData; +import data.transaction.RegisterNameTransactionData; +import data.transaction.UpdateNameTransactionData; import data.transaction.VoteOnPollTransactionData; import data.voting.PollData; import data.voting.PollOptionData; @@ -29,8 +32,10 @@ import qora.block.Block; import qora.block.BlockChain; import qora.transaction.CreatePollTransaction; import qora.transaction.PaymentTransaction; +import qora.transaction.RegisterNameTransaction; import qora.transaction.Transaction; import qora.transaction.Transaction.ValidationResult; +import qora.transaction.UpdateNameTransaction; import qora.transaction.VoteOnPollTransaction; import repository.AccountRepository; import repository.DataException; @@ -54,7 +59,7 @@ public class TransactionTests { private Repository repository; private AccountRepository accountRepository; - private BlockData genesisBlockData; + private BlockData parentBlockData; private PrivateKeyAccount sender; private PrivateKeyAccount generator; private byte[] reference; @@ -80,7 +85,7 @@ public class TransactionTests { repository = RepositoryManager.getRepository(); // Grab genesis block - genesisBlockData = repository.getBlockRepository().fromHeight(1); + parentBlockData = repository.getBlockRepository().fromHeight(1); accountRepository = repository.getAccountRepository(); @@ -115,7 +120,7 @@ public class TransactionTests { Account recipient = new PublicKeyAccount(repository, recipientSeed); BigDecimal amount = BigDecimal.valueOf(1_000L); BigDecimal fee = BigDecimal.ONE; - long timestamp = genesisBlockData.getTimestamp() + 1_000; + long timestamp = parentBlockData.getTimestamp() + 1_000; PaymentTransactionData paymentTransactionData = new PaymentTransactionData(sender.getPublicKey(), recipient.getAddress(), amount, fee, timestamp, reference); @@ -125,7 +130,7 @@ public class TransactionTests { assertEquals(ValidationResult.OK, paymentTransaction.isValid()); // Forge new block with payment transaction - Block block = new Block(repository, genesisBlockData, generator, null, null); + Block block = new Block(repository, parentBlockData, generator, null, null); block.addTransaction(paymentTransactionData); block.sign(); @@ -151,6 +156,106 @@ public class TransactionTests { assertTrue("Recipient's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); } + @Test + public void testRegisterNameTransaction() throws DataException { + createTestAccounts(null); + + // Make a new register name transaction + String name = "test name"; + String data = "{\"key\":\"value\"}"; + + BigDecimal fee = BigDecimal.ONE; + long timestamp = parentBlockData.getTimestamp() + 1_000; + RegisterNameTransactionData registerNameTransactionData = new RegisterNameTransactionData(sender.getPublicKey(), sender.getAddress(), name, data, fee, + timestamp, reference); + + Transaction registerNameTransaction = new RegisterNameTransaction(repository, registerNameTransactionData); + registerNameTransaction.calcSignature(sender); + assertTrue(registerNameTransaction.isSignatureValid()); + assertEquals(ValidationResult.OK, registerNameTransaction.isValid()); + + // Forge new block with transaction + Block block = new Block(repository, parentBlockData, generator, null, null); + block.addTransaction(registerNameTransactionData); + 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(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); + + // Check name was registered + NameData actualNameData = this.repository.getNameRepository().fromName(name); + assertNotNull(actualNameData); + + // Check sender's reference + assertTrue("Sender's new reference incorrect", Arrays.equals(registerNameTransactionData.getSignature(), sender.getLastReference())); + + // Update variables for use by other tests + reference = sender.getLastReference(); + parentBlockData = block.getBlockData(); + } + + @Test + public void testUpdateNamesTransaction() throws DataException { + // Register name using another test + testRegisterNameTransaction(); + + String name = "test name"; + NameData originalNameData = this.repository.getNameRepository().fromName(name); + + // Update name's owner and data + Account newOwner = new PublicKeyAccount(repository, recipientSeed); + String newData = "{\"newKey\":\"newValue\"}"; + byte[] nameReference = reference; + + BigDecimal fee = BigDecimal.ONE; + long timestamp = parentBlockData.getTimestamp() + 2_000; + UpdateNameTransactionData updateNameTransactionData = new UpdateNameTransactionData(sender.getPublicKey(), newOwner.getAddress(), name, newData, + nameReference, fee, timestamp, reference); + + Transaction updateNameTransaction = new UpdateNameTransaction(repository, updateNameTransactionData); + updateNameTransaction.calcSignature(sender); + assertTrue(updateNameTransaction.isSignatureValid()); + assertEquals(ValidationResult.OK, updateNameTransaction.isValid()); + + // Forge new block with transaction + Block block = new Block(repository, parentBlockData, generator, null, null); + block.addTransaction(updateNameTransactionData); + 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); + assertEquals(newOwner.getAddress(), actualNameData.getOwner()); + assertEquals(newData, actualNameData.getData()); + + // Now orphan block + block.orphan(); + repository.saveChanges(); + + // Check name has been reverted correctly + actualNameData = this.repository.getNameRepository().fromName(name); + assertEquals(originalNameData.getOwner(), actualNameData.getOwner()); + assertEquals(originalNameData.getData(), actualNameData.getData()); + } + @Test public void testCreatePollTransaction() throws DataException { // This test requires GenesisBlock's timestamp is set to something after BlockChain.VOTING_RELEASE_TIMESTAMP @@ -167,7 +272,7 @@ public class TransactionTests { Account recipient = new PublicKeyAccount(repository, recipientSeed); BigDecimal fee = BigDecimal.ONE; - long timestamp = genesisBlockData.getTimestamp() + 1_000; + long timestamp = parentBlockData.getTimestamp() + 1_000; CreatePollTransactionData createPollTransactionData = new CreatePollTransactionData(sender.getPublicKey(), recipient.getAddress(), pollName, description, pollOptions, fee, timestamp, reference); @@ -177,7 +282,7 @@ public class TransactionTests { assertEquals(ValidationResult.OK, createPollTransaction.isValid()); // Forge new block with transaction - Block block = new Block(repository, genesisBlockData, generator, null, null); + Block block = new Block(repository, parentBlockData, generator, null, null); block.addTransaction(createPollTransactionData); block.sign(); @@ -204,8 +309,9 @@ public class TransactionTests { // Check sender's reference assertTrue("Sender's new reference incorrect", Arrays.equals(createPollTransactionData.getSignature(), sender.getLastReference())); - // Update reference variable for use by other tests + // Update variables for use by other tests reference = sender.getLastReference(); + parentBlockData = block.getBlockData(); } @Test @@ -217,8 +323,7 @@ public class TransactionTests { String pollName = "test poll"; int pollOptionsSize = 3; BigDecimal fee = BigDecimal.ONE; - long timestamp = genesisBlockData.getTimestamp() + 1_000; - BlockData previousBlockData = genesisBlockData; + long timestamp = parentBlockData.getTimestamp() + 1_000; for (int optionIndex = 0; optionIndex <= pollOptionsSize; ++optionIndex) { // Make a vote-on-poll transaction @@ -236,7 +341,7 @@ public class TransactionTests { assertEquals(ValidationResult.OK, voteOnPollTransaction.isValid()); // Forge new block with transaction - Block block = new Block(repository, previousBlockData, generator, null, null); + Block block = new Block(repository, parentBlockData, generator, null, null); block.addTransaction(voteOnPollTransactionData); block.sign(); @@ -252,7 +357,7 @@ public class TransactionTests { assertEquals(optionIndex, actualVoteOnPollData.getOptionIndex()); // update variables for next round - previousBlockData = block.getBlockData(); + parentBlockData = block.getBlockData(); timestamp += 1_000; reference = voteOnPollTransaction.getTransactionData().getSignature(); } diff --git a/src/transform/Transformer.java b/src/transform/Transformer.java index 8de458d7..cd6b4328 100644 --- a/src/transform/Transformer.java +++ b/src/transform/Transformer.java @@ -2,7 +2,7 @@ package transform; public abstract class Transformer { - public static final int BOOLEAN_LENGTH = 4; + public static final int BOOLEAN_LENGTH = 1; public static final int INT_LENGTH = 4; public static final int LONG_LENGTH = 8; public static final int BIG_DECIMAL_LENGTH = 8; diff --git a/src/transform/transaction/MessageTransactionTransformer.java b/src/transform/transaction/MessageTransactionTransformer.java index 967f1510..5a3f432c 100644 --- a/src/transform/transaction/MessageTransactionTransformer.java +++ b/src/transform/transaction/MessageTransactionTransformer.java @@ -64,7 +64,7 @@ public class MessageTransactionTransformer extends TransactionTransformer { BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer); - int dataSize = byteBuffer.getInt(0); + int dataSize = byteBuffer.getInt(); // Don't allow invalid dataSize here to avoid run-time issues if (dataSize > MessageTransaction.MAX_DATA_SIZE) throw new TransformationException("MessageTransaction data size too large"); diff --git a/src/transform/transaction/RegisterNameTransactionTransformer.java b/src/transform/transaction/RegisterNameTransactionTransformer.java index 026d0c70..ea893f86 100644 --- a/src/transform/transaction/RegisterNameTransactionTransformer.java +++ b/src/transform/transaction/RegisterNameTransactionTransformer.java @@ -25,9 +25,9 @@ public class RegisterNameTransactionTransformer extends TransactionTransformer { 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 DATA_SIZE_LENGTH = INT_LENGTH; - private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + REGISTRANT_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + VALUE_SIZE_LENGTH; + private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + REGISTRANT_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + DATA_SIZE_LENGTH; static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException { if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH) @@ -43,7 +43,7 @@ public class RegisterNameTransactionTransformer extends TransactionTransformer { String owner = Serialization.deserializeRecipient(byteBuffer); String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE); - String value = Serialization.deserializeSizedString(byteBuffer, Name.MAX_VALUE_SIZE); + String data = Serialization.deserializeSizedString(byteBuffer, Name.MAX_DATA_SIZE); // Still need to make sure there are enough bytes left for remaining fields if (byteBuffer.remaining() < FEE_LENGTH + SIGNATURE_LENGTH) @@ -54,13 +54,14 @@ public class RegisterNameTransactionTransformer extends TransactionTransformer { byte[] signature = new byte[SIGNATURE_LENGTH]; byteBuffer.get(signature); - return new RegisterNameTransactionData(registrantPublicKey, owner, name, value, fee, timestamp, reference, signature); + return new RegisterNameTransactionData(registrantPublicKey, owner, name, data, 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(); + int dataLength = TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + registerNameTransactionData.getName().length() + + registerNameTransactionData.getData().length(); return dataLength; } @@ -105,7 +106,7 @@ public class RegisterNameTransactionTransformer extends TransactionTransformer { json.put("owner", registerNameTransactionData.getOwner()); json.put("name", registerNameTransactionData.getName()); - json.put("value", registerNameTransactionData.getData()); + json.put("data", registerNameTransactionData.getData()); } catch (ClassCastException e) { throw new TransformationException(e); } diff --git a/src/transform/transaction/TransactionTransformer.java b/src/transform/transaction/TransactionTransformer.java index 71c52d68..d21cbcb1 100644 --- a/src/transform/transaction/TransactionTransformer.java +++ b/src/transform/transaction/TransactionTransformer.java @@ -40,6 +40,9 @@ public class TransactionTransformer extends Transformer { case REGISTER_NAME: return RegisterNameTransactionTransformer.fromByteBuffer(byteBuffer); + case UPDATE_NAME: + return UpdateNameTransactionTransformer.fromByteBuffer(byteBuffer); + case CREATE_POLL: return CreatePollTransactionTransformer.fromByteBuffer(byteBuffer); @@ -80,6 +83,9 @@ public class TransactionTransformer extends Transformer { case REGISTER_NAME: return RegisterNameTransactionTransformer.getDataLength(transactionData); + case UPDATE_NAME: + return UpdateNameTransactionTransformer.getDataLength(transactionData); + case CREATE_POLL: return CreatePollTransactionTransformer.getDataLength(transactionData); @@ -120,6 +126,9 @@ public class TransactionTransformer extends Transformer { case REGISTER_NAME: return RegisterNameTransactionTransformer.toBytes(transactionData); + case UPDATE_NAME: + return UpdateNameTransactionTransformer.toBytes(transactionData); + case CREATE_POLL: return CreatePollTransactionTransformer.toBytes(transactionData); @@ -160,6 +169,9 @@ public class TransactionTransformer extends Transformer { case REGISTER_NAME: return RegisterNameTransactionTransformer.toJSON(transactionData); + case UPDATE_NAME: + return UpdateNameTransactionTransformer.toJSON(transactionData); + case CREATE_POLL: return CreatePollTransactionTransformer.toJSON(transactionData); diff --git a/src/transform/transaction/UpdateNameTransactionTransformer.java b/src/transform/transaction/UpdateNameTransactionTransformer.java new file mode 100644 index 00000000..56f88aba --- /dev/null +++ b/src/transform/transaction/UpdateNameTransactionTransformer.java @@ -0,0 +1,117 @@ +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.UpdateNameTransactionData; +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 { + + // 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 DATA_SIZE_LENGTH = INT_LENGTH; + + private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + REGISTRANT_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + DATA_SIZE_LENGTH; + + static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException { + if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH) + throw new TransformationException("Byte data too short for UpdateNameTransaction"); + + long timestamp = byteBuffer.getLong(); + + byte[] reference = new byte[REFERENCE_LENGTH]; + byteBuffer.get(reference); + + byte[] ownerPublicKey = Serialization.deserializePublicKey(byteBuffer); + + String newOwner = Serialization.deserializeRecipient(byteBuffer); + + String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE); + String newData = Serialization.deserializeSizedString(byteBuffer, Name.MAX_DATA_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 UpdateNameTransaction"); + + BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer); + + byte[] signature = new byte[SIGNATURE_LENGTH]; + byteBuffer.get(signature); + + return new UpdateNameTransactionData(ownerPublicKey, newOwner, name, newData, null, fee, timestamp, reference, signature); + } + + public static int getDataLength(TransactionData transactionData) throws TransformationException { + UpdateNameTransactionData updateNameTransactionData = (UpdateNameTransactionData) transactionData; + + int dataLength = TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + updateNameTransactionData.getName().length() + + updateNameTransactionData.getNewData().length(); + + return dataLength; + } + + public static byte[] toBytes(TransactionData transactionData) throws TransformationException { + try { + UpdateNameTransactionData updateNameTransactionData = (UpdateNameTransactionData) transactionData; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + bytes.write(Ints.toByteArray(updateNameTransactionData.getType().value)); + bytes.write(Longs.toByteArray(updateNameTransactionData.getTimestamp())); + bytes.write(updateNameTransactionData.getReference()); + + bytes.write(updateNameTransactionData.getOwnerPublicKey()); + bytes.write(Base58.decode(updateNameTransactionData.getNewOwner())); + Serialization.serializeSizedString(bytes, updateNameTransactionData.getName()); + Serialization.serializeSizedString(bytes, updateNameTransactionData.getNewData()); + + Serialization.serializeBigDecimal(bytes, updateNameTransactionData.getFee()); + + if (updateNameTransactionData.getSignature() != null) + bytes.write(updateNameTransactionData.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 { + UpdateNameTransactionData updateNameTransactionData = (UpdateNameTransactionData) transactionData; + + byte[] ownerPublicKey = updateNameTransactionData.getOwnerPublicKey(); + + json.put("owner", PublicKeyAccount.getAddress(ownerPublicKey)); + json.put("ownerPublicKey", HashCode.fromBytes(ownerPublicKey).toString()); + + json.put("newOwner", updateNameTransactionData.getNewOwner()); + json.put("name", updateNameTransactionData.getName()); + json.put("newData", updateNameTransactionData.getNewData()); + } catch (ClassCastException e) { + throw new TransformationException(e); + } + + return json; + } + +}