Registered names: changing 'owner' and allowing renaming.

REGISTER_NAME has an "owner" field which can be different from the actual
registrant (transaction creator's public key, used for signing transaction).

This allowed people to register names to be owned by someone else, thus breaking
the whole "one name per account" aspect.

So now "owner" is removed from REGISTER_NAME, and the actual owner address is
derived from transaction creator's public key, as you would expect.

Similarly, UPDATE_NAME has a corresponding "newOwner" field which has been removed.

In addition, UPDATE_NAME now allows users to change their registered name using a new
"newName" field.

Various changes made to DB, Name class, etc. to accomodate above, along with some minor
bug-fixes and comment improvements/corrections.

Needs new unit tests to cover both new functionality and old!
This commit is contained in:
catbref 2020-05-13 10:19:56 +01:00
parent f29ae656b9
commit d9f784ed2b
16 changed files with 123 additions and 103 deletions

View File

@ -66,6 +66,10 @@ public class NameData {
return this.name; return this.name;
} }
public void setName(String name) {
this.name = name;
}
public String getData() { public String getData() {
return this.data; return this.data;
} }

View File

@ -19,8 +19,6 @@ public class RegisterNameTransactionData extends TransactionData {
// Properties // Properties
@Schema(description = "registrant's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP") @Schema(description = "registrant's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
private byte[] registrantPublicKey; private byte[] registrantPublicKey;
@Schema(description = "new owner's address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v")
private String owner;
@Schema(description = "requested name", example = "my-name") @Schema(description = "requested name", example = "my-name")
private String name; private String name;
@Schema(description = "simple name-related info in JSON format", example = "{ \"age\": 30 }") @Schema(description = "simple name-related info in JSON format", example = "{ \"age\": 30 }")
@ -38,11 +36,10 @@ public class RegisterNameTransactionData extends TransactionData {
} }
/** From repository */ /** From repository */
public RegisterNameTransactionData(BaseTransactionData baseTransactionData, String owner, String name, String data) { public RegisterNameTransactionData(BaseTransactionData baseTransactionData, String name, String data) {
super(TransactionType.REGISTER_NAME, baseTransactionData); super(TransactionType.REGISTER_NAME, baseTransactionData);
this.registrantPublicKey = baseTransactionData.creatorPublicKey; this.registrantPublicKey = baseTransactionData.creatorPublicKey;
this.owner = owner;
this.name = name; this.name = name;
this.data = data; this.data = data;
} }
@ -53,10 +50,6 @@ public class RegisterNameTransactionData extends TransactionData {
return this.registrantPublicKey; return this.registrantPublicKey;
} }
public String getOwner() {
return this.owner;
}
public String getName() { public String getName() {
return this.name; return this.name;
} }

View File

@ -17,10 +17,10 @@ public class UpdateNameTransactionData extends TransactionData {
// Properties // Properties
@Schema(description = "owner's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP") @Schema(description = "owner's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
private byte[] ownerPublicKey; private byte[] ownerPublicKey;
@Schema(description = "new owner's address", example = "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v")
private String newOwner;
@Schema(description = "which name to update", example = "my-name") @Schema(description = "which name to update", example = "my-name")
private String name; private String name;
@Schema(description = "new name", example = "my-new-name")
private String newName;
@Schema(description = "replacement simple name-related info in JSON format", example = "{ \"age\": 30 }") @Schema(description = "replacement simple name-related info in JSON format", example = "{ \"age\": 30 }")
private String newData; private String newData;
// For internal use when orphaning // For internal use when orphaning
@ -40,19 +40,19 @@ public class UpdateNameTransactionData extends TransactionData {
} }
/** From repository */ /** From repository */
public UpdateNameTransactionData(BaseTransactionData baseTransactionData, String newOwner, String name, String newData, byte[] nameReference) { public UpdateNameTransactionData(BaseTransactionData baseTransactionData, String name, String newName, String newData, byte[] nameReference) {
super(TransactionType.UPDATE_NAME, baseTransactionData); super(TransactionType.UPDATE_NAME, baseTransactionData);
this.ownerPublicKey = baseTransactionData.creatorPublicKey; this.ownerPublicKey = baseTransactionData.creatorPublicKey;
this.newOwner = newOwner;
this.name = name; this.name = name;
this.newName = newName;
this.newData = newData; this.newData = newData;
this.nameReference = nameReference; this.nameReference = nameReference;
} }
/** From network/API */ /** From network/API */
public UpdateNameTransactionData(BaseTransactionData baseTransactionData, String newOwner, String name, String newData) { public UpdateNameTransactionData(BaseTransactionData baseTransactionData, String name, String newName, String newData) {
this(baseTransactionData, newOwner, name, newData, null); this(baseTransactionData, name, newName, newData, null);
} }
// Getters / setters // Getters / setters
@ -61,14 +61,14 @@ public class UpdateNameTransactionData extends TransactionData {
return this.ownerPublicKey; return this.ownerPublicKey;
} }
public String getNewOwner() {
return this.newOwner;
}
public String getName() { public String getName() {
return this.name; return this.name;
} }
public String getNewName() {
return this.newName;
}
public String getNewData() { public String getNewData() {
return this.newData; return this.newData;
} }

