mirror of
https://github.com/Qortal/qortal.git
synced 2025-05-09 19:27:54 +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()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
ValidationResult result = transaction.isValid();
|
ValidationResult result = transaction.isValidUnconfirmed();
|
||||||
if (result != ValidationResult.OK)
|
if (result != ValidationResult.OK)
|
||||||
throw TransactionsResource.createTransactionInvalidException(request, result);
|
throw TransactionsResource.createTransactionInvalidException(request, result);
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ public class NamesResource {
|
|||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
ValidationResult result = transaction.isValid();
|
ValidationResult result = transaction.isValidUnconfirmed();
|
||||||
if (result != ValidationResult.OK)
|
if (result != ValidationResult.OK)
|
||||||
throw TransactionsResource.createTransactionInvalidException(request, result);
|
throw TransactionsResource.createTransactionInvalidException(request, result);
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ public class PaymentsResource {
|
|||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
ValidationResult result = transaction.isValid();
|
ValidationResult result = transaction.isValidUnconfirmed();
|
||||||
if (result != ValidationResult.OK)
|
if (result != ValidationResult.OK)
|
||||||
throw TransactionsResource.createTransactionInvalidException(request, result);
|
throw TransactionsResource.createTransactionInvalidException(request, result);
|
||||||
|
|
||||||
|
@ -319,7 +319,7 @@ public class TransactionsResource {
|
|||||||
if (!transaction.isSignatureValid())
|
if (!transaction.isSignatureValid())
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE);
|
||||||
|
|
||||||
ValidationResult result = transaction.isValid();
|
ValidationResult result = transaction.isValidUnconfirmed();
|
||||||
if (result != ValidationResult.OK)
|
if (result != ValidationResult.OK)
|
||||||
throw createTransactionInvalidException(request, result);
|
throw createTransactionInvalidException(request, result);
|
||||||
|
|
||||||
|
@ -111,6 +111,11 @@ public abstract class TransactionData {
|
|||||||
return Crypto.toAddress(this.creatorPublicKey);
|
return Crypto.toAddress(this.creatorPublicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@XmlTransient
|
||||||
|
public void setCreatorPublicKey(byte[] creatorPublicKey) {
|
||||||
|
this.creatorPublicKey = creatorPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
// Comparison
|
// Comparison
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -9,6 +9,7 @@ import org.apache.logging.log4j.Logger;
|
|||||||
import data.block.BlockData;
|
import data.block.BlockData;
|
||||||
import data.transaction.TransactionData;
|
import data.transaction.TransactionData;
|
||||||
import qora.account.PrivateKeyAccount;
|
import qora.account.PrivateKeyAccount;
|
||||||
|
import qora.account.PublicKeyAccount;
|
||||||
import qora.block.Block.ValidationResult;
|
import qora.block.Block.ValidationResult;
|
||||||
import qora.transaction.Transaction;
|
import qora.transaction.Transaction;
|
||||||
import repository.BlockRepository;
|
import repository.BlockRepository;
|
||||||
@ -47,6 +48,12 @@ public class BlockGenerator extends Thread {
|
|||||||
Thread.currentThread().setName("BlockGenerator");
|
Thread.currentThread().setName("BlockGenerator");
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
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);
|
generator = new PrivateKeyAccount(repository, generatorPrivateKey);
|
||||||
|
|
||||||
// Going to need this a lot...
|
// Going to need this a lot...
|
||||||
@ -109,10 +116,10 @@ public class BlockGenerator extends Thread {
|
|||||||
// Grab all unconfirmed transactions (already sorted)
|
// Grab all unconfirmed transactions (already sorted)
|
||||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getAllUnconfirmedTransactions();
|
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getAllUnconfirmedTransactions();
|
||||||
|
|
||||||
// Remove transactions that have timestamp later than block's timestamp (not yet valid)
|
unconfirmedTransactions.removeIf(transactionData -> !isSuitableTransaction(repository, transactionData, newBlock));
|
||||||
unconfirmedTransactions.removeIf(transactionData -> transactionData.getTimestamp() > newBlock.getBlockData().getTimestamp());
|
|
||||||
// Remove transactions that have expired deadline for this block
|
// Discard last-reference changes used to aid transaction validity checks
|
||||||
unconfirmedTransactions.removeIf(transactionData -> Transaction.fromData(repository, transactionData).getDeadline() <= newBlock.getBlockData().getTimestamp());
|
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)
|
for (TransactionData transactionData : unconfirmedTransactions)
|
||||||
@ -120,6 +127,40 @@ public class BlockGenerator extends Thread {
|
|||||||
break;
|
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() {
|
public void shutdown() {
|
||||||
this.running = false;
|
this.running = false;
|
||||||
// Interrupt too, absorbed by HSQLDB but could be caught by Thread.sleep()
|
// Interrupt too, absorbed by HSQLDB but could be caught by Thread.sleep()
|
||||||
|
@ -32,6 +32,10 @@ public class IssueAssetTransaction extends Transaction {
|
|||||||
super(repository, transactionData);
|
super(repository, transactionData);
|
||||||
|
|
||||||
this.issueAssetTransactionData = (IssueAssetTransactionData) this.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
|
// More information
|
||||||
|
@ -26,6 +26,10 @@ public class PaymentTransaction extends Transaction {
|
|||||||
super(repository, transactionData);
|
super(repository, transactionData);
|
||||||
|
|
||||||
this.paymentTransactionData = (PaymentTransactionData) this.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
|
// More information
|
||||||
|
@ -28,6 +28,10 @@ public class RegisterNameTransaction extends Transaction {
|
|||||||
super(repository, transactionData);
|
super(repository, transactionData);
|
||||||
|
|
||||||
this.registerNameTransactionData = (RegisterNameTransactionData) this.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
|
// 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.
|
* Returns whether transaction can be added to the blockchain.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -517,6 +517,11 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to delete transaction from repository", 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
|
# Transactions
|
||||||
TRANSACTION_NO_EXISTS=transaction does not exist
|
TRANSACTION_NO_EXISTS=transaction does not exist
|
||||||
PUBLIC_KEY_NOT_FOUND=public key not found
|
PUBLIC_KEY_NOT_FOUND=public key not found
|
||||||
|
TRANSACTION_INVALID=transaction invalid
|
||||||
|
|
||||||
# Names
|
# Names
|
||||||
NAME_NO_EXISTS=name does not exist
|
NAME_NO_EXISTS=name does not exist
|
||||||
|
Loading…
x
Reference in New Issue
Block a user