API + fix for names in HSQLDB

Added POST /names/update for building an UPDATE-NAME transaction.

BlockGenerator now tries to validate new block after adding each
unconfirmed transaction in turn. If block becomes invalid then
that transaction is removed/skipped. This should further prevent
block jams. Skipped transactions might be deleted as the next block
is forged when unconfirmed transactions are collated/filtered/expired.

Add Block.deleteTransaction() for use during block generation above.

Block.addTransaction() and Block.deleteTransaction() use transaction
signatures to test for presence in Block's existing transactions.

Names shouldn't have stored registrant's public key!
"registrantPublicKey" removed from NameData Java object/bean.
Corresponding column removed from HSQLDB using ALTER TABLE but
also from the original CREATE TABLE definition. Remove the ALTER
TABLE statement just prior to rebuilding database!

(This needs to be applied to Polls too as some point).

Also, UpdateNameTransactions and BuyNameTransactions tables now
allow name_reference to be NULL as this column value isn't set
until the corresponding transactions are processed/added to a
block. (name_reference is a link to previous name-related
transaction that altered Name data like "owner" or "data" so
that name-related transactions can be orphaned/undone).
This commit is contained in:
catbref 2019-01-09 14:41:49 +00:00
parent 95d640cc8c
commit 22c87a6e08
10 changed files with 158 additions and 47 deletions

View File

@ -29,6 +29,7 @@ import org.qora.api.model.NameSummary;
import org.qora.crypto.Crypto; import org.qora.crypto.Crypto;
import org.qora.data.naming.NameData; import org.qora.data.naming.NameData;
import org.qora.data.transaction.RegisterNameTransactionData; import org.qora.data.transaction.RegisterNameTransactionData;
import org.qora.data.transaction.UpdateNameTransactionData;
import org.qora.repository.DataException; import org.qora.repository.DataException;
import org.qora.repository.Repository; import org.qora.repository.Repository;
import org.qora.repository.RepositoryManager; import org.qora.repository.RepositoryManager;
@ -36,6 +37,7 @@ import org.qora.transaction.Transaction;
import org.qora.transaction.Transaction.ValidationResult; import org.qora.transaction.Transaction.ValidationResult;
import org.qora.transform.TransformationException; import org.qora.transform.TransformationException;
import org.qora.transform.transaction.RegisterNameTransactionTransformer; import org.qora.transform.transaction.RegisterNameTransactionTransformer;
import org.qora.transform.transaction.UpdateNameTransactionTransformer;
import org.qora.utils.Base58; import org.qora.utils.Base58;
@Path("/names") @Path("/names")
@ -153,7 +155,7 @@ public class NamesResource {
} }
) )
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE}) @ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
public String buildTransaction(RegisterNameTransactionData transactionData) { public String registerName(RegisterNameTransactionData transactionData) {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
Transaction transaction = Transaction.fromData(repository, transactionData); Transaction transaction = Transaction.fromData(repository, transactionData);
@ -170,4 +172,47 @@ public class NamesResource {
} }
} }
@POST
@Path("/update")
@Operation(
summary = "Build raw, unsigned, UPDATE_NAME transaction",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.APPLICATION_JSON,
schema = @Schema(
implementation = UpdateNameTransactionData.class
)
)
),
responses = {
@ApiResponse(
description = "raw, unsigned, UPDATE_NAME transaction encoded in Base58",
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "string"
)
)
)
}
)
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
public String updateName(UpdateNameTransactionData transactionData) {
try (final Repository repository = RepositoryManager.getRepository()) {
Transaction transaction = Transaction.fromData(repository, transactionData);
ValidationResult result = transaction.isValidUnconfirmed();
if (result != ValidationResult.OK)
throw TransactionsResource.createTransactionInvalidException(request, result);
byte[] bytes = UpdateNameTransactionTransformer.toBytes(transactionData);
return Base58.encode(bytes);
} catch (TransformationException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}
}
} }

View File

