getNamesByOwner(String owner, Integer limit, Integer offset, Boolean reverse) throws DataException {
StringBuilder sql = new StringBuilder(512);
- sql.append("SELECT name, data, registered_when, updated_when, reference, is_for_sale, sale_price, creation_group_id FROM Names WHERE owner = ? ORDER BY name");
+ sql.append("SELECT name, reduced_name, data, registered_when, updated_when, "
+ + "is_for_sale, sale_price, reference, creation_group_id FROM Names WHERE owner = ? ORDER BY name");
if (reverse != null && reverse)
sql.append(" DESC");
@@ -169,24 +219,25 @@ public class HSQLDBNameRepository implements NameRepository {
do {
String name = resultSet.getString(1);
- String data = resultSet.getString(2);
- long registered = resultSet.getLong(3);
+ String reducedName = resultSet.getString(2);
+ String data = resultSet.getString(3);
+ long registered = resultSet.getLong(4);
// Special handling for possibly-NULL "updated" column
- Long updated = resultSet.getLong(4);
+ Long updated = resultSet.getLong(5);
if (updated == 0 && resultSet.wasNull())
updated = null;
- byte[] reference = resultSet.getBytes(5);
boolean isForSale = resultSet.getBoolean(6);
Long salePrice = resultSet.getLong(7);
if (salePrice == 0 && resultSet.wasNull())
salePrice = null;
- int creationGroupId = resultSet.getInt(8);
+ byte[] reference = resultSet.getBytes(8);
+ int creationGroupId = resultSet.getInt(9);
- names.add(new NameData(owner, name, data, registered, updated, reference, isForSale, salePrice, creationGroupId));
+ names.add(new NameData(name, reducedName, owner, data, registered, updated, isForSale, salePrice, reference, creationGroupId));
} while (resultSet.next());
return names;
@@ -223,11 +274,11 @@ public class HSQLDBNameRepository implements NameRepository {
public void save(NameData nameData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("Names");
- saveHelper.bind("owner", nameData.getOwner()).bind("name", nameData.getName()).bind("data", nameData.getData())
+ saveHelper.bind("name", nameData.getName()).bind("reduced_name", nameData.getReducedName())
+ .bind("owner", nameData.getOwner()).bind("data", nameData.getData())
.bind("registered_when", nameData.getRegistered()).bind("updated_when", nameData.getUpdated())
- .bind("reference", nameData.getReference())
- .bind("is_for_sale", nameData.getIsForSale()).bind("sale_price", nameData.getSalePrice())
- .bind("creation_group_id", nameData.getCreationGroupId());
+ .bind("is_for_sale", nameData.isForSale()).bind("sale_price", nameData.getSalePrice())
+ .bind("reference", nameData.getReference()).bind("creation_group_id", nameData.getCreationGroupId());
try {
saveHelper.execute(this.repository);
diff --git a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBRegisterNameTransactionRepository.java b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBRegisterNameTransactionRepository.java
index bf13aeb3..82da794f 100644
--- a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBRegisterNameTransactionRepository.java
+++ b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBRegisterNameTransactionRepository.java
@@ -17,16 +17,17 @@ public class HSQLDBRegisterNameTransactionRepository extends HSQLDBTransactionRe
}
TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException {
- String sql = "SELECT name, data FROM RegisterNameTransactions WHERE signature = ?";
+ String sql = "SELECT name, reduced_name, data FROM RegisterNameTransactions WHERE signature = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
if (resultSet == null)
return null;
String name = resultSet.getString(1);
- String data = resultSet.getString(2);
+ String reducedName = resultSet.getString(2);
+ String data = resultSet.getString(3);
- return new RegisterNameTransactionData(baseTransactionData, name, data);
+ return new RegisterNameTransactionData(baseTransactionData, name, data, reducedName);
} catch (SQLException e) {
throw new DataException("Unable to fetch register name transaction from repository", e);
}
@@ -39,7 +40,8 @@ public class HSQLDBRegisterNameTransactionRepository extends HSQLDBTransactionRe
HSQLDBSaver saveHelper = new HSQLDBSaver("RegisterNameTransactions");
saveHelper.bind("signature", registerNameTransactionData.getSignature()).bind("registrant", registerNameTransactionData.getRegistrantPublicKey())
- .bind("name", registerNameTransactionData.getName()).bind("data", registerNameTransactionData.getData());
+ .bind("name", registerNameTransactionData.getName()).bind("data", registerNameTransactionData.getData())
+ .bind("reduced_name", registerNameTransactionData.getReducedName());
try {
saveHelper.execute(this.repository);
diff --git a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBUpdateNameTransactionRepository.java b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBUpdateNameTransactionRepository.java
index 773ed5e4..447ab5c6 100644
--- a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBUpdateNameTransactionRepository.java
+++ b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBUpdateNameTransactionRepository.java
@@ -17,7 +17,7 @@ public class HSQLDBUpdateNameTransactionRepository extends HSQLDBTransactionRepo
}
TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException {
- String sql = "SELECT name, new_name, new_data, name_reference FROM UpdateNameTransactions WHERE signature = ?";
+ String sql = "SELECT name, new_name, new_data, reduced_new_name, name_reference FROM UpdateNameTransactions WHERE signature = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
if (resultSet == null)
@@ -26,9 +26,10 @@ public class HSQLDBUpdateNameTransactionRepository extends HSQLDBTransactionRepo
String name = resultSet.getString(1);
String newName = resultSet.getString(2);
String newData = resultSet.getString(3);
- byte[] nameReference = resultSet.getBytes(4);
+ String reducedNewName = resultSet.getString(4);
+ byte[] nameReference = resultSet.getBytes(5);
- return new UpdateNameTransactionData(baseTransactionData, name, newName, newData, nameReference);
+ return new UpdateNameTransactionData(baseTransactionData, name, newName, newData, reducedNewName, nameReference);
} catch (SQLException e) {
throw new DataException("Unable to fetch update name transaction from repository", e);
}
@@ -42,7 +43,8 @@ public class HSQLDBUpdateNameTransactionRepository extends HSQLDBTransactionRepo
saveHelper.bind("signature", updateNameTransactionData.getSignature()).bind("owner", updateNameTransactionData.getOwnerPublicKey())
.bind("name", updateNameTransactionData.getName()).bind("new_name", updateNameTransactionData.getNewName())
- .bind("new_data", updateNameTransactionData.getNewData()).bind("name_reference", updateNameTransactionData.getNameReference());
+ .bind("new_data", updateNameTransactionData.getNewData()).bind("reduced_new_name", updateNameTransactionData.getReducedNewName())
+ .bind("name_reference", updateNameTransactionData.getNameReference());
try {
saveHelper.execute(this.repository);
diff --git a/src/main/java/org/qortal/transaction/BuyNameTransaction.java b/src/main/java/org/qortal/transaction/BuyNameTransaction.java
index e2be539f..3b58842d 100644
--- a/src/main/java/org/qortal/transaction/BuyNameTransaction.java
+++ b/src/main/java/org/qortal/transaction/BuyNameTransaction.java
@@ -69,7 +69,7 @@ public class BuyNameTransaction extends Transaction {
return ValidationResult.NAME_DOES_NOT_EXIST;
// Check name is currently for sale
- if (!nameData.getIsForSale())
+ if (!nameData.isForSale())
return ValidationResult.NAME_NOT_FOR_SALE;
// Check buyer isn't trying to buy own name
diff --git a/src/main/java/org/qortal/transaction/CancelSellNameTransaction.java b/src/main/java/org/qortal/transaction/CancelSellNameTransaction.java
index 18852c56..da81e4ea 100644
--- a/src/main/java/org/qortal/transaction/CancelSellNameTransaction.java
+++ b/src/main/java/org/qortal/transaction/CancelSellNameTransaction.java
@@ -62,7 +62,7 @@ public class CancelSellNameTransaction extends Transaction {
return ValidationResult.NAME_DOES_NOT_EXIST;
// Check name is currently for sale
- if (!nameData.getIsForSale())
+ if (!nameData.isForSale())
return ValidationResult.NAME_NOT_FOR_SALE;
// Check transaction creator matches name's current owner
diff --git a/src/main/java/org/qortal/transaction/RegisterNameTransaction.java b/src/main/java/org/qortal/transaction/RegisterNameTransaction.java
index 5587d0ec..c0d91f0b 100644
--- a/src/main/java/org/qortal/transaction/RegisterNameTransaction.java
+++ b/src/main/java/org/qortal/transaction/RegisterNameTransaction.java
@@ -11,6 +11,7 @@ import org.qortal.data.transaction.TransactionData;
import org.qortal.naming.Name;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
+import org.qortal.utils.Unicode;
import com.google.common.base.Utf8;
@@ -40,6 +41,15 @@ public class RegisterNameTransaction extends Transaction {
return this.getCreator();
}
+ private synchronized String getReducedName() {
+ if (this.registerNameTransactionData.getReducedName() == null) {
+ String reducedName = Name.reduceName(this.registerNameTransactionData.getName());
+ this.registerNameTransactionData.setReducedName(reducedName);
+ }
+
+ return this.registerNameTransactionData.getReducedName();
+ }
+
// Processing
@Override
@@ -57,21 +67,24 @@ public class RegisterNameTransaction extends Transaction {
if (dataLength > Name.MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH;
- // Check name is lowercase
- if (!name.equals(name.toLowerCase()))
+ // Check name is in normalized form (no leading/trailing whitespace, etc.)
+ if (!name.equals(Unicode.normalize(name)))
return ValidationResult.NAME_NOT_LOWER_CASE;
// Check registrant has enough funds
if (registrant.getConfirmedBalance(Asset.QORT) < this.registerNameTransactionData.getFee())
return ValidationResult.NO_BALANCE;
+ // Fill in missing reduced name. Caller is likely to save this as next step.
+ getReducedName();
+
return ValidationResult.OK;
}
@Override
public ValidationResult isProcessable() throws DataException {
// Check the name isn't already taken
- if (this.repository.getNameRepository().nameExists(this.registerNameTransactionData.getName()))
+ if (this.repository.getNameRepository().reducedNameExists(getReducedName()))
return ValidationResult.NAME_ALREADY_REGISTERED;
// If accounts are only allowed one registered name then check for this
diff --git a/src/main/java/org/qortal/transaction/SellNameTransaction.java b/src/main/java/org/qortal/transaction/SellNameTransaction.java
index 8694e66d..5b77097f 100644
--- a/src/main/java/org/qortal/transaction/SellNameTransaction.java
+++ b/src/main/java/org/qortal/transaction/SellNameTransaction.java
@@ -65,7 +65,7 @@ public class SellNameTransaction extends Transaction {
return ValidationResult.NAME_DOES_NOT_EXIST;
// Check name isn't currently for sale
- if (nameData.getIsForSale())
+ if (nameData.isForSale())
return ValidationResult.NAME_ALREADY_FOR_SALE;
// Check transaction's public key matches name's current owner
diff --git a/src/main/java/org/qortal/transaction/UpdateNameTransaction.java b/src/main/java/org/qortal/transaction/UpdateNameTransaction.java
index 379b4e08..f4cd3020 100644
--- a/src/main/java/org/qortal/transaction/UpdateNameTransaction.java
+++ b/src/main/java/org/qortal/transaction/UpdateNameTransaction.java
@@ -11,6 +11,7 @@ import org.qortal.data.transaction.UpdateNameTransactionData;
import org.qortal.naming.Name;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
+import org.qortal.utils.Unicode;
import com.google.common.base.Utf8;
@@ -40,6 +41,15 @@ public class UpdateNameTransaction extends Transaction {
return this.getCreator();
}
+ private synchronized String getReducedNewName() {
+ if (this.updateNameTransactionData.getReducedNewName() == null) {
+ String reducedNewName = Name.reduceName(this.updateNameTransactionData.getNewName());
+ this.updateNameTransactionData.setReducedNewName(reducedNewName);
+ }
+
+ return this.updateNameTransactionData.getReducedNewName();
+ }
+
// Processing
@Override
@@ -51,8 +61,8 @@ public class UpdateNameTransaction extends Transaction {
if (nameLength < Name.MIN_NAME_SIZE || nameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH;
- // Check name is lowercase
- if (!name.equals(name.toLowerCase()))
+ // Check name is in normalized form (no leading/trailing whitespace, etc.)
+ if (!name.equals(Unicode.normalize(name)))
return ValidationResult.NAME_NOT_LOWER_CASE;
NameData nameData = this.repository.getNameRepository().fromName(name);
@@ -73,8 +83,8 @@ public class UpdateNameTransaction extends Transaction {
if (newNameLength < Name.MIN_NAME_SIZE || newNameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH;
- // Check new name is lowercase
- if (!newName.equals(newName.toLowerCase()))
+ // Check new name is in normalized form (no leading/trailing whitespace, etc.)
+ if (!newName.equals(Unicode.normalize(newName)))
return ValidationResult.NAME_NOT_LOWER_CASE;
}
@@ -89,6 +99,9 @@ public class UpdateNameTransaction extends Transaction {
if (owner.getConfirmedBalance(Asset.QORT) < this.updateNameTransactionData.getFee())
return ValidationResult.NO_BALANCE;
+ // Fill in missing reduced new name. Caller is likely to save this as next step.
+ getReducedNewName();
+
return ValidationResult.OK;
}
@@ -101,7 +114,7 @@ public class UpdateNameTransaction extends Transaction {
return ValidationResult.NAME_DOES_NOT_EXIST;
// Check name isn't currently for sale
- if (nameData.getIsForSale())
+ if (nameData.isForSale())
return ValidationResult.NAME_ALREADY_FOR_SALE;
Account owner = getOwner();
@@ -110,8 +123,9 @@ public class UpdateNameTransaction extends Transaction {
if (!owner.getAddress().equals(nameData.getOwner()))
return ValidationResult.INVALID_NAME_OWNER;
- // Check new name isn't already taken
- if (this.repository.getNameRepository().nameExists(this.updateNameTransactionData.getNewName()))
+ // Check new name isn't already taken, unless it is the same name (this allows for case-adjusting renames)
+ NameData newNameData = this.repository.getNameRepository().fromReducedName(getReducedNewName());
+ if (newNameData != null && !newNameData.getName().equals(nameData.getName()))
return ValidationResult.NAME_ALREADY_REGISTERED;
return ValidationResult.OK;
@@ -129,7 +143,7 @@ public class UpdateNameTransaction extends Transaction {
@Override
public void orphan() throws DataException {
- // Revert name
+ // Revert update
String nameToRevert = this.updateNameTransactionData.getNewName();
if (nameToRevert.isEmpty())
diff --git a/src/main/java/org/qortal/utils/Unicode.java b/src/main/java/org/qortal/utils/Unicode.java
new file mode 100644
index 00000000..8a9092ea
--- /dev/null
+++ b/src/main/java/org/qortal/utils/Unicode.java
@@ -0,0 +1,220 @@
+package org.qortal.utils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UncheckedIOException;
+import java.text.Normalizer;
+import java.text.Normalizer.Form;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.TreeMap;
+
+import com.google.common.base.CharMatcher;
+
+import net.codebox.homoglyph.HomoglyphBuilder;
+
+public abstract class Unicode {
+
+ public static final String NO_BREAK_SPACE = "\u00a0";
+
+ public static final String ZERO_WIDTH_SPACE = "\u200b";
+ public static final String ZERO_WIDTH_NON_JOINER = "\u200c";
+ public static final String ZERO_WIDTH_JOINER = "\u200d";
+ public static final String WORD_JOINER = "\u2060";
+ public static final String ZERO_WIDTH_NO_BREAK_SPACE = "\ufeff";
+
+ public static final CharMatcher ZERO_WIDTH_CHAR_MATCHER = CharMatcher.anyOf(ZERO_WIDTH_SPACE + ZERO_WIDTH_NON_JOINER + ZERO_WIDTH_JOINER + WORD_JOINER + ZERO_WIDTH_NO_BREAK_SPACE);
+
+ private static int[] homoglyphCodePoints;
+ private static int[] reducedCodePoints;
+
+ private static final String CHAR_CODES_FILE = "/char_codes.txt";
+
+ static {
+ buildHomoglyphCodePointArrays();
+ }
+
+ /** Returns string in Unicode canonical normalized form (NFC),
+ * with zero-width spaces/joiners removed,
+ * leading/trailing whitespace trimmed
+ * and all other whitespace blocks collapsed into a single space character.
+ *
+ * Example: [ZWS] means zero-width space
+ *
+ * - " powdered [TAB] to[ZWS]ast " becomes "powdered toast"
+ *
+ *
+ * @see Form#NFKC
+ * @see Unicode#removeZeroWidth(String)
+ * @see CharMatcher#whitespace()
+ * @see CharMatcher#trimAndCollapseFrom(CharSequence, char)
+ */
+ public static String normalize(String input) {
+ String output;
+
+ // Normalize
+ output = Normalizer.normalize(input, Form.NFKC);
+
+ // Remove zero-width code-points, used for rendering
+ output = removeZeroWidth(output);
+
+ // Normalize whitespace
+ output = CharMatcher.whitespace().trimAndCollapseFrom(output, ' ');
+
+ return output;
+ }
+
+ /** Returns string after normalization,
+ * conversion to lowercase (locale insensitive)
+ * and homoglyphs replaced with simpler, reduced codepoints.
+ *
+ * Example:
+ *
+ * - " TΟÁST " becomes "toast"
+ *
+ *
+ * @see Form#NFKC
+ * @see Unicode#removeZeroWidth(String)
+ * @see CharMatcher#whitespace()
+ * @see CharMatcher#trimAndCollapseFrom(CharSequence, char)
+ * @see String#toLowerCase(Locale)
+ * @see Locale#ROOT
+ * @see Unicode#reduceHomoglyphs(String)
+ */
+ public static String sanitize(String input) {
+ String output;
+
+ // Normalize
+ output = Normalizer.normalize(input, Form.NFKD);
+
+ // Remove zero-width code-points, used for rendering
+ output = removeZeroWidth(output);
+
+ // Normalize whitespace
+ output = CharMatcher.whitespace().trimAndCollapseFrom(output, ' ');
+
+ // Remove accents, combining marks
+ output = output.replaceAll("[\\p{M}\\p{C}]", "");
+
+ // Convert to lowercase
+ output = output.toLowerCase(Locale.ROOT);
+
+ // Reduce homoglyphs
+ output = reduceHomoglyphs(output);
+
+ return output;
+ }
+
+ public static String removeZeroWidth(String input) {
+ return ZERO_WIDTH_CHAR_MATCHER.removeFrom(input);
+ }
+
+ public static String reduceHomoglyphs(String input) {
+ CodePoints codePoints = new CodePoints(input);
+ final int length = codePoints.getLength();
+
+ for (int i = 0; i < length; ++i) {
+ int inputCodePoint = codePoints.getValue(i);
+
+ int index = Arrays.binarySearch(homoglyphCodePoints, inputCodePoint);
+ if (index >= 0)
+ codePoints.setValue(i, reducedCodePoints[index]);
+ }
+
+ return codePoints.toString();
+ }
+
+ private static void buildHomoglyphCodePointArrays() {
+ final InputStream is = HomoglyphBuilder.class.getResourceAsStream(CHAR_CODES_FILE);
+
+ if (is == null)
+ throw new MissingResourceException("Unable to read " + CHAR_CODES_FILE, HomoglyphBuilder.class.getName(),
+ CHAR_CODES_FILE);
+
+ final Reader reader = new InputStreamReader(is);
+
+ Map homoglyphReductions = new TreeMap<>();
+
+ try (final BufferedReader bufferedReader = new BufferedReader(reader)) {
+ String line;
+
+ while ((line = bufferedReader.readLine()) != null) {
+ line = line.trim();
+
+ if (line.startsWith("#") || line.length() == 0)
+ continue;
+
+ String[] charCodes = line.split(",");
+
+ // We consider the first charCode to be the 'reduced' form
+ int reducedCodepoint;
+ try {
+ reducedCodepoint = Integer.parseInt(charCodes[0], 16);
+ } catch (NumberFormatException ex) {
+ // ignore badly formatted lines
+ continue;
+ }
+
+ // Map remaining charCodes
+ for (int i = 1; i < charCodes.length; ++i)
+ try {
+ int homoglyphCodepoint = Integer.parseInt(charCodes[i], 16);
+
+ homoglyphReductions.put(homoglyphCodepoint, reducedCodepoint);
+ } catch (NumberFormatException ex) {
+ // ignore
+ }
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ homoglyphCodePoints = homoglyphReductions.keySet().stream().mapToInt(i -> i).toArray();
+ reducedCodePoints = homoglyphReductions.values().stream().mapToInt(i -> i).toArray();
+ }
+
+ private static class CodePoints {
+ private final int[] codepointArray;
+
+ public CodePoints(String text) {
+ final List codepointList = new ArrayList<>();
+
+ int codepoint;
+ for (int offset = 0; offset < text.length(); offset += Character.charCount(codepoint)) {
+ codepoint = text.codePointAt(offset);
+ codepointList.add(codepoint);
+ }
+
+ this.codepointArray = codepointList.stream().mapToInt(i -> i).toArray();
+ }
+
+ public int getValue(int i) {
+ return codepointArray[i];
+ }
+
+ public void setValue(int i, int codepoint) {
+ codepointArray[i] = codepoint;
+ }
+
+ public int getLength() {
+ return codepointArray.length;
+ }
+
+ public String toString() {
+ final StringBuilder sb = new StringBuilder(this.codepointArray.length);
+
+ for (int i = 0; i < this.codepointArray.length; i++)
+ sb.appendCodePoint(this.codepointArray[i]);
+
+ return sb.toString();
+ }
+ }
+
+}
diff --git a/src/test/java/org/qortal/test/UnicodeTests.java b/src/test/java/org/qortal/test/UnicodeTests.java
new file mode 100644
index 00000000..2e0f7968
--- /dev/null
+++ b/src/test/java/org/qortal/test/UnicodeTests.java
@@ -0,0 +1,38 @@
+package org.qortal.test;
+
+import static org.junit.Assert.*;
+import static org.qortal.utils.Unicode.*;
+
+import org.junit.Test;
+import org.qortal.utils.Unicode;
+
+public class UnicodeTests {
+
+ @Test
+ public void testWhitespace() {
+ String input = " " + NO_BREAK_SPACE + "test ";
+
+ String output = Unicode.normalize(input);
+
+ assertEquals("trim & collapse failed", "test", output);
+ }
+
+ @Test
+ public void testCaseComparison() {
+ String input1 = " " + NO_BREAK_SPACE + "test ";
+ String input2 = " " + NO_BREAK_SPACE + "TEST " + ZERO_WIDTH_SPACE;
+
+ assertEquals("strings should match", Unicode.sanitize(input1), Unicode.sanitize(input2));
+ }
+
+ @Test
+ public void testHomoglyph() {
+ String omicron = "\u03bf";
+
+ String input1 = " " + NO_BREAK_SPACE + "toÁst ";
+ String input2 = " " + NO_BREAK_SPACE + "t" + omicron + "ast " + ZERO_WIDTH_SPACE;
+
+ assertEquals("strings should match", Unicode.sanitize(input1), Unicode.sanitize(input2));
+ }
+
+}
diff --git a/src/test/java/org/qortal/test/naming/BuySellTests.java b/src/test/java/org/qortal/test/naming/BuySellTests.java
index 23022613..f0320da5 100644
--- a/src/test/java/org/qortal/test/naming/BuySellTests.java
+++ b/src/test/java/org/qortal/test/naming/BuySellTests.java
@@ -95,7 +95,7 @@ public class BuySellTests extends Common {
// Check name is for sale
nameData = repository.getNameRepository().fromName(name);
- assertTrue(nameData.getIsForSale());
+ assertTrue(nameData.isForSale());
assertEquals("price incorrect", price, nameData.getSalePrice());
// Orphan sell-name
@@ -103,7 +103,7 @@ public class BuySellTests extends Common {
// Check name no longer for sale
nameData = repository.getNameRepository().fromName(name);
- assertFalse(nameData.getIsForSale());
+ assertFalse(nameData.isForSale());
// Not concerned about price
// Re-process sell-name
@@ -111,7 +111,7 @@ public class BuySellTests extends Common {
// Check name is for sale
nameData = repository.getNameRepository().fromName(name);
- assertTrue(nameData.getIsForSale());
+ assertTrue(nameData.isForSale());
assertEquals("price incorrect", price, nameData.getSalePrice());
// Orphan sell-name and register-name
@@ -133,7 +133,7 @@ public class BuySellTests extends Common {
// Check name is for sale
nameData = repository.getNameRepository().fromName(name);
- assertTrue(nameData.getIsForSale());
+ assertTrue(nameData.isForSale());
assertEquals("price incorrect", price, nameData.getSalePrice());
}
@@ -150,7 +150,7 @@ public class BuySellTests extends Common {
// Check name is no longer for sale
nameData = repository.getNameRepository().fromName(name);
- assertFalse(nameData.getIsForSale());
+ assertFalse(nameData.isForSale());
// Not concerned about price
// Orphan cancel sell-name
@@ -158,7 +158,7 @@ public class BuySellTests extends Common {
// Check name is for sale
nameData = repository.getNameRepository().fromName(name);
- assertTrue(nameData.getIsForSale());
+ assertTrue(nameData.isForSale());
assertEquals("price incorrect", price, nameData.getSalePrice());
}
@@ -177,7 +177,7 @@ public class BuySellTests extends Common {
// Check name is sold
nameData = repository.getNameRepository().fromName(name);
- assertFalse(nameData.getIsForSale());
+ assertFalse(nameData.isForSale());
// Not concerned about price
// Orphan buy-name
@@ -185,7 +185,7 @@ public class BuySellTests extends Common {
// Check name is for sale (not sold)
nameData = repository.getNameRepository().fromName(name);
- assertTrue(nameData.getIsForSale());
+ assertTrue(nameData.isForSale());
assertEquals("price incorrect", price, nameData.getSalePrice());
// Re-process buy-name
@@ -193,7 +193,7 @@ public class BuySellTests extends Common {
// Check name is sold
nameData = repository.getNameRepository().fromName(name);
- assertFalse(nameData.getIsForSale());
+ assertFalse(nameData.isForSale());
// Not concerned about price
assertEquals(bob.getAddress(), nameData.getOwner());
@@ -202,7 +202,7 @@ public class BuySellTests extends Common {
// Check name no longer for sale
nameData = repository.getNameRepository().fromName(name);
- assertFalse(nameData.getIsForSale());
+ assertFalse(nameData.isForSale());
// Not concerned about price
assertEquals(alice.getAddress(), nameData.getOwner());
@@ -214,7 +214,7 @@ public class BuySellTests extends Common {
// Check name is sold
nameData = repository.getNameRepository().fromName(name);
- assertFalse(nameData.getIsForSale());
+ assertFalse(nameData.isForSale());
// Not concerned about price
assertEquals(bob.getAddress(), nameData.getOwner());
}
@@ -233,7 +233,7 @@ public class BuySellTests extends Common {
// Check name is for sale
nameData = repository.getNameRepository().fromName(name);
- assertTrue(nameData.getIsForSale());
+ assertTrue(nameData.isForSale());
assertEquals("price incorrect", newPrice, nameData.getSalePrice());
// Orphan sell-name
@@ -241,7 +241,7 @@ public class BuySellTests extends Common {
// Check name no longer for sale
nameData = repository.getNameRepository().fromName(name);
- assertFalse(nameData.getIsForSale());
+ assertFalse(nameData.isForSale());
// Not concerned about price
// Re-process sell-name
@@ -249,7 +249,7 @@ public class BuySellTests extends Common {
// Check name is for sale
nameData = repository.getNameRepository().fromName(name);
- assertTrue(nameData.getIsForSale());
+ assertTrue(nameData.isForSale());
assertEquals("price incorrect", newPrice, nameData.getSalePrice());
// Orphan sell-name and buy-name
@@ -257,7 +257,7 @@ public class BuySellTests extends Common {
// Check name is for sale
nameData = repository.getNameRepository().fromName(name);
- assertTrue(nameData.getIsForSale());
+ assertTrue(nameData.isForSale());
// Note: original sale price
assertEquals("price incorrect", price, nameData.getSalePrice());
assertEquals(alice.getAddress(), nameData.getOwner());
@@ -273,7 +273,7 @@ public class BuySellTests extends Common {
// Check name is for sale
nameData = repository.getNameRepository().fromName(name);
- assertTrue(nameData.getIsForSale());
+ assertTrue(nameData.isForSale());
assertEquals("price incorrect", newPrice, nameData.getSalePrice());
assertEquals(bob.getAddress(), nameData.getOwner());
}
diff --git a/src/test/java/org/qortal/test/naming/MiscTests.java b/src/test/java/org/qortal/test/naming/MiscTests.java
index f124b2dc..0bb750d0 100644
--- a/src/test/java/org/qortal/test/naming/MiscTests.java
+++ b/src/test/java/org/qortal/test/naming/MiscTests.java
@@ -13,7 +13,6 @@ import org.qortal.data.transaction.UpdateNameTransactionData;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager;
-import org.qortal.test.common.BlockUtils;
import org.qortal.test.common.Common;
import org.qortal.test.common.TransactionUtils;
import org.qortal.test.common.transaction.TestTransaction;
@@ -32,9 +31,10 @@ public class MiscTests extends Common {
try (final Repository repository = RepositoryManager.getRepository()) {
// Register-name
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
- String name = "test-name";
+ String name = "initial-name";
+ String data = "initial-data";
- RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "{}");
+ RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
TransactionUtils.signAndMint(repository, transactionData, alice);
List recentNames = repository.getNameRepository().getRecentNames(0L);
@@ -44,124 +44,6 @@ public class MiscTests extends Common {
}
}
- @Test
- public void testUpdateName() throws DataException {
- try (final Repository repository = RepositoryManager.getRepository()) {
- // Register-name
- PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
- String name = "test-name";
-
- TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "{}");
- TransactionUtils.signAndMint(repository, transactionData, alice);
-
- String newName = "new-name";
- String newData = "";
- transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), name, newName, newData);
- TransactionUtils.signAndMint(repository, transactionData, alice);
-
- // Check old name no longer exists
- assertFalse(repository.getNameRepository().nameExists(name));
-
- // Check new name exists
- assertTrue(repository.getNameRepository().nameExists(newName));
-
- // orphan and recheck
- BlockUtils.orphanLastBlock(repository);
-
- // Check new name no longer exists
- assertFalse(repository.getNameRepository().nameExists(newName));
-
- // Check old name exists again
- assertTrue(repository.getNameRepository().nameExists(name));
- }
- }
-
- // Test that reverting using previous UPDATE_NAME works as expected
- @Test
- public void testDoubleUpdateName() throws DataException {
- try (final Repository repository = RepositoryManager.getRepository()) {
- // Register-name
- PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
- String name = "test-name";
-
- TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "{}");
- TransactionUtils.signAndMint(repository, transactionData, alice);
-
- String newName = "new-name";
- String newData = "";
- transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), name, newName, newData);
- TransactionUtils.signAndMint(repository, transactionData, alice);
-
- // Check old name no longer exists
- assertFalse(repository.getNameRepository().nameExists(name));
-
- // Check new name exists
- assertTrue(repository.getNameRepository().nameExists(newName));
-
- String newestName = "newest-name";
- String newestData = "abc";
- transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), newName, newestName, newestData);
- TransactionUtils.signAndMint(repository, transactionData, alice);
-
- // Check previous name no longer exists
- assertFalse(repository.getNameRepository().nameExists(newName));
-
- // Check newest name exists
- assertTrue(repository.getNameRepository().nameExists(newestName));
-
- // orphan and recheck
- BlockUtils.orphanLastBlock(repository);
-
- // Check newest name no longer exists
- assertFalse(repository.getNameRepository().nameExists(newestName));
-
- // Check previous name exists again
- assertTrue(repository.getNameRepository().nameExists(newName));
-
- // orphan and recheck
- BlockUtils.orphanLastBlock(repository);
-
- // Check new name no longer exists
- assertFalse(repository.getNameRepository().nameExists(newName));
-
- // Check original name exists again
- assertTrue(repository.getNameRepository().nameExists(name));
- }
- }
-
- @Test
- public void testUpdateData() throws DataException {
- try (final Repository repository = RepositoryManager.getRepository()) {
- // Register-name
- PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
- String name = "test-name";
- String data = "{}";
-
- TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
- TransactionUtils.signAndMint(repository, transactionData, alice);
-
- String newName = "";
- String newData = "new-data";
- transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), name, newName, newData);
- TransactionUtils.signAndMint(repository, transactionData, alice);
-
- // Check name still exists
- assertTrue(repository.getNameRepository().nameExists(name));
-
- // Check data is correct
- assertEquals(newData, repository.getNameRepository().fromName(name).getData());
-
- // orphan and recheck
- BlockUtils.orphanLastBlock(repository);
-
- // Check name still exists
- assertTrue(repository.getNameRepository().nameExists(name));
-
- // Check old data restored
- assertEquals(data, repository.getNameRepository().fromName(name).getData());
- }
- }
-
// test trying to register same name twice
@Test
public void testDuplicateRegisterName() throws DataException {
@@ -169,12 +51,14 @@ public class MiscTests extends Common {
// Register-name
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
String name = "test-name";
+ String data = "{}";
- RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "{}");
+ RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
TransactionUtils.signAndMint(repository, transactionData, alice);
// duplicate
- transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "{}");
+ String duplicateName = "TEST-nÁme";
+ transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), duplicateName, data);
Transaction transaction = Transaction.fromData(repository, transactionData);
transaction.sign(alice);
@@ -190,17 +74,20 @@ public class MiscTests extends Common {
// Register-name
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
String name = "test-name";
+ String data = "{}";
- TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "{}");
+ TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
TransactionUtils.signAndMint(repository, transactionData, alice);
- String newName = "new-name";
- String newData = "";
- transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), newName, newData);
+ // Register another name that we will later attempt to rename to first name (above)
+ String otherName = "new-name";
+ String otherData = "";
+ transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), otherName, otherData);
TransactionUtils.signAndMint(repository, transactionData, alice);
// we shouldn't be able to update name to existing name
- transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), newName, name, newData);
+ String duplicateName = "TEST-nÁme";
+ transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), otherName, duplicateName, otherData);
Transaction transaction = Transaction.fromData(repository, transactionData);
transaction.sign(alice);
diff --git a/src/test/java/org/qortal/test/naming/UpdateTests.java b/src/test/java/org/qortal/test/naming/UpdateTests.java
new file mode 100644
index 00000000..ffbf7177
--- /dev/null
+++ b/src/test/java/org/qortal/test/naming/UpdateTests.java
@@ -0,0 +1,334 @@
+package org.qortal.test.naming;
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.qortal.account.PrivateKeyAccount;
+import org.qortal.data.transaction.RegisterNameTransactionData;
+import org.qortal.data.transaction.TransactionData;
+import org.qortal.data.transaction.UpdateNameTransactionData;
+import org.qortal.repository.DataException;
+import org.qortal.repository.Repository;
+import org.qortal.repository.RepositoryManager;
+import org.qortal.test.common.BlockUtils;
+import org.qortal.test.common.Common;
+import org.qortal.test.common.TransactionUtils;
+import org.qortal.test.common.transaction.TestTransaction;
+
+public class UpdateTests extends Common {
+
+ @Before
+ public void beforeTest() throws DataException {
+ Common.useDefaultSettings();
+ }
+
+ @Test
+ public void testUpdateName() throws DataException {
+ try (final Repository repository = RepositoryManager.getRepository()) {
+ // Register-name
+ PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
+ String initialName = "initial-name";
+ String initialData = "initial-data";
+
+ TransactionData initialTransactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
+ TransactionUtils.signAndMint(repository, initialTransactionData, alice);
+
+ String newName = "new-name";
+ String newData = "";
+ TransactionData updateTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, newName, newData);
+ TransactionUtils.signAndMint(repository, updateTransactionData, alice);
+
+ // Check old name no longer exists
+ assertFalse(repository.getNameRepository().nameExists(initialName));
+
+ // Check new name exists
+ assertTrue(repository.getNameRepository().nameExists(newName));
+
+ // Check updated timestamp is correct
+ assertEquals((Long) updateTransactionData.getTimestamp(), repository.getNameRepository().fromName(newName).getUpdated());
+
+ // orphan and recheck
+ BlockUtils.orphanLastBlock(repository);
+
+ // Check new name no longer exists
+ assertFalse(repository.getNameRepository().nameExists(newName));
+
+ // Check old name exists again
+ assertTrue(repository.getNameRepository().nameExists(initialName));
+
+ // Check updated timestamp is empty
+ assertNull(repository.getNameRepository().fromName(initialName).getUpdated());
+ }
+ }
+
+ @Test
+ public void testUpdateNameSameOwner() throws DataException {
+ try (final Repository repository = RepositoryManager.getRepository()) {
+ // Register-name
+ PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
+ String initialName = "initial-name";
+ String initialData = "initial-data";
+
+ TransactionData initialTransactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
+ TransactionUtils.signAndMint(repository, initialTransactionData, alice);
+
+ String newName = "Initial-Name";
+ String newData = "";
+ TransactionData updateTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, newName, newData);
+ TransactionUtils.signAndMint(repository, updateTransactionData, alice);
+
+ // Check old name no longer exists
+ assertFalse(repository.getNameRepository().nameExists(initialName));
+
+ // Check new name exists
+ assertTrue(repository.getNameRepository().nameExists(newName));
+
+ // Check updated timestamp is correct
+ assertEquals((Long) updateTransactionData.getTimestamp(), repository.getNameRepository().fromName(newName).getUpdated());
+
+ // orphan and recheck
+ BlockUtils.orphanLastBlock(repository);
+
+ // Check new name no longer exists
+ assertFalse(repository.getNameRepository().nameExists(newName));
+
+ // Check old name exists again
+ assertTrue(repository.getNameRepository().nameExists(initialName));
+
+ // Check updated timestamp is empty
+ assertNull(repository.getNameRepository().fromName(initialName).getUpdated());
+ }
+ }
+
+ // Test that reverting using previous UPDATE_NAME works as expected
+ @Test
+ public void testDoubleUpdateName() throws DataException {
+ try (final Repository repository = RepositoryManager.getRepository()) {
+ // Register-name
+ PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
+ String initialName = "initial-name";
+ String initialData = "initial-data";
+
+ TransactionData initialTransactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
+ TransactionUtils.signAndMint(repository, initialTransactionData, alice);
+
+ String middleName = "middle-name";
+ String middleData = "";
+ TransactionData middleTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, middleName, middleData);
+ TransactionUtils.signAndMint(repository, middleTransactionData, alice);
+
+ // Check old name no longer exists
+ assertFalse(repository.getNameRepository().nameExists(initialName));
+
+ // Check new name exists
+ assertTrue(repository.getNameRepository().nameExists(middleName));
+
+ String newestName = "newest-name";
+ String newestData = "newest-data";
+ TransactionData newestTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), middleName, newestName, newestData);
+ TransactionUtils.signAndMint(repository, newestTransactionData, alice);
+
+ // Check previous name no longer exists
+ assertFalse(repository.getNameRepository().nameExists(middleName));
+
+ // Check newest name exists
+ assertTrue(repository.getNameRepository().nameExists(newestName));
+
+ // Check updated timestamp is correct
+ assertEquals((Long) newestTransactionData.getTimestamp(), repository.getNameRepository().fromName(newestName).getUpdated());
+
+ // orphan and recheck
+ BlockUtils.orphanLastBlock(repository);
+
+ // Check newest name no longer exists
+ assertFalse(repository.getNameRepository().nameExists(newestName));
+
+ // Check previous name exists again
+ assertTrue(repository.getNameRepository().nameExists(middleName));
+
+ // Check updated timestamp is correct
+ assertEquals((Long) middleTransactionData.getTimestamp(), repository.getNameRepository().fromName(middleName).getUpdated());
+
+ // orphan and recheck
+ BlockUtils.orphanLastBlock(repository);
+
+ // Check new name no longer exists
+ assertFalse(repository.getNameRepository().nameExists(middleName));
+
+ // Check original name exists again
+ assertTrue(repository.getNameRepository().nameExists(initialName));
+
+ // Check updated timestamp is empty
+ assertNull(repository.getNameRepository().fromName(initialName).getUpdated());
+ }
+ }
+
+ // Test that reverting using previous UPDATE_NAME works as expected
+ @Test
+ public void testIntermediateUpdateName() throws DataException {
+ try (final Repository repository = RepositoryManager.getRepository()) {
+ // Register-name
+ PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
+ String initialName = "initial-name";
+ String initialData = "initial-data";
+
+ TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
+ TransactionUtils.signAndMint(repository, transactionData, alice);
+
+ // Don't update name, but update data.
+ // This tests whether reverting a future update/sale can find the correct previous name
+ String middleName = "";
+ String middleData = "middle-data";
+ transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, middleName, middleData);
+ TransactionUtils.signAndMint(repository, transactionData, alice);
+
+ // Check old name still exists
+ assertTrue(repository.getNameRepository().nameExists(initialName));
+
+ String newestName = "newest-name";
+ String newestData = "newest-data";
+ transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, newestName, newestData);
+ TransactionUtils.signAndMint(repository, transactionData, alice);
+
+ // Check previous name no longer exists
+ assertFalse(repository.getNameRepository().nameExists(initialName));
+
+ // Check newest name exists
+ assertTrue(repository.getNameRepository().nameExists(newestName));
+
+ // orphan and recheck
+ BlockUtils.orphanLastBlock(repository);
+
+ // Check original name exists again
+ assertTrue(repository.getNameRepository().nameExists(initialName));
+
+ // orphan and recheck
+ BlockUtils.orphanLastBlock(repository);
+
+ // Check original name still exists
+ assertTrue(repository.getNameRepository().nameExists(initialName));
+ }
+ }
+
+ @Test
+ public void testUpdateData() throws DataException {
+ try (final Repository repository = RepositoryManager.getRepository()) {
+ // Register-name
+ PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
+ String initialName = "initial-name";
+ String initialData = "initial-data";
+
+ TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
+ TransactionUtils.signAndMint(repository, transactionData, alice);
+
+ String newName = "";
+ String newData = "new-data";
+ transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, newName, newData);
+ TransactionUtils.signAndMint(repository, transactionData, alice);
+
+ // Check name still exists
+ assertTrue(repository.getNameRepository().nameExists(initialName));
+
+ // Check data is correct
+ assertEquals(newData, repository.getNameRepository().fromName(initialName).getData());
+
+ // orphan and recheck
+ BlockUtils.orphanLastBlock(repository);
+
+ // Check name still exists
+ assertTrue(repository.getNameRepository().nameExists(initialName));
+
+ // Check old data restored
+ assertEquals(initialData, repository.getNameRepository().fromName(initialName).getData());
+ }
+ }
+
+ // Test that reverting using previous UPDATE_NAME works as expected
+ @Test
+ public void testDoubleUpdateData() throws DataException {
+ try (final Repository repository = RepositoryManager.getRepository()) {
+ // Register-name
+ PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
+ String initialName = "initial-name";
+ String initialData = "initial-data";
+
+ TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
+ TransactionUtils.signAndMint(repository, transactionData, alice);
+
+ // Update data
+ String middleName = "middle-name";
+ String middleData = "middle-data";
+ transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, middleName, middleData);
+ TransactionUtils.signAndMint(repository, transactionData, alice);
+
+ // Check data is correct
+ assertEquals(middleData, repository.getNameRepository().fromName(middleName).getData());
+
+ String newestName = "newest-name";
+ String newestData = "newest-data";
+ transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), middleName, newestName, newestData);
+ TransactionUtils.signAndMint(repository, transactionData, alice);
+
+ // Check data is correct
+ assertEquals(newestData, repository.getNameRepository().fromName(newestName).getData());
+
+ // orphan and recheck
+ BlockUtils.orphanLastBlock(repository);
+
+ // Check data is correct
+ assertEquals(middleData, repository.getNameRepository().fromName(middleName).getData());
+
+ // orphan and recheck
+ BlockUtils.orphanLastBlock(repository);
+
+ // Check data is correct
+ assertEquals(initialData, repository.getNameRepository().fromName(initialName).getData());
+ }
+ }
+
+ // Test that reverting using previous UPDATE_NAME works as expected
+ @Test
+ public void testIntermediateUpdateData() throws DataException {
+ try (final Repository repository = RepositoryManager.getRepository()) {
+ // Register-name
+ PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
+ String initialName = "initial-name";
+ String initialData = "initial-data";
+
+ TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, initialData);
+ TransactionUtils.signAndMint(repository, transactionData, alice);
+
+ // Don't update data, but update name.
+ // This tests whether reverting a future update/sale can find the correct previous data
+ String middleName = "middle-name";
+ String middleData = "";
+ transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, middleName, middleData);
+ TransactionUtils.signAndMint(repository, transactionData, alice);
+
+ // Check data is correct
+ assertEquals(initialData, repository.getNameRepository().fromName(middleName).getData());
+
+ String newestName = "newest-name";
+ String newestData = "newest-data";
+ transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), middleName, newestName, newestData);
+ TransactionUtils.signAndMint(repository, transactionData, alice);
+
+ // Check data is correct
+ assertEquals(newestData, repository.getNameRepository().fromName(newestName).getData());
+
+ // orphan and recheck
+ BlockUtils.orphanLastBlock(repository);
+
+ // Check data is correct
+ assertEquals(initialData, repository.getNameRepository().fromName(middleName).getData());
+
+ // orphan and recheck
+ BlockUtils.orphanLastBlock(repository);
+
+ // Check data is correct
+ assertEquals(initialData, repository.getNameRepository().fromName(initialName).getData());
+ }
+ }
+
+}