mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-13 11:12:31 +00:00
Wipe unconfirmed transactions on startup
Fix issues with last reference and unconfirmed transactions.
This commit is contained in:
parent
c4ed4b378c
commit
783edb3447
18
src/main/java/api/UnmarshalListener.java
Normal file
18
src/main/java/api/UnmarshalListener.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -111,6 +111,11 @@ public abstract class TransactionData {
|
||||
return Crypto.toAddress(this.creatorPublicKey);
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
public void setCreatorPublicKey(byte[] creatorPublicKey) {
|
||||
this.creatorPublicKey = creatorPublicKey;
|
||||
}
|
||||
|
||||
// Comparison
|
||||
|
||||
@Override
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user