@ -543,8 +543,8 @@ public class Block {
if (this.blockData.getGeneratorSignature() == null) if (this.blockData.getGeneratorSignature() == null)
throw new IllegalStateException("Cannot calculate transactions signature as block has no generator signature"); throw new IllegalStateException("Cannot calculate transactions signature as block has no generator signature");
// Already added? // Already added? (Check using signature)
if (this.transactions.contains(transactionData)) if (this.transactions.stream().anyMatch(transaction -> Arrays.equals(transaction.getTransactionData().getSignature(), transactionData.getSignature())))
return true; return true;
// Check there is space in block // Check there is space in block
@ -573,6 +573,47 @@ public class Block {
return true; return true;
} }
/**
* Remove a transaction from the block.
* <p>
* Used when constructing a new block during forging.
* <p>
* Requires block's {@code generator} being a {@code PrivateKeyAccount} so block's transactions signature can be recalculated.
*
* @param transactionData
* @throws IllegalStateException
* if block's {@code generator} is not a {@code PrivateKeyAccount}.
*/
public void deleteTransaction(TransactionData transactionData) {
// Can't add to transactions if we haven't loaded existing ones yet
if (this.transactions == null)
throw new IllegalStateException("Attempted to add transaction to partially loaded database Block");
if (!(this.generator instanceof PrivateKeyAccount))
throw new IllegalStateException("Block's generator has no private key");
if (this.blockData.getGeneratorSignature() == null)
throw new IllegalStateException("Cannot calculate transactions signature as block has no generator signature");
// Attempt to remove from block (Check using signature)
boolean wasElementRemoved = this.transactions.removeIf(transaction -> Arrays.equals(transaction.getTransactionData().getSignature(), transactionData.getSignature()));
if (!wasElementRemoved)
// Wasn't there - nothing more to do
return;
// Re-sort
this.transactions.sort(Transaction.getComparator());
// Update transaction count
this.blockData.setTransactionCount(this.blockData.getTransactionCount() - 1);
// Update totalFees
this.blockData.setTotalFees(this.blockData.getTotalFees().subtract(transactionData.getFee()));
// We've removed a transaction, so recalculate transactions signature
calcTransactionsSignature();
}
/** /**
* Recalculate block's generator signature. * Recalculate block's generator signature.
* <p> * <p>
@ -787,7 +828,7 @@ public class Block {
// NOTE: in Gen1 there was an extra block height passed to DeployATTransaction.isValid // NOTE: in Gen1 there was an extra block height passed to DeployATTransaction.isValid
Transaction.ValidationResult validationResult = transaction.isValid(); Transaction.ValidationResult validationResult = transaction.isValid();
if (validationResult != Transaction.ValidationResult.OK) { if (validationResult != Transaction.ValidationResult.OK) {
LOGGER.error("Error during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()) + ": " LOGGER.debug("Error during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()) + ": "
+ validationResult.name()); + validationResult.name());
return ValidationResult.TRANSACTION_INVALID; return ValidationResult.TRANSACTION_INVALID;
} }

View File

@ -147,9 +147,21 @@ public class BlockGenerator extends Thread {
repository.discardChanges(); repository.discardChanges();
// Attempt to add transactions until block is full, or we run out // Attempt to add transactions until block is full, or we run out
for (TransactionData transactionData : unconfirmedTransactions) // If a transaction makes the block invalid then skip it and it'll either expire or be in next block.
for (TransactionData transactionData : unconfirmedTransactions) {
if (!newBlock.addTransaction(transactionData)) if (!newBlock.addTransaction(transactionData))
break; break;
// Sign to create block's signature
newBlock.sign();
// If newBlock is no longer valid then we can't use transaction
ValidationResult validationResult = newBlock.isValid();
if (validationResult != ValidationResult.OK) {
LOGGER.debug("Skipping invalid transaction " + Base58.encode(transactionData.getSignature()) + " during block generation");
newBlock.deleteTransaction(transactionData);
}
}
} }
public void shutdown() { public void shutdown() {

View File

@ -10,7 +10,6 @@ import javax.xml.bind.annotation.XmlAccessorType;
public class NameData { public class NameData {
// Properties // Properties
private byte[] registrantPublicKey;
private String owner; private String owner;
private String name; private String name;
private String data; private String data;
@ -26,9 +25,8 @@ public class NameData {
protected NameData() { protected NameData() {
} }
public NameData(byte[] registrantPublicKey, String owner, String name, String data, long registered, Long updated, byte[] reference, boolean isForSale, public NameData(String owner, String name, String data, long registered, Long updated, byte[] reference, boolean isForSale,
BigDecimal salePrice) { BigDecimal salePrice) {
this.registrantPublicKey = registrantPublicKey;
this.owner = owner; this.owner = owner;
this.name = name; this.name = name;
this.data = data; this.data = data;
@ -39,16 +37,12 @@ public class NameData {
this.salePrice = salePrice; this.salePrice = salePrice;
} }
public NameData(byte[] registrantPublicKey, String owner, String name, String data, long registered, byte[] reference) { public NameData(String owner, String name, String data, long registered, byte[] reference) {
this(registrantPublicKey, owner, name, data, registered, null, reference, false, null); this(owner, name, data, registered, null, reference, false, null);
} }
// Getters / setters // Getters / setters
public byte[] getRegistrantPublicKey() {
return this.registrantPublicKey;
}
public String getOwner() { public String getOwner() {
return this.owner; return this.owner;
} }

View File

@ -4,6 +4,7 @@ import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlTransient;
import org.qora.transaction.Transaction.TransactionType; import org.qora.transaction.Transaction.TransactionType;
@ -15,16 +16,24 @@ import io.swagger.v3.oas.annotations.media.Schema;
public class UpdateNameTransactionData extends TransactionData { public class UpdateNameTransactionData extends TransactionData {
// Properties // Properties
@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; private String newOwner;
@Schema(description = "which name to update", example = "my-name")
private String name; private String name;
@Schema(description = "replacement simple name-related info in JSON format", example = "{ \"age\": 30 }")
private String newData; private String newData;
// For internal use when orphaning
@XmlTransient
@Schema(hidden = true)
private byte[] nameReference; private byte[] nameReference;
// Constructors // Constructors
// For JAX-RS // For JAX-RS
protected UpdateNameTransactionData() { protected UpdateNameTransactionData() {
super(TransactionType.UPDATE_NAME);
} }
public UpdateNameTransactionData(byte[] ownerPublicKey, String newOwner, String name, String newData, byte[] nameReference, BigDecimal fee, long timestamp, public UpdateNameTransactionData(byte[] ownerPublicKey, String newOwner, String name, String newData, byte[] nameReference, BigDecimal fee, long timestamp,

View File

@ -33,7 +33,7 @@ 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.getRegistrantPublicKey(), registerNameTransactionData.getOwner(), this.nameData = new NameData(registerNameTransactionData.getOwner(),
registerNameTransactionData.getName(), registerNameTransactionData.getData(), registerNameTransactionData.getTimestamp(), registerNameTransactionData.getName(), registerNameTransactionData.getData(), registerNameTransactionData.getTimestamp(),
registerNameTransactionData.getSignature()); registerNameTransactionData.getSignature());
} }

View File

@ -184,7 +184,7 @@ public class HSQLDBDatabaseUpdates {
case 6: case 6:
// Update Name Transactions // Update Name Transactions
stmt.execute("CREATE TABLE UpdateNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, " stmt.execute("CREATE TABLE UpdateNameTransactions (signature Signature, owner QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "new_owner QoraAddress NOT NULL, new_data NameData NOT NULL, name_reference Signature NOT NULL, " + "new_owner QoraAddress NOT NULL, new_data NameData NOT NULL, name_reference Signature, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;
@ -203,7 +203,7 @@ public class HSQLDBDatabaseUpdates {
case 9: case 9:
// Buy Name Transactions // Buy Name Transactions
stmt.execute("CREATE TABLE BuyNameTransactions (signature Signature, buyer QoraPublicKey NOT NULL, name RegisteredName NOT NULL, " stmt.execute("CREATE TABLE BuyNameTransactions (signature Signature, buyer QoraPublicKey NOT NULL, name RegisteredName NOT NULL, "
+ "seller QoraAddress NOT NULL, amount QoraAmount NOT NULL, name_reference Signature NOT NULL, " + "seller QoraAddress NOT NULL, amount QoraAmount NOT NULL, name_reference Signature, "
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
break; break;
@ -357,7 +357,7 @@ public class HSQLDBDatabaseUpdates {
case 26: case 26:
// Registered Names // Registered Names
stmt.execute( stmt.execute(
"CREATE TABLE Names (name RegisteredName, data VARCHAR(4000) NOT NULL, registrant QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, " "CREATE TABLE Names (name RegisteredName, data VARCHAR(4000) NOT NULL, owner QoraAddress NOT NULL, "
+ "registered TIMESTAMP WITH TIME ZONE NOT NULL, updated TIMESTAMP WITH TIME ZONE, reference Signature, is_for_sale BOOLEAN NOT NULL, sale_price QoraAmount, " + "registered TIMESTAMP WITH TIME ZONE NOT NULL, updated TIMESTAMP WITH TIME ZONE, reference Signature, is_for_sale BOOLEAN NOT NULL, sale_price QoraAmount, "
+ "PRIMARY KEY (name))"); + "PRIMARY KEY (name))");
break; break;
@ -389,6 +389,15 @@ public class HSQLDBDatabaseUpdates {
stmt.execute("CREATE INDEX ATTransactionsIndex on ATTransactions (AT_address)"); stmt.execute("CREATE INDEX ATTransactionsIndex on ATTransactions (AT_address)");
break; break;
case 28:
// XXX TEMP fix until database rebuild
// Allow name_reference to be NULL while transaction is unconfirmed
stmt.execute("ALTER TABLE UpdateNameTransactions ALTER COLUMN name_reference SET NULL");
stmt.execute("ALTER TABLE BuyNameTransactions ALTER COLUMN name_reference SET NULL");
// Names.registrant shouldn't be there
stmt.execute("ALTER TABLE Names DROP COLUMN registrant");
break;
default: default:
// nothing to do // nothing to do
return false; return false;

View File

@ -23,24 +23,23 @@ public class HSQLDBNameRepository implements NameRepository {
@Override @Override
public NameData fromName(String name) throws DataException { public NameData fromName(String name) throws DataException {
try (ResultSet resultSet = this.repository try (ResultSet resultSet = this.repository
.checkedExecute("SELECT registrant, owner, data, registered, updated, reference, is_for_sale, sale_price FROM Names WHERE name = ?", name)) { .checkedExecute("SELECT owner, data, registered, updated, reference, is_for_sale, sale_price FROM Names WHERE name = ?", name)) {
if (resultSet == null) if (resultSet == null)
return null; return null;
byte[] registrantPublicKey = resultSet.getBytes(1); String owner = resultSet.getString(1);
String owner = resultSet.getString(2); String data = resultSet.getString(2);
String data = resultSet.getString(3); long registered = resultSet.getTimestamp(3, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
long registered = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
// Special handling for possibly-NULL "updated" column // Special handling for possibly-NULL "updated" column
Timestamp updatedTimestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)); Timestamp updatedTimestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC));
Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime(); Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime();
byte[] reference = resultSet.getBytes(6); byte[] reference = resultSet.getBytes(5);
boolean isForSale = resultSet.getBoolean(7); boolean isForSale = resultSet.getBoolean(6);
BigDecimal salePrice = resultSet.getBigDecimal(8); BigDecimal salePrice = resultSet.getBigDecimal(7);
return new NameData(registrantPublicKey, owner, name, data, registered, updated, reference, isForSale, salePrice); return new NameData(owner, name, data, registered, updated, reference, isForSale, salePrice);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Unable to fetch name info from repository", e); throw new DataException("Unable to fetch name info from repository", e);
} }
@ -60,26 +59,25 @@ public class HSQLDBNameRepository implements NameRepository {
List<NameData> names = new ArrayList<>(); List<NameData> names = new ArrayList<>();
try (ResultSet resultSet = this.repository try (ResultSet resultSet = this.repository
.checkedExecute("SELECT name, data, registrant, owner, registered, updated, reference, is_for_sale, sale_price FROM Names")) { .checkedExecute("SELECT name, data, owner, registered, updated, reference, is_for_sale, sale_price FROM Names")) {
if (resultSet == null) if (resultSet == null)
return names; return names;
do { do {
String name = resultSet.getString(1); String name = resultSet.getString(1);
String data = resultSet.getString(2); String data = resultSet.getString(2);
byte[] registrantPublicKey = resultSet.getBytes(3); String owner = resultSet.getString(3);
String owner = resultSet.getString(4); long registered = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
long registered = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
// Special handling for possibly-NULL "updated" column // Special handling for possibly-NULL "updated" column
Timestamp updatedTimestamp = resultSet.getTimestamp(6, Calendar.getInstance(HSQLDBRepository.UTC)); Timestamp updatedTimestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC));
Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime(); Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime();
byte[] reference = resultSet.getBytes(7); byte[] reference = resultSet.getBytes(6);
boolean isForSale = resultSet.getBoolean(8); boolean isForSale = resultSet.getBoolean(7);
BigDecimal salePrice = resultSet.getBigDecimal(9); BigDecimal salePrice = resultSet.getBigDecimal(8);
names.add(new NameData(registrantPublicKey, owner, name, data, registered, updated, reference, isForSale, salePrice)); names.add(new NameData(owner, name, data, registered, updated, reference, isForSale, salePrice));
} while (resultSet.next()); } while (resultSet.next());
return names; return names;
@ -93,25 +91,24 @@ public class HSQLDBNameRepository implements NameRepository {
List<NameData> names = new ArrayList<>(); List<NameData> names = new ArrayList<>();
try (ResultSet resultSet = this.repository try (ResultSet resultSet = this.repository
.checkedExecute("SELECT name, data, registrant, registered, updated, reference, is_for_sale, sale_price FROM Names WHERE owner = ?", owner)) { .checkedExecute("SELECT name, data, registered, updated, reference, is_for_sale, sale_price FROM Names WHERE owner = ?", owner)) {
if (resultSet == null) if (resultSet == null)
return names; return names;
do { do {
String name = resultSet.getString(1); String name = resultSet.getString(1);
String data = resultSet.getString(2); String data = resultSet.getString(2);
byte[] registrantPublicKey = resultSet.getBytes(3); long registered = resultSet.getTimestamp(3, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
long registered = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC)).getTime();
// Special handling for possibly-NULL "updated" column // Special handling for possibly-NULL "updated" column
Timestamp updatedTimestamp = resultSet.getTimestamp(5, Calendar.getInstance(HSQLDBRepository.UTC)); Timestamp updatedTimestamp = resultSet.getTimestamp(4, Calendar.getInstance(HSQLDBRepository.UTC));
Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime(); Long updated = updatedTimestamp == null ? null : updatedTimestamp.getTime();
byte[] reference = resultSet.getBytes(6); byte[] reference = resultSet.getBytes(5);
boolean isForSale = resultSet.getBoolean(7); boolean isForSale = resultSet.getBoolean(6);
BigDecimal salePrice = resultSet.getBigDecimal(8); BigDecimal salePrice = resultSet.getBigDecimal(7);
names.add(new NameData(registrantPublicKey, owner, name, data, registered, updated, reference, isForSale, salePrice)); names.add(new NameData(owner, name, data, registered, updated, reference, isForSale, salePrice));
} while (resultSet.next()); } while (resultSet.next());
return names; return names;
@ -128,7 +125,7 @@ public class HSQLDBNameRepository implements NameRepository {
Long updated = nameData.getUpdated(); Long updated = nameData.getUpdated();
Timestamp updatedTimestamp = updated == null ? null : new Timestamp(updated); Timestamp updatedTimestamp = updated == null ? null : new Timestamp(updated);
saveHelper.bind("registrant", nameData.getRegistrantPublicKey()).bind("owner", nameData.getOwner()).bind("name", nameData.getName()) saveHelper.bind("owner", nameData.getOwner()).bind("name", nameData.getName())
.bind("data", nameData.getData()).bind("registered", new Timestamp(nameData.getRegistered())).bind("updated", updatedTimestamp) .bind("data", nameData.getData()).bind("registered", new Timestamp(nameData.getRegistered())).bind("updated", updatedTimestamp)
.bind("reference", nameData.getReference()).bind("is_for_sale", nameData.getIsForSale()).bind("sale_price", nameData.getSalePrice()); .bind("reference", nameData.getReference()).bind("is_for_sale", nameData.getIsForSale()).bind("sale_price", nameData.getSalePrice());

View File

@ -441,7 +441,7 @@ public abstract class Transaction {
public ValidationResult isValidUnconfirmed() throws DataException { public ValidationResult isValidUnconfirmed() throws DataException {
// Transactions with a timestamp prior to latest block's timestamp are too old // Transactions with a timestamp prior to latest block's timestamp are too old
BlockData latestBlock = repository.getBlockRepository().getLastBlock(); BlockData latestBlock = repository.getBlockRepository().getLastBlock();
if (this.transactionData.getTimestamp() <= latestBlock.getTimestamp()) if (this.getDeadline() <= latestBlock.getTimestamp())
return ValidationResult.TIMESTAMP_TOO_OLD; return ValidationResult.TIMESTAMP_TOO_OLD;
// Transactions with a timestamp too far into future are too new // Transactions with a timestamp too far into future are too new

View File

@ -29,6 +29,10 @@ public class UpdateNameTransaction extends Transaction {
super(repository, transactionData); super(repository, transactionData);
this.updateNameTransactionData = (UpdateNameTransactionData) this.transactionData; this.updateNameTransactionData = (UpdateNameTransactionData) this.transactionData;
// XXX This is horrible - thanks to JAXB unmarshalling not calling constructor
if (this.transactionData.getCreatorPublicKey() == null)
this.transactionData.setCreatorPublicKey(this.updateNameTransactionData.getOwnerPublicKey());
} }
// More information // More information