From 88fe3b0af661ac242487056effc93b6d67837623 Mon Sep 17 00:00:00 2001 From: kennycud Date: Fri, 23 May 2025 17:49:26 -0700 Subject: [PATCH] primary names implementation --- src/main/java/org/qortal/account/Account.java | 142 ++++++++++++++ .../qortal/api/resource/NamesResource.java | 40 ++++ src/main/java/org/qortal/block/Block.java | 4 + .../org/qortal/block/PrimaryNamesBlock.java | 47 +++++ .../java/org/qortal/data/naming/NameData.java | 5 + .../org/qortal/repository/NameRepository.java | 10 +- .../hsqldb/HSQLDBChatRepository.java | 22 ++- .../hsqldb/HSQLDBDatabaseUpdates.java | 6 + .../hsqldb/HSQLDBNameRepository.java | 50 +++++ .../transaction/BuyNameTransaction.java | 40 +++- .../transaction/RegisterNameTransaction.java | 22 +++ .../transaction/UpdateNameTransaction.java | 27 +++ .../org/qortal/test/naming/BuySellTests.java | 63 +++++++ .../qortal/test/naming/IntegrityTests.java | 12 +- .../org/qortal/test/naming/MiscTests.java | 173 +++++++++++++++++- .../org/qortal/test/naming/UpdateTests.java | 121 ++++++++++++ 16 files changed, 778 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/qortal/block/PrimaryNamesBlock.java diff --git a/src/main/java/org/qortal/account/Account.java b/src/main/java/org/qortal/account/Account.java index 722e70da..f741b166 100644 --- a/src/main/java/org/qortal/account/Account.java +++ b/src/main/java/org/qortal/account/Account.java @@ -2,12 +2,14 @@ package org.qortal.account; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.qortal.api.resource.TransactionsResource; import org.qortal.block.BlockChain; import org.qortal.controller.LiteNode; import org.qortal.data.account.AccountBalanceData; import org.qortal.data.account.AccountData; import org.qortal.data.account.RewardShareData; import org.qortal.data.naming.NameData; +import org.qortal.data.transaction.TransactionData; import org.qortal.repository.DataException; import org.qortal.repository.GroupRepository; import org.qortal.repository.NameRepository; @@ -19,7 +21,11 @@ import org.qortal.utils.Groups; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Optional; import static org.qortal.utils.Amounts.prettyAmount; @@ -361,6 +367,142 @@ public class Account { return accountData.getLevel(); } + /** + * Get Primary Name + * + * @return the primary name for this address if present, otherwise empty + * + * @throws DataException + */ + public Optional getPrimaryName() throws DataException { + + return this.repository.getNameRepository().getPrimaryName(this.address); + } + + /** + * Remove Primary Name + * + * @throws DataException + */ + public void removePrimaryName() throws DataException { + this.repository.getNameRepository().removePrimaryName(this.address); + } + + /** + * Reset Primary Name + * + * Set primary name based on the names (and their history) this account owns. + * + * @param confirmationStatus the status of the transactions for the determining the primary name + * + * @return the primary name, empty if their isn't one + * + * @throws DataException + */ + public Optional resetPrimaryName(TransactionsResource.ConfirmationStatus confirmationStatus) throws DataException { + Optional primaryName = determinePrimaryName(confirmationStatus); + + if(primaryName.isPresent()) { + return setPrimaryName(primaryName.get()); + } + else { + return primaryName; + } + } + + /** + * Determine Primary Name + * + * Determine primary name based on a list of registered names. + * + * @param confirmationStatus the status of the transactions for this determination + * + * @return the primary name, empty if there is no primary name + * + * @throws DataException + */ + public Optional determinePrimaryName(TransactionsResource.ConfirmationStatus confirmationStatus) throws DataException { + + // all registered names for the owner + List names = this.repository.getNameRepository().getNamesByOwner(this.address); + + Optional primaryName; + + // if no registered names, the no primary name possible + if (names.isEmpty()) { + primaryName = Optional.empty(); + } + // if names + else { + // if one name, then that is the primary name + if (names.size() == 1) { + primaryName = Optional.of( names.get(0).getName() ); + } + // if more than one name, then seek the earliest name acquisition that was never released + else { + Map txByName = new HashMap<>(names.size()); + + // for each name, get the latest transaction + for (NameData nameData : names) { + + // since the name is currently registered to the owner, + // we assume the latest transaction involving this name was the transaction that the acquired + // name through registration, purchase or update + Optional latestTransaction + = this.repository + .getTransactionRepository() + .getTransactionsInvolvingName( + nameData.getName(), + confirmationStatus + ) + .stream() + .sorted(Comparator.comparing( + TransactionData::getTimestamp).reversed() + ) + .findFirst(); // first is the last, since it was reversed + + // if there is a latest transaction, expected for all registered names + if (latestTransaction.isPresent()) { + txByName.put(nameData.getName(), latestTransaction.get()); + } + // if there is no latest transaction, then + else { + LOGGER.warn("No matching transaction for name: " + nameData.getName()); + } + } + + // get the first name aqcuistion for this address + Optional> firstNameEntry + = txByName.entrySet().stream().sorted(Comparator.comparing(entry -> entry.getValue().getTimestamp())).findFirst(); + + // if their is a name acquisition, then the first one is the primary name + if (firstNameEntry.isPresent()) { + primaryName = Optional.of( firstNameEntry.get().getKey() ); + } + // if there is no nameacquistion, then there is no primary name + else { + primaryName = Optional.empty(); + } + } + } + return primaryName; + } + + /** + * Set Primary Name + * + * @param primaryName the primary to set to this address + * + * @return the primary name if successful, empty if unsuccessful + * + * @throws DataException + */ + public Optional setPrimaryName( String primaryName ) throws DataException { + int changed = this.repository.getNameRepository().setPrimaryName(this.address, primaryName); + + return changed > 0 ? Optional.of(primaryName) : Optional.empty(); + } + /** * Returns reward-share minting address, or unknown if reward-share does not exist. * diff --git a/src/main/java/org/qortal/api/resource/NamesResource.java b/src/main/java/org/qortal/api/resource/NamesResource.java index c7d4a425..a28615fd 100644 --- a/src/main/java/org/qortal/api/resource/NamesResource.java +++ b/src/main/java/org/qortal/api/resource/NamesResource.java @@ -33,6 +33,7 @@ import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; @Path("/names") @@ -104,6 +105,45 @@ public class NamesResource { } } + @GET + @Path("/primary/{address}") + @Operation( + summary = "primary name owned by address", + responses = { + @ApiResponse( + description = "registered primary name info", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = NameSummary.class) + ) + ) + } + ) + @ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE, ApiError.UNAUTHORIZED}) + public NameSummary getPrimaryNameByAddress(@PathParam("address") String address) { + if (!Crypto.isValidAddress(address)) + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS); + + try (final Repository repository = RepositoryManager.getRepository()) { + + if (Settings.getInstance().isLite()) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.UNAUTHORIZED); + } + else { + Optional primaryName = repository.getNameRepository().getPrimaryName(address); + + if(primaryName.isPresent()) { + return new NameSummary(new NameData(primaryName.get(), address)); + } + else { + return new NameSummary((new NameData(null, address))); + } + } + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); + } + } + @GET @Path("/{name}") @Operation( diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index 67e6dd43..753b5dfa 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -1640,6 +1640,8 @@ public class Block { SelfSponsorshipAlgoV2Block.processAccountPenalties(this); } else if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV3Height()) { SelfSponsorshipAlgoV3Block.processAccountPenalties(this); + } else if (this.blockData.getHeight() == BlockChain.getInstance().getMultipleNamesPerAccountHeight()) { + PrimaryNamesBlock.processNames(this.repository); } } } @@ -1952,6 +1954,8 @@ public class Block { SelfSponsorshipAlgoV2Block.orphanAccountPenalties(this); } else if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV3Height()) { SelfSponsorshipAlgoV3Block.orphanAccountPenalties(this); + } else if (this.blockData.getHeight() == BlockChain.getInstance().getMultipleNamesPerAccountHeight()) { + PrimaryNamesBlock.orphanNames( this.repository ); } } diff --git a/src/main/java/org/qortal/block/PrimaryNamesBlock.java b/src/main/java/org/qortal/block/PrimaryNamesBlock.java new file mode 100644 index 00000000..3dd21344 --- /dev/null +++ b/src/main/java/org/qortal/block/PrimaryNamesBlock.java @@ -0,0 +1,47 @@ +package org.qortal.block; + +import org.qortal.account.Account; +import org.qortal.api.resource.TransactionsResource; +import org.qortal.data.naming.NameData; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; + +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Class PrimaryNamesBlock + */ +public class PrimaryNamesBlock { + + /** + * Process Primary Names + * + * @param repository + * @throws DataException + */ + public static void processNames(Repository repository) throws DataException { + + Set addressesWithNames + = repository.getNameRepository().getAllNames().stream() + .map(NameData::getOwner).collect(Collectors.toSet()); + + // for each address with a name, set primary name to the address + for( String address : addressesWithNames ) { + + Account account = new Account(repository, address); + account.resetPrimaryName(TransactionsResource.ConfirmationStatus.CONFIRMED); + } + } + + /** + * Orphan the Primary Names Block + * + * @param repository + * @throws DataException + */ + public static void orphanNames(Repository repository) throws DataException { + + repository.getNameRepository().clearPrimaryNames(); + } +} \ No newline at end of file diff --git a/src/main/java/org/qortal/data/naming/NameData.java b/src/main/java/org/qortal/data/naming/NameData.java index 16e490a2..c76b7d48 100644 --- a/src/main/java/org/qortal/data/naming/NameData.java +++ b/src/main/java/org/qortal/data/naming/NameData.java @@ -67,6 +67,11 @@ public class NameData { this(name, reducedName, owner, data, registered, null, false, null, reference, creationGroupId); } + // Typically used for name summsry + public NameData(String name, String owner) { + this(name, null, owner, null, 0L, null, false, null, null, 0); + } + // Getters / setters public String getName() { diff --git a/src/main/java/org/qortal/repository/NameRepository.java b/src/main/java/org/qortal/repository/NameRepository.java index c49d5d18..c61bb96f 100644 --- a/src/main/java/org/qortal/repository/NameRepository.java +++ b/src/main/java/org/qortal/repository/NameRepository.java @@ -3,6 +3,7 @@ package org.qortal.repository; import org.qortal.data.naming.NameData; import java.util.List; +import java.util.Optional; public interface NameRepository { @@ -34,10 +35,17 @@ public interface NameRepository { return getNamesByOwner(address, null, null, null); } + public int setPrimaryName(String address, String primaryName) throws DataException; + + public void removePrimaryName(String address) throws DataException; + + public Optional getPrimaryName(String address) throws DataException; + + public int clearPrimaryNames() throws DataException; + public List getRecentNames(long startTimestamp) throws DataException; public void save(NameData nameData) throws DataException; public void delete(String name) throws DataException; - } diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java index 80865739..48262dee 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java @@ -1,5 +1,8 @@ package org.qortal.repository.hsqldb; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.qortal.block.BlockChain; import org.qortal.data.chat.ActiveChats; import org.qortal.data.chat.ActiveChats.DirectChat; import org.qortal.data.chat.ActiveChats.GroupChat; @@ -18,6 +21,8 @@ import static org.qortal.data.chat.ChatMessage.Encoding; public class HSQLDBChatRepository implements ChatRepository { + private static final Logger LOGGER = LogManager.getLogger(HSQLDBChatRepository.class); + protected HSQLDBRepository repository; public HSQLDBChatRepository(HSQLDBRepository repository) { @@ -142,10 +147,23 @@ public class HSQLDBChatRepository implements ChatRepository { @Override public ChatMessage toChatMessage(ChatTransactionData chatTransactionData, Encoding encoding) throws DataException { + + String tableName; + + // if the PrimaryTable is available, then use it + if( this.repository.getBlockRepository().getBlockchainHeight() > BlockChain.getInstance().getMultipleNamesPerAccountHeight()) { + LOGGER.info("using PrimaryNames for chat transactions"); + tableName = "PrimaryNames"; + } + else { + LOGGER.info("using Names for chat transactions"); + tableName = "Names"; + } + String sql = "SELECT SenderNames.name, RecipientNames.name " + "FROM ChatTransactions " - + "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender " - + "LEFT OUTER JOIN Names AS RecipientNames ON RecipientNames.owner = recipient " + + "LEFT OUTER JOIN " + tableName + " AS SenderNames ON SenderNames.owner = sender " + + "LEFT OUTER JOIN " + tableName + " AS RecipientNames ON RecipientNames.owner = recipient " + "WHERE signature = ?"; try (ResultSet resultSet = this.repository.checkedExecute(sql, chatTransactionData.getSignature())) { diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java index ca55f3a8..69a06b6a 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -1053,6 +1053,12 @@ public class HSQLDBDatabaseUpdates { stmt.execute("UPDATE Accounts SET blocks_minted_penalty = -5000000 WHERE blocks_minted_penalty < 0"); break; + case 50: + // Primary name for a Qortal Address, 0-1 for any address + stmt.execute("CREATE TABLE PrimaryNames (owner QortalAddress, name RegisteredName, " + + "PRIMARY KEY (owner), FOREIGN KEY (name) REFERENCES Names (name) ON DELETE CASCADE)"); + break; + default: // nothing to do return false; diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBNameRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBNameRepository.java index 7bcdebda..fba1e83d 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBNameRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBNameRepository.java @@ -8,6 +8,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.Optional; public class HSQLDBNameRepository implements NameRepository { @@ -333,6 +334,55 @@ public class HSQLDBNameRepository implements NameRepository { } } + @Override + public void removePrimaryName(String address) throws DataException { + try { + this.repository.delete("PrimaryNames", "owner = ?", address); + } catch (SQLException e) { + throw new DataException("Unable to delete primary name from repository", e); + } + } + + @Override + public Optional getPrimaryName(String address) throws DataException { + String sql = "SELECT name FROM PrimaryNames WHERE owner = ?"; + + List names = new ArrayList<>(); + + try (ResultSet resultSet = this.repository.checkedExecute(sql, address)) { + if (resultSet == null) + return Optional.empty(); + + String name = resultSet.getString(1); + + return Optional.of(name); + } catch (SQLException e) { + throw new DataException("Unable to fetch recent names from repository", e); + } + } + + @Override + public int setPrimaryName(String address, String primaryName) throws DataException { + + String sql = "INSERT INTO PrimaryNames (owner, name) VALUES (?, ?) ON DUPLICATE KEY UPDATE name = ?"; + + try{ + return this.repository.executeCheckedUpdate(sql, address, primaryName, primaryName); + } catch (SQLException e) { + throw new DataException("Unable to set primary name", e); + } + } + + @Override + public int clearPrimaryNames() throws DataException { + + try { + return this.repository.delete("PrimaryNames"); + } catch (SQLException e) { + throw new DataException("Unable to clear primary names from repository", e); + } + } + @Override public void save(NameData nameData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("Names"); diff --git a/src/main/java/org/qortal/transaction/BuyNameTransaction.java b/src/main/java/org/qortal/transaction/BuyNameTransaction.java index b7ca1d93..6c390085 100644 --- a/src/main/java/org/qortal/transaction/BuyNameTransaction.java +++ b/src/main/java/org/qortal/transaction/BuyNameTransaction.java @@ -16,6 +16,7 @@ import org.qortal.utils.Unicode; import java.util.Collections; import java.util.List; +import java.util.Optional; public class BuyNameTransaction extends Transaction { @@ -117,6 +118,25 @@ public class BuyNameTransaction extends Transaction { // Save transaction with updated "name reference" pointing to previous transaction that changed name this.repository.getTransactionRepository().save(this.buyNameTransactionData); + + // if multiple names feature is activated, then check the buyer and seller's primary name status + if( this.repository.getBlockRepository().getBlockchainHeight() > BlockChain.getInstance().getMultipleNamesPerAccountHeight()) { + + Account seller = new Account(this.repository, this.buyNameTransactionData.getSeller()); + Optional sellerPrimaryName = seller.getPrimaryName(); + + // if the seller sold their primary name, then remove their primary name + if (sellerPrimaryName.isPresent() && sellerPrimaryName.get().equals(buyNameTransactionData.getName())) { + seller.removePrimaryName(); + } + + Account buyer = new Account(this.repository, this.getBuyer().getAddress()); + + // if the buyer had no primary name, then set the primary name to the name bought + if( buyer.getPrimaryName().isEmpty() ) { + buyer.setPrimaryName(this.buyNameTransactionData.getName()); + } + } } @Override @@ -127,6 +147,24 @@ public class BuyNameTransaction extends Transaction { // Save this transaction, with previous "name reference" this.repository.getTransactionRepository().save(this.buyNameTransactionData); - } + // if multiple names feature is activated, then check the buyer and seller's primary name status + if( this.repository.getBlockRepository().getBlockchainHeight() > BlockChain.getInstance().getMultipleNamesPerAccountHeight()) { + + Account seller = new Account(this.repository, this.buyNameTransactionData.getSeller()); + + // if the seller lost their primary name, then set their primary name back + if (seller.getPrimaryName().isEmpty()) { + seller.setPrimaryName(this.buyNameTransactionData.getName()); + } + + Account buyer = new Account(this.repository, this.getBuyer().getAddress()); + Optional buyerPrimaryName = buyer.getPrimaryName(); + + // if the buyer bought their primary, then remove it + if( buyerPrimaryName.isPresent() && this.buyNameTransactionData.getName().equals(buyerPrimaryName.get()) ) { + buyer.removePrimaryName(); + } + } + } } diff --git a/src/main/java/org/qortal/transaction/RegisterNameTransaction.java b/src/main/java/org/qortal/transaction/RegisterNameTransaction.java index c4520fbf..8e8e2fcc 100644 --- a/src/main/java/org/qortal/transaction/RegisterNameTransaction.java +++ b/src/main/java/org/qortal/transaction/RegisterNameTransaction.java @@ -2,10 +2,12 @@ package org.qortal.transaction; import com.google.common.base.Utf8; import org.qortal.account.Account; +import org.qortal.api.resource.TransactionsResource; import org.qortal.asset.Asset; import org.qortal.block.BlockChain; import org.qortal.controller.repository.NamesDatabaseIntegrityCheck; import org.qortal.crypto.Crypto; +import org.qortal.data.naming.NameData; import org.qortal.data.transaction.RegisterNameTransactionData; import org.qortal.data.transaction.TransactionData; import org.qortal.naming.Name; @@ -15,6 +17,7 @@ import org.qortal.utils.Unicode; import java.util.Collections; import java.util.List; +import java.util.Optional; public class RegisterNameTransaction extends Transaction { @@ -54,6 +57,15 @@ public class RegisterNameTransaction extends Transaction { Account registrant = getRegistrant(); String name = this.registerNameTransactionData.getName(); + Optional registrantPrimaryName = registrant.getPrimaryName(); + if( registrantPrimaryName.isPresent() ) { + + NameData nameData = repository.getNameRepository().fromName(registrantPrimaryName.get()); + if (nameData.isForSale()) { + return ValidationResult.NOT_SUPPORTED; + } + } + int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight(); final int start = BlockChain.getInstance().getSelfSponsorshipAlgoV2Height() - 1180; final int end = BlockChain.getInstance().getSelfSponsorshipAlgoV3Height(); @@ -117,6 +129,16 @@ public class RegisterNameTransaction extends Transaction { // Register Name Name name = new Name(this.repository, this.registerNameTransactionData); name.register(); + + if( this.repository.getBlockRepository().getBlockchainHeight() > BlockChain.getInstance().getMultipleNamesPerAccountHeight()) { + + Account account = new Account(this.repository, this.getCreator().getAddress()); + + // if there is no primary name established, then the new registered name is the primary name + if (account.getPrimaryName().isEmpty()) { + account.setPrimaryName(this.registerNameTransactionData.getName()); + } + } } @Override diff --git a/src/main/java/org/qortal/transaction/UpdateNameTransaction.java b/src/main/java/org/qortal/transaction/UpdateNameTransaction.java index 8d42207c..bf0d12dc 100644 --- a/src/main/java/org/qortal/transaction/UpdateNameTransaction.java +++ b/src/main/java/org/qortal/transaction/UpdateNameTransaction.java @@ -3,6 +3,7 @@ package org.qortal.transaction; import com.google.common.base.Utf8; import org.qortal.account.Account; import org.qortal.asset.Asset; +import org.qortal.block.BlockChain; import org.qortal.controller.repository.NamesDatabaseIntegrityCheck; import org.qortal.crypto.Crypto; import org.qortal.data.naming.NameData; @@ -49,6 +50,12 @@ public class UpdateNameTransaction extends Transaction { public ValidationResult isValid() throws DataException { String name = this.updateNameTransactionData.getName(); + // if the account has more than one name, then they cannot update their primary name + if( this.repository.getNameRepository().getNamesByOwner(this.getOwner().getAddress()).size() > 1 && + this.getOwner().getPrimaryName().get().equals(name) ) { + return ValidationResult.NOT_SUPPORTED; + } + // Check name size bounds int nameLength = Utf8.encodedLength(name); if (nameLength < Name.MIN_NAME_SIZE || nameLength > Name.MAX_NAME_SIZE) @@ -152,6 +159,16 @@ public class UpdateNameTransaction extends Transaction { // Save this transaction, now with updated "name reference" to previous transaction that changed name this.repository.getTransactionRepository().save(this.updateNameTransactionData); + + if( this.repository.getBlockRepository().getBlockchainHeight() > BlockChain.getInstance().getMultipleNamesPerAccountHeight()) { + + Account account = new Account(this.repository, this.getCreator().getAddress()); + + // if updating the primary name, then set primary name to new name + if( account.getPrimaryName().isEmpty() || account.getPrimaryName().get().equals(this.updateNameTransactionData.getName())) { + account.setPrimaryName(this.updateNameTransactionData.getNewName()); + } + } } @Override @@ -167,6 +184,16 @@ public class UpdateNameTransaction extends Transaction { // Save this transaction, with previous "name reference" this.repository.getTransactionRepository().save(this.updateNameTransactionData); + + if( this.repository.getBlockRepository().getBlockchainHeight() > BlockChain.getInstance().getMultipleNamesPerAccountHeight()) { + + Account account = new Account(this.repository, this.getCreator().getAddress()); + + // if the primary name is the new updated name, then it needs to be set back to the previous name + if (account.getPrimaryName().isPresent() && account.getPrimaryName().get().equals(this.updateNameTransactionData.getNewName())) { + account.setPrimaryName(this.updateNameTransactionData.getName()); + } + } } } diff --git a/src/test/java/org/qortal/test/naming/BuySellTests.java b/src/test/java/org/qortal/test/naming/BuySellTests.java index 2283404c..9788bd7c 100644 --- a/src/test/java/org/qortal/test/naming/BuySellTests.java +++ b/src/test/java/org/qortal/test/naming/BuySellTests.java @@ -4,6 +4,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.qortal.account.PrivateKeyAccount; +import org.qortal.api.resource.TransactionsResource; import org.qortal.block.BlockChain; import org.qortal.data.naming.NameData; import org.qortal.data.transaction.BuyNameTransactionData; @@ -22,6 +23,7 @@ import org.qortal.transaction.Transaction; import org.qortal.utils.Amounts; import java.util.List; +import java.util.Optional; import java.util.Random; import static org.junit.Assert.*; @@ -135,13 +137,26 @@ public class BuySellTests extends Common { @Test public void testSellName() throws DataException { + // mint passed the feature trigger block + BlockUtils.mintBlocks(repository, BlockChain.getInstance().getMultipleNamesPerAccountHeight()); + // Register-name testRegisterName(); + // assert primary name for alice + Optional alicePrimaryName1 = alice.getPrimaryName(); + assertTrue(alicePrimaryName1.isPresent()); + assertTrue(alicePrimaryName1.get().equals(name)); + // Sell-name SellNameTransactionData transactionData = new SellNameTransactionData(TestTransaction.generateBase(alice), name, price); TransactionUtils.signAndMint(repository, transactionData, alice); + // assert primary name for alice + Optional alicePrimaryName2 = alice.getPrimaryName(); + assertTrue(alicePrimaryName2.isPresent()); + assertTrue(alicePrimaryName2.get().equals(name)); + NameData nameData; // Check name is for sale @@ -149,6 +164,14 @@ public class BuySellTests extends Common { assertTrue(nameData.isForSale()); assertEquals("price incorrect", price, nameData.getSalePrice()); + // assert alice cannot register another name while primary name is for sale + final String name2 = "another name"; + RegisterNameTransactionData registerSecondNameData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name2, "{}"); + Transaction.ValidationResult registrationResult = TransactionUtils.signAndImport(repository, registerSecondNameData, alice); + + // check that registering is not supported while primary name is for sale + assertTrue(Transaction.ValidationResult.NOT_SUPPORTED.equals(registrationResult)); + // Orphan sell-name BlockUtils.orphanLastBlock(repository); @@ -168,6 +191,10 @@ public class BuySellTests extends Common { // Orphan sell-name and register-name BlockUtils.orphanBlocks(repository, 2); + // assert primary name for alice + Optional alicePrimaryName3 = alice.getPrimaryName(); + assertTrue(alicePrimaryName3.isEmpty()); + // Check name no longer exists assertFalse(repository.getNameRepository().nameExists(name)); nameData = repository.getNameRepository().fromName(name); @@ -261,15 +288,36 @@ public class BuySellTests extends Common { @Test public void testBuyName() throws DataException { + // move passed primary initiation + BlockUtils.mintBlocks(repository, BlockChain.getInstance().getMultipleNamesPerAccountHeight()); + // Register-name and sell-name testSellName(); String seller = alice.getAddress(); + // assert alice has the name as primary + Optional alicePrimaryName1 = alice.getPrimaryName(); + assertTrue(alicePrimaryName1.isPresent()); + assertEquals(name, alicePrimaryName1.get()); + + // assert bob does not have a primary name + Optional bobPrimaryName1 = bob.getPrimaryName(); + assertTrue(bobPrimaryName1.isEmpty()); + // Buy-name BuyNameTransactionData transactionData = new BuyNameTransactionData(TestTransaction.generateBase(bob), name, price, seller); TransactionUtils.signAndMint(repository, transactionData, bob); + // assert alice does not have a primary name anymore + Optional alicePrimaryName2 = alice.getPrimaryName(); + assertTrue(alicePrimaryName2.isEmpty()); + + // assert bob does have the name as primary + Optional bobPrimaryName2 = bob.getPrimaryName(); + assertTrue(bobPrimaryName2.isPresent()); + assertEquals(name, bobPrimaryName2.get()); + NameData nameData; // Check name is sold @@ -280,6 +328,15 @@ public class BuySellTests extends Common { // Orphan buy-name BlockUtils.orphanLastBlock(repository); + // assert alice has the name as primary + Optional alicePrimaryNameOrphaned = alice.getPrimaryName(); + assertTrue(alicePrimaryNameOrphaned.isPresent()); + assertEquals(name, alicePrimaryNameOrphaned.get()); + + // assert bob does not have a primary name + Optional bobPrimaryNameOrphaned = bob.getPrimaryName(); + assertTrue(bobPrimaryNameOrphaned.isEmpty()); + // Check name is for sale (not sold) nameData = repository.getNameRepository().fromName(name); assertTrue(nameData.isForSale()); @@ -314,6 +371,9 @@ public class BuySellTests extends Common { assertFalse(nameData.isForSale()); // Not concerned about price assertEquals(bob.getAddress(), nameData.getOwner()); + + assertEquals(alice.getPrimaryName(), alice.determinePrimaryName(TransactionsResource.ConfirmationStatus.CONFIRMED)); + assertEquals(bob.getPrimaryName(), bob.determinePrimaryName(TransactionsResource.ConfirmationStatus.CONFIRMED)); } @Test @@ -373,6 +433,9 @@ public class BuySellTests extends Common { assertTrue(nameData.isForSale()); assertEquals("price incorrect", newPrice, nameData.getSalePrice()); assertEquals(bob.getAddress(), nameData.getOwner()); + + assertEquals(alice.getPrimaryName(), alice.determinePrimaryName(TransactionsResource.ConfirmationStatus.CONFIRMED)); + assertEquals(bob.getPrimaryName(), bob.determinePrimaryName(TransactionsResource.ConfirmationStatus.CONFIRMED)); } } diff --git a/src/test/java/org/qortal/test/naming/IntegrityTests.java b/src/test/java/org/qortal/test/naming/IntegrityTests.java index 767ea388..14a6891b 100644 --- a/src/test/java/org/qortal/test/naming/IntegrityTests.java +++ b/src/test/java/org/qortal/test/naming/IntegrityTests.java @@ -4,6 +4,8 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.qortal.account.PrivateKeyAccount; +import org.qortal.api.resource.TransactionsResource; +import org.qortal.block.BlockChain; import org.qortal.controller.repository.NamesDatabaseIntegrityCheck; import org.qortal.data.naming.NameData; import org.qortal.data.transaction.*; @@ -13,6 +15,7 @@ import org.qortal.repository.RepositoryFactory; import org.qortal.repository.RepositoryManager; import org.qortal.repository.hsqldb.HSQLDBRepositoryFactory; import org.qortal.settings.Settings; +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; @@ -385,6 +388,8 @@ public class IntegrityTests extends Common { @Test public void testUpdateToMissingName() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { + BlockUtils.mintBlocks(repository, BlockChain.getInstance().getMultipleNamesPerAccountHeight()); + // Register-name PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); String initialName = "test-name"; @@ -422,7 +427,12 @@ public class IntegrityTests extends Common { // Therefore the name that we are trying to rename TO already exists Transaction.ValidationResult result = transaction.importAsUnconfirmed(); assertTrue("Transaction should be invalid", Transaction.ValidationResult.OK != result); - assertTrue("Destination name should already exist", Transaction.ValidationResult.NAME_ALREADY_REGISTERED == result); + + // this assertion has been updated, because the primary name logic now comes into play and you cannot update a primary name when there + // is other names registered and if your try a NOT SUPPORTED result will be given + assertTrue("Destination name should already exist", Transaction.ValidationResult.NOT_SUPPORTED == result); + + assertEquals(alice.getPrimaryName(), alice.determinePrimaryName(TransactionsResource.ConfirmationStatus.CONFIRMED)); } } diff --git a/src/test/java/org/qortal/test/naming/MiscTests.java b/src/test/java/org/qortal/test/naming/MiscTests.java index 324cdf12..b85ca95c 100644 --- a/src/test/java/org/qortal/test/naming/MiscTests.java +++ b/src/test/java/org/qortal/test/naming/MiscTests.java @@ -1,6 +1,7 @@ package org.qortal.test.naming; import org.apache.commons.lang3.reflect.FieldUtils; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.qortal.account.PrivateKeyAccount; @@ -8,6 +9,7 @@ import org.qortal.api.AmountTypeAdapter; import org.qortal.block.BlockChain; import org.qortal.block.BlockChain.UnitFeesByTimestamp; import org.qortal.controller.BlockMinter; +import org.qortal.data.naming.NameData; import org.qortal.data.transaction.PaymentTransactionData; import org.qortal.data.transaction.RegisterNameTransactionData; import org.qortal.data.transaction.TransactionData; @@ -28,6 +30,7 @@ import org.qortal.utils.NTP; import java.util.Arrays; import java.util.List; +import java.util.Optional; import static org.junit.Assert.*; @@ -121,6 +124,8 @@ public class MiscTests extends Common { transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp())); TransactionUtils.signAndMint(repository, transactionData, alice); + BlockUtils.mintBlocks(repository, BlockChain.getInstance().getMultipleNamesPerAccountHeight()); + // Register another name that we will later attempt to rename to first name (above) String otherName = "new-name"; String otherData = ""; @@ -335,6 +340,8 @@ public class MiscTests extends Common { public void testRegisterNameFeeIncrease() throws Exception { try (final Repository repository = RepositoryManager.getRepository()) { + BlockUtils.mintBlocks(repository, BlockChain.getInstance().getMultipleNamesPerAccountHeight()); + // Add original fee to nameRegistrationUnitFees UnitFeesByTimestamp originalFee = new UnitFeesByTimestamp(); originalFee.timestamp = 0L; @@ -517,4 +524,168 @@ public class MiscTests extends Common { } } -} + @Test + public void testPrimaryNameEmpty() throws DataException { + + try (final Repository repository = RepositoryManager.getRepository()) { + + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + + // mint passed the feature trigger block + BlockUtils.mintBlocks(repository, BlockChain.getInstance().getMultipleNamesPerAccountHeight()); + + Optional primaryName = repository.getNameRepository().getPrimaryName(alice.getAddress()); + + Assert.assertNotNull(primaryName); + Assert.assertTrue(primaryName.isEmpty()); + } + } + + @Test + public void testPrimaryNameSingle() throws DataException { + + try (final Repository repository = RepositoryManager.getRepository()) { + String name = "alice 1"; + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + + // register name 1 + RegisterNameTransactionData transactionData1 = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "{}"); + transactionData1.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData1.getTimestamp())); + TransactionUtils.signAndMint(repository, transactionData1, alice); + + String name1 = transactionData1.getName(); + + // check name does exist + assertTrue(repository.getNameRepository().nameExists(name1)); + + + // mint passed the feature trigger block + BlockUtils.mintBlocks(repository, BlockChain.getInstance().getMultipleNamesPerAccountHeight() + 1); + + Optional primaryName = repository.getNameRepository().getPrimaryName(alice.getAddress()); + + Assert.assertNotNull(primaryName); + Assert.assertTrue(primaryName.isPresent()); + Assert.assertEquals(name, primaryName.get()); + } + } + + @Test + public void testPrimaryNameSingleAfterFeature() throws DataException { + + try (final Repository repository = RepositoryManager.getRepository()) { + String name = "alice 1"; + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + + // mint passed the feature trigger block + BlockUtils.mintBlocks(repository, BlockChain.getInstance().getMultipleNamesPerAccountHeight()); + + // register name 1 + RegisterNameTransactionData transactionData1 = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "{}"); + transactionData1.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData1.getTimestamp())); + TransactionUtils.signAndMint(repository, transactionData1, alice); + + String name1 = transactionData1.getName(); + + // check name does exist + assertTrue(repository.getNameRepository().nameExists(name1)); + + + Optional primaryName = repository.getNameRepository().getPrimaryName(alice.getAddress()); + + Assert.assertNotNull(primaryName); + Assert.assertTrue(primaryName.isPresent()); + Assert.assertEquals(name, primaryName.get()); + + BlockUtils.orphanLastBlock(repository); + + Optional primaryNameOrpaned = repository.getNameRepository().getPrimaryName(alice.getAddress()); + + Assert.assertNotNull(primaryNameOrpaned); + Assert.assertTrue(primaryNameOrpaned.isEmpty()); + } + } + + @Test + public void testUpdateNameMultiple() throws DataException { + + try (final Repository repository = RepositoryManager.getRepository()) { + String name = "alice 1"; + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + + // register name 1 + RegisterNameTransactionData transactionData1 = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "{}"); + transactionData1.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData1.getTimestamp())); + TransactionUtils.signAndMint(repository, transactionData1, alice); + + String name1 = transactionData1.getName(); + + // check name does exist + assertTrue(repository.getNameRepository().nameExists(name1)); + + // register another name, second registered name should fail before the feature trigger + final String name2 = "another name"; + RegisterNameTransactionData transactionData2 = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name2, "{}"); + Transaction.ValidationResult resultBeforeFeatureTrigger = TransactionUtils.signAndImport(repository, transactionData2, alice); + + // check that that multiple names is forbidden + assertTrue(Transaction.ValidationResult.MULTIPLE_NAMES_FORBIDDEN.equals(resultBeforeFeatureTrigger)); + + // mint passed the feature trigger block + BlockUtils.mintBlocks(repository, BlockChain.getInstance().getMultipleNamesPerAccountHeight()); + + // register again, now that we are passed the feature trigger + RegisterNameTransactionData transactionData3 = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name2, "{}"); + Transaction.ValidationResult resultAfterFeatureTrigger = TransactionUtils.signAndImport(repository, transactionData3, alice); + + // check that multiple names is ok + assertTrue(Transaction.ValidationResult.OK.equals(resultAfterFeatureTrigger)); + + // mint block, confirm transaction + BlockUtils.mintBlock(repository); + + // check name does exist + assertTrue(repository.getNameRepository().nameExists(name2)); + + // check that there are 2 names for one account + List namesByOwner = repository.getNameRepository().getNamesByOwner(alice.getAddress(), 0, 0, false); + + assertEquals(2, namesByOwner.size()); + + // check that the order is correct + assertEquals(name1, namesByOwner.get(0).getName()); + + String newestName = "newest-name"; + String newestReducedName = "newest-name"; + String newestData = "newest-data"; + TransactionData newestTransactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), name2, newestName, newestData); + TransactionUtils.signAndMint(repository, newestTransactionData, alice); + + // Check previous name no longer exists + assertFalse(repository.getNameRepository().nameExists(name2)); + + // Check newest name exists + assertTrue(repository.getNameRepository().nameExists(newestName)); + + Optional alicePrimaryName1 = alice.getPrimaryName(); + + assertTrue( alicePrimaryName1.isPresent() ); + assertEquals( name1, alicePrimaryName1.get() ); + + // orphan and recheck + BlockUtils.orphanLastBlock(repository); + + Optional alicePrimaryName2 = alice.getPrimaryName(); + + assertTrue( alicePrimaryName2.isPresent() ); + assertEquals( name1, alicePrimaryName2.get() ); + + // Check newest name no longer exists + assertFalse(repository.getNameRepository().nameExists(newestName)); + assertNull(repository.getNameRepository().fromReducedName(newestReducedName)); + + // Check previous name exists again + assertTrue(repository.getNameRepository().nameExists(name2)); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/qortal/test/naming/UpdateTests.java b/src/test/java/org/qortal/test/naming/UpdateTests.java index 8e54eb96..01b9a2ee 100644 --- a/src/test/java/org/qortal/test/naming/UpdateTests.java +++ b/src/test/java/org/qortal/test/naming/UpdateTests.java @@ -3,8 +3,12 @@ package org.qortal.test.naming; import org.junit.Before; import org.junit.Test; import org.qortal.account.PrivateKeyAccount; +import org.qortal.api.resource.TransactionsResource; +import org.qortal.block.BlockChain; import org.qortal.data.naming.NameData; +import org.qortal.data.transaction.BuyNameTransactionData; import org.qortal.data.transaction.RegisterNameTransactionData; +import org.qortal.data.transaction.SellNameTransactionData; import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.UpdateNameTransactionData; import org.qortal.repository.DataException; @@ -15,6 +19,9 @@ import org.qortal.test.common.Common; import org.qortal.test.common.TransactionUtils; import org.qortal.test.common.transaction.TestTransaction; import org.qortal.transaction.RegisterNameTransaction; +import org.qortal.transaction.Transaction; + +import java.util.Optional; import static org.junit.Assert.*; @@ -395,6 +402,13 @@ public class UpdateTests extends Common { assertTrue(repository.getNameRepository().nameExists(initialName)); assertNotNull(repository.getNameRepository().fromReducedName(initialReducedName)); + // move passed primary initiation + BlockUtils.mintBlocks(repository, BlockChain.getInstance().getMultipleNamesPerAccountHeight()); + + // check primary name + assertTrue(alice.getPrimaryName().isPresent()); + assertEquals(initialName, alice.getPrimaryName().get()); + // Update data String middleName = "middle-name"; String middleReducedName = "midd1e-name"; @@ -402,6 +416,11 @@ public class UpdateTests extends Common { transactionData = new UpdateNameTransactionData(TestTransaction.generateBase(alice), initialName, middleName, middleData); TransactionUtils.signAndMint(repository, transactionData, alice); + // check primary name + Optional alicePrimaryName1 = alice.getPrimaryName(); + assertTrue(alicePrimaryName1.isPresent()); + assertEquals(middleName, alicePrimaryName1.get()); + // Check data is correct assertEquals(middleData, repository.getNameRepository().fromName(middleName).getData()); @@ -414,6 +433,11 @@ public class UpdateTests extends Common { // Check data is correct assertEquals(newestData, repository.getNameRepository().fromName(newestName).getData()); + // check primary name + Optional alicePrimaryName2 = alice.getPrimaryName(); + assertTrue(alicePrimaryName2.isPresent()); + assertEquals(newestName, alicePrimaryName2.get()); + // Check initial name no longer exists assertFalse(repository.getNameRepository().nameExists(initialName)); assertNull(repository.getNameRepository().fromReducedName(initialReducedName)); @@ -516,4 +540,101 @@ public class UpdateTests extends Common { } } + @Test + public void testUpdatePrimaryName() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + // mint passed the feature trigger block + BlockUtils.mintBlocks(repository, BlockChain.getInstance().getMultipleNamesPerAccountHeight()); + + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + // register name 1 + String initialName = "initial-name"; + RegisterNameTransactionData registerNameTransactionData1 = new RegisterNameTransactionData(TestTransaction.generateBase(alice), initialName, "{}"); + registerNameTransactionData1.setFee(new RegisterNameTransaction(null, null).getUnitFee(registerNameTransactionData1.getTimestamp())); + TransactionUtils.signAndMint(repository, registerNameTransactionData1, alice); + + // assert name 1 registration, assert primary name + assertTrue(repository.getNameRepository().nameExists(initialName)); + + Optional primaryNameOptional = alice.getPrimaryName(); + assertTrue(primaryNameOptional.isPresent()); + assertEquals(initialName, primaryNameOptional.get()); + + // register name 2 + String secondName = "second-name"; + RegisterNameTransactionData registerNameTransactionData2 = new RegisterNameTransactionData(TestTransaction.generateBase(alice), secondName, "{}"); + registerNameTransactionData2.setFee(new RegisterNameTransaction(null, null).getUnitFee(registerNameTransactionData2.getTimestamp())); + TransactionUtils.signAndMint(repository, registerNameTransactionData2, alice); + + // assert name 2 registration, assert primary has not changed + assertTrue(repository.getNameRepository().nameExists(secondName)); + + // the name alice is trying to update to + String newName = "updated-name"; + + // update name, assert invalid + updateName(repository, initialName, newName, Transaction.ValidationResult.NOT_SUPPORTED, alice); + + // check primary name did not update + // check primary name update + Optional primaryNameNotUpdateOptional = alice.getPrimaryName(); + assertTrue(primaryNameNotUpdateOptional.isPresent()); + assertEquals(initialName, primaryNameNotUpdateOptional.get()); + + // sell name 2, assert valid + Long amount = 1000000L; + SellNameTransactionData transactionData = new SellNameTransactionData(TestTransaction.generateBase(alice), secondName, amount); + TransactionUtils.signAndMint(repository, transactionData, alice); + + // Check name is for sale + NameData nameData = repository.getNameRepository().fromName(secondName); + assertTrue(nameData.isForSale()); + assertEquals("price incorrect", amount, nameData.getSalePrice()); + + // bob buys name 2, assert + BuyNameTransactionData bobBuysName2Data = new BuyNameTransactionData(TestTransaction.generateBase(bob), secondName, amount, alice.getAddress()); + TransactionUtils.signAndMint(repository, bobBuysName2Data, bob); + + // update name, assert valid, assert primary name change + updateName(repository, initialName, newName, Transaction.ValidationResult.OK, alice); + + // check primary name update + Optional primaryNameUpdateOptional = alice.getPrimaryName(); + assertTrue(primaryNameUpdateOptional.isPresent()); + assertEquals(newName, primaryNameUpdateOptional.get()); + + assertEquals(alice.getPrimaryName(), alice.determinePrimaryName(TransactionsResource.ConfirmationStatus.CONFIRMED)); + assertEquals(bob.getPrimaryName(), bob.determinePrimaryName(TransactionsResource.ConfirmationStatus.CONFIRMED)); + } + } + + /** + * Update Name + * + * @param repository + * @param initialName the name before the update + * @param newName the name after the update + * @param expectedValidationResult the validation result expected from the update + * @param account the account for the update + * + * @throws DataException + */ + private static void updateName(Repository repository, String initialName, String newName, Transaction.ValidationResult expectedValidationResult, PrivateKeyAccount account) throws DataException { + TransactionData data = new UpdateNameTransactionData(TestTransaction.generateBase(account), initialName, newName, "{}"); + Transaction.ValidationResult result = TransactionUtils.signAndImport(repository,data, account); + + assertEquals("Transaction invalid", expectedValidationResult, result); + + BlockUtils.mintBlock(repository); + + if( Transaction.ValidationResult.OK.equals(expectedValidationResult) ) { + assertTrue(repository.getNameRepository().nameExists(newName)); + } + else { + // the new name should not exist, because the update was invalid + assertFalse(repository.getNameRepository().nameExists(newName)); + } + } }