View File

@ -3,6 +3,7 @@ package org.qortal.naming;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount; import org.qortal.account.PublicKeyAccount;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.data.naming.NameData; import org.qortal.data.naming.NameData;
import org.qortal.data.transaction.BuyNameTransactionData; import org.qortal.data.transaction.BuyNameTransactionData;
import org.qortal.data.transaction.CancelSellNameTransactionData; import org.qortal.data.transaction.CancelSellNameTransactionData;
@ -20,6 +21,7 @@ public class Name {
private NameData nameData; private NameData nameData;
// Useful constants // Useful constants
public static final int MIN_NAME_SIZE = 3;
public static final int MAX_NAME_SIZE = 400; public static final int MAX_NAME_SIZE = 400;
public static final int MAX_DATA_SIZE = 4000; public static final int MAX_DATA_SIZE = 4000;
@ -33,7 +35,10 @@ public class Name {
*/ */
public Name(Repository repository, RegisterNameTransactionData registerNameTransactionData) { public Name(Repository repository, RegisterNameTransactionData registerNameTransactionData) {
this.repository = repository; this.repository = repository;
this.nameData = new NameData(registerNameTransactionData.getOwner(),
String owner = Crypto.toAddress(registerNameTransactionData.getRegistrantPublicKey());
this.nameData = new NameData(owner,
registerNameTransactionData.getName(), registerNameTransactionData.getData(), registerNameTransactionData.getTimestamp(), registerNameTransactionData.getName(), registerNameTransactionData.getData(), registerNameTransactionData.getTimestamp(),
registerNameTransactionData.getSignature(), registerNameTransactionData.getTxGroupId()); registerNameTransactionData.getSignature(), registerNameTransactionData.getTxGroupId());
} }
@ -66,23 +71,31 @@ public class Name {
throw new DataException("Unable to revert name transaction as referenced transaction not found in repository"); throw new DataException("Unable to revert name transaction as referenced transaction not found in repository");
switch (previousTransactionData.getType()) { switch (previousTransactionData.getType()) {
case REGISTER_NAME: case REGISTER_NAME: {
RegisterNameTransactionData previousRegisterNameTransactionData = (RegisterNameTransactionData) previousTransactionData; RegisterNameTransactionData previousRegisterNameTransactionData = (RegisterNameTransactionData) previousTransactionData;
this.nameData.setOwner(previousRegisterNameTransactionData.getOwner()); this.nameData.setName(previousRegisterNameTransactionData.getName());
this.nameData.setData(previousRegisterNameTransactionData.getData()); this.nameData.setData(previousRegisterNameTransactionData.getData());
break; break;
}
case UPDATE_NAME: case UPDATE_NAME: {
UpdateNameTransactionData previousUpdateNameTransactionData = (UpdateNameTransactionData) previousTransactionData; UpdateNameTransactionData previousUpdateNameTransactionData = (UpdateNameTransactionData) previousTransactionData;
this.nameData.setData(previousUpdateNameTransactionData.getNewData());
this.nameData.setOwner(previousUpdateNameTransactionData.getNewOwner());
break;
case BUY_NAME: if (!previousUpdateNameTransactionData.getNewName().isBlank())
this.nameData.setName(previousUpdateNameTransactionData.getNewName());
if (!previousUpdateNameTransactionData.getNewData().isEmpty())
this.nameData.setData(previousUpdateNameTransactionData.getNewData());
break;
}
case BUY_NAME: {
BuyNameTransactionData previousBuyNameTransactionData = (BuyNameTransactionData) previousTransactionData; BuyNameTransactionData previousBuyNameTransactionData = (BuyNameTransactionData) previousTransactionData;
Account buyer = new PublicKeyAccount(this.repository, previousBuyNameTransactionData.getBuyerPublicKey()); Account buyer = new PublicKeyAccount(this.repository, previousBuyNameTransactionData.getBuyerPublicKey());
this.nameData.setOwner(buyer.getAddress()); this.nameData.setOwner(buyer.getAddress());
break; break;
}
default: default:
throw new IllegalStateException("Unable to revert name transaction due to unsupported referenced transaction"); throw new IllegalStateException("Unable to revert name transaction due to unsupported referenced transaction");
@ -96,8 +109,11 @@ public class Name {
// New name reference is this transaction's signature // New name reference is this transaction's signature
this.nameData.setReference(updateNameTransactionData.getSignature()); this.nameData.setReference(updateNameTransactionData.getSignature());
// Update Name's owner and data // Update name and data where appropriate
this.nameData.setOwner(updateNameTransactionData.getNewOwner()); if (!updateNameTransactionData.getNewName().isEmpty())
this.nameData.setOwner(updateNameTransactionData.getNewName());
if (!updateNameTransactionData.getNewData().isEmpty())
this.nameData.setData(updateNameTransactionData.getNewData()); this.nameData.setData(updateNameTransactionData.getNewData());
// Save updated name data // Save updated name data

View File

@ -294,11 +294,11 @@ public class HSQLDBDatabaseUpdates {
// Register Name Transactions // Register Name Transactions
stmt.execute("CREATE TABLE RegisterNameTransactions (signature Signature, registrant QortalPublicKey NOT NULL, name RegisteredName NOT NULL, " stmt.execute("CREATE TABLE RegisterNameTransactions (signature Signature, registrant QortalPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "owner QortalAddress NOT NULL, data NameData NOT NULL, " + TRANSACTION_KEYS + ")"); + "data NameData NOT NULL, " + TRANSACTION_KEYS + ")");
// Update Name Transactions // Update Name Transactions
stmt.execute("CREATE TABLE UpdateNameTransactions (signature Signature, owner QortalPublicKey NOT NULL, name RegisteredName NOT NULL, " stmt.execute("CREATE TABLE UpdateNameTransactions (signature Signature, owner QortalPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "new_owner QortalAddress NOT NULL, new_data NameData NOT NULL, name_reference Signature, " + TRANSACTION_KEYS + ")"); + "new_name RegisteredName NOT NULL, new_data NameData NOT NULL, name_reference Signature, " + TRANSACTION_KEYS + ")");
// Sell Name Transactions // Sell Name Transactions
stmt.execute("CREATE TABLE SellNameTransactions (signature Signature, owner QortalPublicKey NOT NULL, name RegisteredName NOT NULL, " stmt.execute("CREATE TABLE SellNameTransactions (signature Signature, owner QortalPublicKey NOT NULL, name RegisteredName NOT NULL, "

View File

@ -17,17 +17,16 @@ public class HSQLDBRegisterNameTransactionRepository extends HSQLDBTransactionRe
} }
TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException { TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException {
String sql = "SELECT owner, name, data FROM RegisterNameTransactions WHERE signature = ?"; String sql = "SELECT name, data FROM RegisterNameTransactions WHERE signature = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) { try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
if (resultSet == null) if (resultSet == null)
return null; return null;
String owner = resultSet.getString(1); String name = resultSet.getString(1);
String name = resultSet.getString(2); String data = resultSet.getString(2);
String data = resultSet.getString(3);
return new RegisterNameTransactionData(baseTransactionData, owner, name, data); return new RegisterNameTransactionData(baseTransactionData, name, data);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Unable to fetch register name transaction from repository", e); throw new DataException("Unable to fetch register name transaction from repository", e);
} }
@ -40,8 +39,7 @@ public class HSQLDBRegisterNameTransactionRepository extends HSQLDBTransactionRe
HSQLDBSaver saveHelper = new HSQLDBSaver("RegisterNameTransactions"); HSQLDBSaver saveHelper = new HSQLDBSaver("RegisterNameTransactions");
saveHelper.bind("signature", registerNameTransactionData.getSignature()).bind("registrant", registerNameTransactionData.getRegistrantPublicKey()) saveHelper.bind("signature", registerNameTransactionData.getSignature()).bind("registrant", registerNameTransactionData.getRegistrantPublicKey())
.bind("owner", registerNameTransactionData.getOwner()).bind("name", registerNameTransactionData.getName()) .bind("name", registerNameTransactionData.getName()).bind("data", registerNameTransactionData.getData());
.bind("data", registerNameTransactionData.getData());
try { try {
saveHelper.execute(this.repository); saveHelper.execute(this.repository);

View File

@ -17,18 +17,18 @@ public class HSQLDBUpdateNameTransactionRepository extends HSQLDBTransactionRepo
} }
TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException { TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException {
String sql = "SELECT new_owner, name, new_data, name_reference FROM UpdateNameTransactions WHERE signature = ?"; String sql = "SELECT name, new_name, new_data, name_reference FROM UpdateNameTransactions WHERE signature = ?";
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) { try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
if (resultSet == null) if (resultSet == null)
return null; return null;
String newOwner = resultSet.getString(1); String name = resultSet.getString(1);
String name = resultSet.getString(2); String newName = resultSet.getString(2);
String newData = resultSet.getString(3); String newData = resultSet.getString(3);
byte[] nameReference = resultSet.getBytes(4); byte[] nameReference = resultSet.getBytes(4);
return new UpdateNameTransactionData(baseTransactionData, newOwner, name, newData, nameReference); return new UpdateNameTransactionData(baseTransactionData, name, newName, newData, nameReference);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Unable to fetch update name transaction from repository", e); throw new DataException("Unable to fetch update name transaction from repository", e);
} }
@ -41,7 +41,7 @@ public class HSQLDBUpdateNameTransactionRepository extends HSQLDBTransactionRepo
HSQLDBSaver saveHelper = new HSQLDBSaver("UpdateNameTransactions"); HSQLDBSaver saveHelper = new HSQLDBSaver("UpdateNameTransactions");
saveHelper.bind("signature", updateNameTransactionData.getSignature()).bind("owner", updateNameTransactionData.getOwnerPublicKey()) saveHelper.bind("signature", updateNameTransactionData.getSignature()).bind("owner", updateNameTransactionData.getOwnerPublicKey())
.bind("new_owner", updateNameTransactionData.getNewOwner()).bind("name", updateNameTransactionData.getName()) .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("name_reference", updateNameTransactionData.getNameReference());
try { try {

View File

@ -5,6 +5,7 @@ import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.block.BlockChain;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.naming.NameData; import org.qortal.data.naming.NameData;
import org.qortal.data.transaction.BuyNameTransactionData; import org.qortal.data.transaction.BuyNameTransactionData;
@ -54,7 +55,7 @@ public class BuyNameTransaction extends Transaction {
// Check name size bounds // Check name size bounds
int nameLength = Utf8.encodedLength(name); int nameLength = Utf8.encodedLength(name);
if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE) if (nameLength < Name.MIN_NAME_SIZE || nameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH; return ValidationResult.INVALID_NAME_LENGTH;
// Check name is lowercase // Check name is lowercase
@ -76,6 +77,11 @@ public class BuyNameTransaction extends Transaction {
if (buyer.getAddress().equals(nameData.getOwner())) if (buyer.getAddress().equals(nameData.getOwner()))
return ValidationResult.BUYER_ALREADY_OWNER; return ValidationResult.BUYER_ALREADY_OWNER;
// If accounts are only allowed one registered name then check for this
if (BlockChain.getInstance().oneNamePerAccount()
&& !this.repository.getNameRepository().getNamesByOwner(buyer.getAddress()).isEmpty())
return ValidationResult.MULTIPLE_NAMES_FORBIDDEN;
// Check expected seller currently owns name // Check expected seller currently owns name
if (!this.buyNameTransactionData.getSeller().equals(nameData.getOwner())) if (!this.buyNameTransactionData.getSeller().equals(nameData.getOwner()))
return ValidationResult.INVALID_SELLER; return ValidationResult.INVALID_SELLER;
@ -84,7 +90,7 @@ public class BuyNameTransaction extends Transaction {
if (this.buyNameTransactionData.getAmount() != nameData.getSalePrice()) if (this.buyNameTransactionData.getAmount() != nameData.getSalePrice())
return ValidationResult.INVALID_AMOUNT; return ValidationResult.INVALID_AMOUNT;
// Check issuer has enough funds // Check buyer has enough funds
if (buyer.getConfirmedBalance(Asset.QORT) < this.buyNameTransactionData.getFee()) if (buyer.getConfirmedBalance(Asset.QORT) < this.buyNameTransactionData.getFee())
return ValidationResult.NO_BALANCE; return ValidationResult.NO_BALANCE;
@ -93,21 +99,21 @@ public class BuyNameTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Update Name // Buy Name
Name name = new Name(this.repository, this.buyNameTransactionData.getName()); Name name = new Name(this.repository, this.buyNameTransactionData.getName());
name.buy(this.buyNameTransactionData); name.buy(this.buyNameTransactionData);
// Save transaction with updated "name reference" pointing to previous transaction that updated name // Save transaction with updated "name reference" pointing to previous transaction that changed name
this.repository.getTransactionRepository().save(this.buyNameTransactionData); this.repository.getTransactionRepository().save(this.buyNameTransactionData);
} }
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Revert name // Un-buy name
Name name = new Name(this.repository, this.buyNameTransactionData.getName()); Name name = new Name(this.repository, this.buyNameTransactionData.getName());
name.unbuy(this.buyNameTransactionData); name.unbuy(this.buyNameTransactionData);
// Save this transaction, with removed "name reference" // Save this transaction, with previous "name reference"
this.repository.getTransactionRepository().save(this.buyNameTransactionData); this.repository.getTransactionRepository().save(this.buyNameTransactionData);
} }

View File

@ -6,7 +6,6 @@ import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.block.BlockChain; import org.qortal.block.BlockChain;
import org.qortal.crypto.Crypto;
import org.qortal.data.transaction.RegisterNameTransactionData; import org.qortal.data.transaction.RegisterNameTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.naming.Name; import org.qortal.naming.Name;
@ -32,7 +31,7 @@ public class RegisterNameTransaction extends Transaction {
@Override @Override
public List<String> getRecipientAddresses() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.registerNameTransactionData.getOwner()); return Collections.emptyList();
} }
// Navigation // Navigation
@ -46,23 +45,20 @@ public class RegisterNameTransaction extends Transaction {
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
Account registrant = getRegistrant(); Account registrant = getRegistrant();
String name = this.registerNameTransactionData.getName();
// Check owner address is valid
if (!Crypto.isValidAddress(this.registerNameTransactionData.getOwner()))
return ValidationResult.INVALID_ADDRESS;
// Check name size bounds // Check name size bounds
int nameLength = Utf8.encodedLength(this.registerNameTransactionData.getName()); int nameLength = Utf8.encodedLength(name);
if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE) if (nameLength < Name.MIN_NAME_SIZE || nameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH; return ValidationResult.INVALID_NAME_LENGTH;
// Check data size bounds // Check data size bounds
int dataLength = Utf8.encodedLength(this.registerNameTransactionData.getData()); int dataLength = Utf8.encodedLength(this.registerNameTransactionData.getData());
if (dataLength < 1 || dataLength > Name.MAX_DATA_SIZE) if (dataLength > Name.MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH; return ValidationResult.INVALID_DATA_LENGTH;
// Check name is lowercase // Check name is lowercase
if (!this.registerNameTransactionData.getName().equals(this.registerNameTransactionData.getName().toLowerCase())) if (!name.equals(name.toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE; return ValidationResult.NAME_NOT_LOWER_CASE;
// Check registrant has enough funds // Check registrant has enough funds
@ -78,10 +74,9 @@ public class RegisterNameTransaction extends Transaction {
if (this.repository.getNameRepository().nameExists(this.registerNameTransactionData.getName())) if (this.repository.getNameRepository().nameExists(this.registerNameTransactionData.getName()))
return ValidationResult.NAME_ALREADY_REGISTERED; return ValidationResult.NAME_ALREADY_REGISTERED;
Account registrant = getRegistrant();
// If accounts are only allowed one registered name then check for this // If accounts are only allowed one registered name then check for this
if (BlockChain.getInstance().oneNamePerAccount() && !this.repository.getNameRepository().getNamesByOwner(registrant.getAddress()).isEmpty()) if (BlockChain.getInstance().oneNamePerAccount()
&& !this.repository.getNameRepository().getNamesByOwner(getRegistrant().getAddress()).isEmpty())
return ValidationResult.MULTIPLE_NAMES_FORBIDDEN; return ValidationResult.MULTIPLE_NAMES_FORBIDDEN;
return ValidationResult.OK; return ValidationResult.OK;

View File

@ -5,7 +5,6 @@ import java.util.List;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.crypto.Crypto;
import org.qortal.data.naming.NameData; import org.qortal.data.naming.NameData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
import org.qortal.data.transaction.UpdateNameTransactionData; import org.qortal.data.transaction.UpdateNameTransactionData;
@ -32,7 +31,7 @@ public class UpdateNameTransaction extends Transaction {
@Override @Override
public List<String> getRecipientAddresses() throws DataException { public List<String> getRecipientAddresses() throws DataException {
return Collections.singletonList(this.updateNameTransactionData.getNewOwner()); return Collections.emptyList();
} }
// Navigation // Navigation
@ -41,30 +40,17 @@ public class UpdateNameTransaction extends Transaction {
return this.getCreator(); return this.getCreator();
} }
public Account getNewOwner() {
return new Account(this.repository, this.updateNameTransactionData.getNewOwner());
}
// Processing // Processing
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
String name = this.updateNameTransactionData.getName(); String name = this.updateNameTransactionData.getName();
// Check new owner address is valid
if (!Crypto.isValidAddress(this.updateNameTransactionData.getNewOwner()))
return ValidationResult.INVALID_ADDRESS;
// Check name size bounds // Check name size bounds
int nameLength = Utf8.encodedLength(name); int nameLength = Utf8.encodedLength(name);
if (nameLength < 1 || nameLength > Name.MAX_NAME_SIZE) if (nameLength < Name.MIN_NAME_SIZE || nameLength > Name.MAX_NAME_SIZE)
return ValidationResult.INVALID_NAME_LENGTH; return ValidationResult.INVALID_NAME_LENGTH;
// Check new data size bounds
int newDataLength = Utf8.encodedLength(this.updateNameTransactionData.getNewData());
if (newDataLength < 1 || newDataLength > Name.MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH;
// Check name is lowercase // Check name is lowercase
if (!name.equals(name.toLowerCase())) if (!name.equals(name.toLowerCase()))
return ValidationResult.NAME_NOT_LOWER_CASE; return ValidationResult.NAME_NOT_LOWER_CASE;
@ -79,6 +65,24 @@ public class UpdateNameTransaction extends Transaction {
if (nameData.getCreationGroupId() != this.updateNameTransactionData.getTxGroupId()) if (nameData.getCreationGroupId() != this.updateNameTransactionData.getTxGroupId())
return ValidationResult.TX_GROUP_ID_MISMATCH; return ValidationResult.TX_GROUP_ID_MISMATCH;
// Check new name (0 length means don't update name)
String newName = this.updateNameTransactionData.getNewName();
int newNameLength = Utf8.encodedLength(newName);
if (newNameLength != 0) {
// Check new name size bounds
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()))
return ValidationResult.NAME_NOT_LOWER_CASE;
}
// Check new data size bounds (0 length means don't update data)
int newDataLength = Utf8.encodedLength(this.updateNameTransactionData.getNewData());
if (newDataLength > Name.MAX_DATA_SIZE)
return ValidationResult.INVALID_DATA_LENGTH;
Account owner = getOwner(); Account owner = getOwner();
// Check owner has enough funds // Check owner has enough funds
@ -92,6 +96,10 @@ public class UpdateNameTransaction extends Transaction {
public ValidationResult isProcessable() throws DataException { public ValidationResult isProcessable() throws DataException {
NameData nameData = this.repository.getNameRepository().fromName(this.updateNameTransactionData.getName()); NameData nameData = this.repository.getNameRepository().fromName(this.updateNameTransactionData.getName());
// Check name still exists
if (nameData == null)
return ValidationResult.NAME_DOES_NOT_EXIST;
// Check name isn't currently for sale // Check name isn't currently for sale
if (nameData.getIsForSale()) if (nameData.getIsForSale())
return ValidationResult.NAME_ALREADY_FOR_SALE; return ValidationResult.NAME_ALREADY_FOR_SALE;
@ -102,6 +110,10 @@ public class UpdateNameTransaction extends Transaction {
if (!owner.getAddress().equals(nameData.getOwner())) if (!owner.getAddress().equals(nameData.getOwner()))
return ValidationResult.INVALID_NAME_OWNER; return ValidationResult.INVALID_NAME_OWNER;
// Check new name isn't already taken
if (this.repository.getNameRepository().nameExists(this.updateNameTransactionData.getNewName()))
return ValidationResult.NAME_ALREADY_REGISTERED;
return ValidationResult.OK; return ValidationResult.OK;
} }
@ -111,7 +123,7 @@ public class UpdateNameTransaction extends Transaction {
Name name = new Name(this.repository, this.updateNameTransactionData.getName()); Name name = new Name(this.repository, this.updateNameTransactionData.getName());
name.update(this.updateNameTransactionData); name.update(this.updateNameTransactionData);
// Save this transaction, now with updated "name reference" to previous transaction that updated name // Save this transaction, now with updated "name reference" to previous transaction that changed name
this.repository.getTransactionRepository().save(this.updateNameTransactionData); this.repository.getTransactionRepository().save(this.updateNameTransactionData);
} }
@ -121,7 +133,7 @@ public class UpdateNameTransaction extends Transaction {
Name name = new Name(this.repository, this.updateNameTransactionData.getName()); Name name = new Name(this.repository, this.updateNameTransactionData.getName());
name.revert(this.updateNameTransactionData); name.revert(this.updateNameTransactionData);
// Save this transaction, now with removed "name reference" // Save this transaction, with previous "name reference"
this.repository.getTransactionRepository().save(this.updateNameTransactionData); this.repository.getTransactionRepository().save(this.updateNameTransactionData);
} }

View File

@ -33,7 +33,6 @@ public class RegisterNameTransactionTransformer extends TransactionTransformer {
layout.add("transaction's groupID", TransformationType.INT); layout.add("transaction's groupID", TransformationType.INT);
layout.add("reference", TransformationType.SIGNATURE); layout.add("reference", TransformationType.SIGNATURE);
layout.add("name registrant's public key", TransformationType.PUBLIC_KEY); layout.add("name registrant's public key", TransformationType.PUBLIC_KEY);
layout.add("name owner", TransformationType.ADDRESS);
layout.add("name length", TransformationType.INT); layout.add("name length", TransformationType.INT);
layout.add("name", TransformationType.STRING); layout.add("name", TransformationType.STRING);
layout.add("data length", TransformationType.INT); layout.add("data length", TransformationType.INT);
@ -52,8 +51,6 @@ public class RegisterNameTransactionTransformer extends TransactionTransformer {
byte[] registrantPublicKey = Serialization.deserializePublicKey(byteBuffer); byte[] registrantPublicKey = Serialization.deserializePublicKey(byteBuffer);
String owner = Serialization.deserializeAddress(byteBuffer);
String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE); String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE);
String data = Serialization.deserializeSizedString(byteBuffer, Name.MAX_DATA_SIZE); String data = Serialization.deserializeSizedString(byteBuffer, Name.MAX_DATA_SIZE);
@ -65,7 +62,7 @@ public class RegisterNameTransactionTransformer extends TransactionTransformer {
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, registrantPublicKey, fee, signature); BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, registrantPublicKey, fee, signature);
return new RegisterNameTransactionData(baseTransactionData, owner, name, data); return new RegisterNameTransactionData(baseTransactionData, name, data);
} }
public static int getDataLength(TransactionData transactionData) throws TransformationException { public static int getDataLength(TransactionData transactionData) throws TransformationException {
@ -83,8 +80,6 @@ public class RegisterNameTransactionTransformer extends TransactionTransformer {
transformCommonBytes(transactionData, bytes); transformCommonBytes(transactionData, bytes);
Serialization.serializeAddress(bytes, registerNameTransactionData.getOwner());
Serialization.serializeSizedString(bytes, registerNameTransactionData.getName()); Serialization.serializeSizedString(bytes, registerNameTransactionData.getName());
Serialization.serializeSizedString(bytes, registerNameTransactionData.getData()); Serialization.serializeSizedString(bytes, registerNameTransactionData.getData());

View File

@ -18,11 +18,11 @@ import com.google.common.primitives.Longs;
public class UpdateNameTransactionTransformer extends TransactionTransformer { public class UpdateNameTransactionTransformer extends TransactionTransformer {
// Property lengths // Property lengths
private static final int OWNER_LENGTH = ADDRESS_LENGTH;
private static final int NAME_SIZE_LENGTH = INT_LENGTH; private static final int NAME_SIZE_LENGTH = INT_LENGTH;
private static final int DATA_SIZE_LENGTH = INT_LENGTH; private static final int NEW_NAME_SIZE_LENGTH = INT_LENGTH;
private static final int NEW_DATA_SIZE_LENGTH = INT_LENGTH;
private static final int EXTRAS_LENGTH = OWNER_LENGTH + NAME_SIZE_LENGTH + DATA_SIZE_LENGTH; private static final int EXTRAS_LENGTH = NAME_SIZE_LENGTH + NEW_NAME_SIZE_LENGTH + NEW_DATA_SIZE_LENGTH;
protected static final TransactionLayout layout; protected static final TransactionLayout layout;
@ -33,10 +33,11 @@ public class UpdateNameTransactionTransformer extends TransactionTransformer {
layout.add("transaction's groupID", TransformationType.INT); layout.add("transaction's groupID", TransformationType.INT);
layout.add("reference", TransformationType.SIGNATURE); layout.add("reference", TransformationType.SIGNATURE);
layout.add("name owner's public key", TransformationType.PUBLIC_KEY); layout.add("name owner's public key", TransformationType.PUBLIC_KEY);
layout.add("name's new owner", TransformationType.ADDRESS);
layout.add("name length", TransformationType.INT); layout.add("name length", TransformationType.INT);
layout.add("name", TransformationType.STRING); layout.add("name", TransformationType.STRING);
layout.add("new data length", TransformationType.INT); layout.add("new name's length (0 for no change)", TransformationType.INT);
layout.add("new name", TransformationType.STRING);
layout.add("new data length (0 for no change)", TransformationType.INT);
layout.add("new data", TransformationType.STRING); layout.add("new data", TransformationType.STRING);
layout.add("fee", TransformationType.AMOUNT); layout.add("fee", TransformationType.AMOUNT);
layout.add("signature", TransformationType.SIGNATURE); layout.add("signature", TransformationType.SIGNATURE);
@ -52,10 +53,10 @@ public class UpdateNameTransactionTransformer extends TransactionTransformer {
byte[] ownerPublicKey = Serialization.deserializePublicKey(byteBuffer); byte[] ownerPublicKey = Serialization.deserializePublicKey(byteBuffer);
String newOwner = Serialization.deserializeAddress(byteBuffer);
String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE); String name = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE);
String newName = Serialization.deserializeSizedString(byteBuffer, Name.MAX_NAME_SIZE);
String newData = Serialization.deserializeSizedString(byteBuffer, Name.MAX_DATA_SIZE); String newData = Serialization.deserializeSizedString(byteBuffer, Name.MAX_DATA_SIZE);
long fee = byteBuffer.getLong(); long fee = byteBuffer.getLong();
@ -65,13 +66,14 @@ public class UpdateNameTransactionTransformer extends TransactionTransformer {
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, ownerPublicKey, fee, signature); BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, ownerPublicKey, fee, signature);
return new UpdateNameTransactionData(baseTransactionData, newOwner, name, newData); return new UpdateNameTransactionData(baseTransactionData, name, newName, newData);
} }
public static int getDataLength(TransactionData transactionData) throws TransformationException { public static int getDataLength(TransactionData transactionData) throws TransformationException {
UpdateNameTransactionData updateNameTransactionData = (UpdateNameTransactionData) transactionData; UpdateNameTransactionData updateNameTransactionData = (UpdateNameTransactionData) transactionData;
return getBaseLength(transactionData) + EXTRAS_LENGTH + Utf8.encodedLength(updateNameTransactionData.getName()) return getBaseLength(transactionData) + EXTRAS_LENGTH + Utf8.encodedLength(updateNameTransactionData.getName())
+ Utf8.encodedLength(updateNameTransactionData.getNewName())
+ Utf8.encodedLength(updateNameTransactionData.getNewData()); + Utf8.encodedLength(updateNameTransactionData.getNewData());
} }
@ -83,10 +85,10 @@ public class UpdateNameTransactionTransformer extends TransactionTransformer {
transformCommonBytes(transactionData, bytes); transformCommonBytes(transactionData, bytes);
Serialization.serializeAddress(bytes, updateNameTransactionData.getNewOwner());
Serialization.serializeSizedString(bytes, updateNameTransactionData.getName()); Serialization.serializeSizedString(bytes, updateNameTransactionData.getName());
Serialization.serializeSizedString(bytes, updateNameTransactionData.getNewName());
Serialization.serializeSizedString(bytes, updateNameTransactionData.getNewData()); Serialization.serializeSizedString(bytes, updateNameTransactionData.getNewData());
bytes.write(Longs.toByteArray(updateNameTransactionData.getFee())); bytes.write(Longs.toByteArray(updateNameTransactionData.getFee()));

View File

@ -46,7 +46,7 @@ public class NamesApiTests extends ApiCommon {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
String name = "test-name"; String name = "test-name";
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), alice.getAddress(), name, "{}"); RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "{}");
TransactionUtils.signAndMint(repository, transactionData, alice); TransactionUtils.signAndMint(repository, transactionData, alice);
assertNotNull(this.namesResource.getNamesByAddress(alice.getAddress(), null, null, null)); assertNotNull(this.namesResource.getNamesByAddress(alice.getAddress(), null, null, null));
@ -61,7 +61,7 @@ public class NamesApiTests extends ApiCommon {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
String name = "test-name"; String name = "test-name";
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), alice.getAddress(), name, "{}"); RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "{}");
TransactionUtils.signAndMint(repository, transactionData, alice); TransactionUtils.signAndMint(repository, transactionData, alice);
assertNotNull(this.namesResource.getName(name)); assertNotNull(this.namesResource.getName(name));
@ -76,7 +76,7 @@ public class NamesApiTests extends ApiCommon {
String name = "test-name"; String name = "test-name";
long price = 1_23456789L; long price = 1_23456789L;
TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), alice.getAddress(), name, "{}"); TransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "{}");
TransactionUtils.signAndMint(repository, transactionData, alice); TransactionUtils.signAndMint(repository, transactionData, alice);
// Sell-name // Sell-name

View File

@ -9,14 +9,13 @@ import org.qortal.repository.Repository;
public class RegisterNameTestTransaction extends TestTransaction { public class RegisterNameTestTransaction extends TestTransaction {
public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException { public static TransactionData randomTransaction(Repository repository, PrivateKeyAccount account, boolean wantValid) throws DataException {
String owner = account.getAddress();
String name = "test name"; String name = "test name";
if (!wantValid) if (!wantValid)
name += " " + random.nextInt(1_000_000); name += " " + random.nextInt(1_000_000);
String data = "{ \"key\": \"value\" }"; String data = "{ \"key\": \"value\" }";
return new RegisterNameTransactionData(generateBase(account), owner, name, data); return new RegisterNameTransactionData(generateBase(account), name, data);
} }
} }

View File

@ -61,7 +61,7 @@ public class BuySellTests extends Common {
@Test @Test
public void testRegisterName() throws DataException { public void testRegisterName() throws DataException {
// Register-name // Register-name
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), alice.getAddress(), name, "{}"); RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "{}");
TransactionUtils.signAndMint(repository, transactionData, alice); TransactionUtils.signAndMint(repository, transactionData, alice);
String name = transactionData.getName(); String name = transactionData.getName();

View File

@ -29,7 +29,7 @@ public class MiscTests extends Common {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
String name = "test-name"; String name = "test-name";
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), alice.getAddress(), name, "{}"); RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "{}");
TransactionUtils.signAndMint(repository, transactionData, alice); TransactionUtils.signAndMint(repository, transactionData, alice);
List<String> recentNames = repository.getNameRepository().getRecentNames(0L); List<String> recentNames = repository.getNameRepository().getRecentNames(0L);