Wipe unconfirmed transactions on startup

Fix issues with last reference and unconfirmed transactions.
This commit is contained in:
catbref 2018-12-21 15:27:38 +00:00
parent c4ed4b378c
commit 783edb3447
13 changed files with 112 additions and 8 deletions

View File

@ -0,0 +1,18 @@
package api;
import javax.xml.bind.Unmarshaller.Listener;
import data.transaction.TransactionData;
public class UnmarshalListener extends Listener {
@Override
public void afterUnmarshal(Object target, Object parent) {
if (!(target instanceof TransactionData))
return;
// do something
return;
}
}

View File

@ -258,7 +258,7 @@ public class AssetsResource {
try (final Repository repository = RepositoryManager.getRepository()) {
Transaction transaction = Transaction.fromData(repository, transactionData);
ValidationResult result = transaction.isValid();
ValidationResult result = transaction.isValidUnconfirmed();
if (result != ValidationResult.OK)
throw TransactionsResource.createTransactionInvalidException(request, result);

View File

@ -65,7 +65,7 @@ public class NamesResource {
try (final Repository repository = RepositoryManager.getRepository()) {
Transaction transaction = Transaction.fromData(repository, transactionData);
ValidationResult result = transaction.isValid();
ValidationResult result = transaction.isValidUnconfirmed();
if (result != ValidationResult.OK)
throw TransactionsResource.createTransactionInvalidException(request, result);

View File

@ -65,7 +65,7 @@ public class PaymentsResource {
try (final Repository repository = RepositoryManager.getRepository()) {
Transaction transaction = Transaction.fromData(repository, transactionData);
ValidationResult result = transaction.isValid();
ValidationResult result = transaction.isValidUnconfirmed();
if (result != ValidationResult.OK)
throw TransactionsResource.createTransactionInvalidException(request, result);

View File

@ -319,7 +319,7 @@ public class TransactionsResource {
if (!transaction.isSignatureValid())
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE);
ValidationResult result = transaction.isValid();
ValidationResult result = transaction.isValidUnconfirmed();
if (result != ValidationResult.OK)
throw createTransactionInvalidException(request, result);

View File

@ -111,6 +111,11 @@ public abstract class TransactionData {
return Crypto.toAddress(this.creatorPublicKey);
}
@XmlTransient
public void setCreatorPublicKey(byte[] creatorPublicKey) {
this.creatorPublicKey = creatorPublicKey;
}
// Comparison
@Override

View File

@ -9,6 +9,7 @@ import org.apache.logging.log4j.Logger;
import data.block.BlockData;
import data.transaction.TransactionData;
import qora.account.PrivateKeyAccount;
import qora.account.PublicKeyAccount;
import qora.block.Block.ValidationResult;
import qora.transaction.Transaction;
import repository.BlockRepository;
@ -47,6 +48,12 @@ public class BlockGenerator extends Thread {
Thread.currentThread().setName("BlockGenerator");
try (final Repository repository = RepositoryManager.getRepository()) {
// Wipe existing unconfirmed transactions
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getAllUnconfirmedTransactions();
for (TransactionData transactionData : unconfirmedTransactions)
repository.getTransactionRepository().delete(transactionData);
repository.saveChanges();
generator = new PrivateKeyAccount(repository, generatorPrivateKey);
// Going to need this a lot...
@ -109,10 +116,10 @@ public class BlockGenerator extends Thread {
// Grab all unconfirmed transactions (already sorted)
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getAllUnconfirmedTransactions();
// Remove transactions that have timestamp later than block's timestamp (not yet valid)
unconfirmedTransactions.removeIf(transactionData -> transactionData.getTimestamp() > newBlock.getBlockData().getTimestamp());
// Remove transactions that have expired deadline for this block
unconfirmedTransactions.removeIf(transactionData -> Transaction.fromData(repository, transactionData).getDeadline() <= newBlock.getBlockData().getTimestamp());
unconfirmedTransactions.removeIf(transactionData -> !isSuitableTransaction(repository, transactionData, newBlock));
// Discard last-reference changes used to aid transaction validity checks
repository.discardChanges();
// Attempt to add transactions until block is full, or we run out
for (TransactionData transactionData : unconfirmedTransactions)
@ -120,6 +127,40 @@ public class BlockGenerator extends Thread {
break;
}
/** Returns true if transaction is suitable for adding to new block */
private boolean isSuitableTransaction(Repository repository, TransactionData transactionData, Block newBlock) {
// Ignore transactions that have timestamp later than block's timestamp (not yet valid)
if (transactionData.getTimestamp() > newBlock.getBlockData().getTimestamp())
return false;
Transaction transaction = Transaction.fromData(repository, transactionData);
// Ignore transactions that have expired deadline for this block
if (transaction.getDeadline() <= newBlock.getBlockData().getTimestamp())
return false;
// Ignore transactions that are currently not valid
try {
if (transaction.isValid() != Transaction.ValidationResult.OK)
return false;
} catch (DataException e) {
// Not good either
return false;
}
// Good for adding to a block
// Temporarily update sender's last reference so that subsequent transactions validations work
// These updates will be discard on exit of addUnconfirmedTransactions() above
PublicKeyAccount creator = new PublicKeyAccount(repository, transactionData.getCreatorPublicKey());
try {
creator.setLastReference(transactionData.getSignature());
} catch (DataException e) {
// Not good
return false;
}
return true;
}
public void shutdown() {
this.running = false;
// Interrupt too, absorbed by HSQLDB but could be caught by Thread.sleep()

View File

@ -32,6 +32,10 @@ public class IssueAssetTransaction extends Transaction {
super(repository, transactionData);
this.issueAssetTransactionData = (IssueAssetTransactionData) this.transactionData;
// XXX This is horrible - thanks to JAXB unmarshalling not calling constructor
if (this.transactionData.getCreatorPublicKey() == null)
this.transactionData.setCreatorPublicKey(this.issueAssetTransactionData.getIssuerPublicKey());
}
// More information

View File

@ -26,6 +26,10 @@ public class PaymentTransaction extends Transaction {
super(repository, transactionData);
this.paymentTransactionData = (PaymentTransactionData) this.transactionData;
// XXX This is horrible - thanks to JAXB unmarshalling not calling constructor
if (this.transactionData.getCreatorPublicKey() == null)
this.transactionData.setCreatorPublicKey(this.paymentTransactionData.getSenderPublicKey());
}
// More information

View File

@ -28,6 +28,10 @@ public class RegisterNameTransaction extends Transaction {
super(repository, transactionData);
this.registerNameTransactionData = (RegisterNameTransactionData) this.transactionData;
// XXX This is horrible - thanks to JAXB unmarshalling not calling constructor
if (this.transactionData.getCreatorPublicKey() == null)
this.transactionData.setCreatorPublicKey(this.registerNameTransactionData.getRegistrantPublicKey());
}
// More information

View File

@ -410,6 +410,28 @@ public abstract class Transaction {
}
}
/**
* Returns whether transaction can be added to unconfirmed transactions.
* <p>
* NOTE: temporarily updates creator's lastReference to that from
* unconfirmed transactions, and hence calls <tt>repository.discardChanges()</tt>
* before exit.
* <p>
* This is not done normally because we don't want unconfirmed transactions affecting validity of transactions already included in a block.
*
* @return true if transaction can be added to unconfirmed transactions, false otherwise
* @throws DataException
*/
public ValidationResult isValidUnconfirmed() throws DataException {
try {
Account creator = this.getCreator();
creator.setLastReference(creator.getUnconfirmedLastReference());
return this.isValid();
} finally {
repository.discardChanges();
}
}
/**
* Returns whether transaction can be added to the blockchain.
* <p>

View File

@ -517,6 +517,11 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
} catch (SQLException e) {
throw new DataException("Unable to delete transaction from repository", e);
}
try {
this.repository.delete("UnconfirmedTransactions", "signature = ?", transactionData.getSignature());
} catch (SQLException e) {
throw new DataException("Unable to remove transaction from unconfirmed transactions repository", e);
}
}
}

View File

@ -47,6 +47,7 @@ BLOCK_NO_EXISTS=block does not exist
# Transactions
TRANSACTION_NO_EXISTS=transaction does not exist
PUBLIC_KEY_NOT_FOUND=public key not found
TRANSACTION_INVALID=transaction invalid
# Names
NAME_NO_EXISTS=name does not exist