Permalink
Browse files

allow limiting unconfirmed transaction pool size

  • Loading branch information...
1 parent cf1c661 commit 73b6a75681dd5661dbded3c8a6938e13934dbd3f Jean-Luc Picard committed Apr 13, 2015
@@ -19,6 +19,14 @@ Reverted DGS goods delivery maximum size to 10 kbytes, same as in 1.4. For
goods of a larger size, a prunable encrypted message should be included in the
delivery instead.
+Limit the number of unconfirmed transactions that can be brought in memory to
+nxt.maxUnconfirmedTransactions, no limit by default. If set, transactions with
+lowest fee/size ratio are dropped from the unconfirmed pool first. The ordering
+is same as the one used when selecting which transactions to include in a new
+block.
+
+Added requeueUnconfirmedTransactions debug API.
+
Added support for prunable plain and encrypted messages in the client UI.
Voting system UI improvements and bugfixes.
@@ -143,6 +143,12 @@ nxt.isOffline=false
# risk of revealing that this node is the submitter of such re-broadcasted new transactions.
nxt.enableTransactionRebroadcasting=true
+# Do not allow the number of unconfirmed transactions in memory to exceed this value.
+# If exceeded, the same transaction ordering as when choosing which transactions to include
+# in a forged block is used to decide which to keep in the unconfirmed pool.
+# Set to 0 or negative for no limit.
+nxt.maxUnconfirmedTransactions=0
+
# Verify batches of blocks downloaded from a single peer with that many other peers.
nxt.numberOfForkConfirmations=2
nxt.testnetNumberOfForkConfirmations=0
@@ -22,6 +22,8 @@
void clearUnconfirmedTransactions();
+ void requeueAllUnconfirmedTransactions();
+
void broadcast(Transaction transaction) throws NxtException.ValidationException;
void processPeerTransactions(JSONObject request) throws NxtException.ValidationException;
@@ -24,17 +24,22 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
final class TransactionProcessorImpl implements TransactionProcessor {
private static final boolean enableTransactionRebroadcasting = Nxt.getBooleanProperty("nxt.enableTransactionRebroadcasting");
private static final boolean testUnconfirmedTransactions = Nxt.getBooleanProperty("nxt.testUnconfirmedTransactions");
+ private static final int maxUnconfirmedTransactions;
+ static {
+ int n = Nxt.getIntProperty("nxt.maxUnconfirmedTransactions");
+ maxUnconfirmedTransactions = n <= 0 ? Integer.MAX_VALUE : n;
+ }
private static final TransactionProcessorImpl instance = new TransactionProcessorImpl();
@@ -90,7 +95,37 @@ protected String defaultSort() {
private final Set<TransactionImpl> broadcastedTransactions = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final Listeners<List<? extends Transaction>,Event> transactionListeners = new Listeners<>();
- private final Set<UnconfirmedTransaction> lostTransactions = new HashSet<>();
+
+ private final PriorityQueue<UnconfirmedTransaction> lostTransactions = new PriorityQueue<UnconfirmedTransaction>(
+ (UnconfirmedTransaction o1, UnconfirmedTransaction o2) -> {
+ int result;
+ if ((result = Integer.compare(o2.getHeight(), o1.getHeight())) != 0) {
+ return result;
+ }
+ if ((result = Long.compare(o1.getFeePerByte(), o2.getFeePerByte())) != 0) {
+ return result;
+ }
+ if ((result = Long.compare(o2.getArrivalTimestamp(), o1.getArrivalTimestamp())) != 0) {
+ return result;
+ }
+ return Long.compare(o2.getId(), o1.getId());
+ })
+ {
+
+ @Override
+ public boolean add(UnconfirmedTransaction unconfirmedTransaction) {
+ if (!super.add(unconfirmedTransaction)) {
+ return false;
+ }
+ if (size() > maxUnconfirmedTransactions) {
+ UnconfirmedTransaction removed = remove();
+ Logger.logDebugMessage("Dropped unconfirmed transaction " + removed.getJSONObject().toJSONString());
+ }
+ return true;
+ }
+
+ };
+
private final Map<TransactionType, Map<String, Boolean>> unconfirmedDuplicates = new HashMap<>();
@@ -313,18 +348,37 @@ public void clearUnconfirmedTransactions() {
}
}
- void requeueAllUnconfirmedTransactions() {
- List<Transaction> removed = new ArrayList<>();
- try (DbIterator<UnconfirmedTransaction> unconfirmedTransactions = getAllUnconfirmedTransactions()) {
- for (UnconfirmedTransaction unconfirmedTransaction : unconfirmedTransactions) {
- unconfirmedTransaction.getTransaction().undoUnconfirmed();
- removed.add(unconfirmedTransaction.getTransaction());
- lostTransactions.add(unconfirmedTransaction);
+ @Override
+ public void requeueAllUnconfirmedTransactions() {
+ synchronized (BlockchainImpl.getInstance()) {
+ if (!Db.db.isInTransaction()) {
+ try {
+ Db.db.beginTransaction();
+ requeueAllUnconfirmedTransactions();
+ Db.db.commitTransaction();
+ } catch (Exception e) {
+ Logger.logErrorMessage(e.toString(), e);
+ Db.db.rollbackTransaction();
+ throw e;
+ } finally {
+ Db.db.endTransaction();
+ }
+ return;
}
+ List<Transaction> removed = new ArrayList<>();
+ try (DbIterator<UnconfirmedTransaction> unconfirmedTransactions = getAllUnconfirmedTransactions()) {
+ for (UnconfirmedTransaction unconfirmedTransaction : unconfirmedTransactions) {
+ unconfirmedTransaction.getTransaction().undoUnconfirmed();
+ if (removed.size() < maxUnconfirmedTransactions) {
+ removed.add(unconfirmedTransaction.getTransaction());
+ }
+ lostTransactions.add(unconfirmedTransaction);
+ }
+ }
+ unconfirmedTransactionTable.truncate();
+ unconfirmedDuplicates.clear();
+ transactionListeners.notify(removed, Event.REMOVED_UNCONFIRMED_TRANSACTIONS);
}
- unconfirmedTransactionTable.truncate();
- unconfirmedDuplicates.clear();
- transactionListeners.notify(removed, Event.REMOVED_UNCONFIRMED_TRANSACTIONS);
}
void removeUnconfirmedTransaction(TransactionImpl transaction) {
@@ -378,7 +432,7 @@ private void processLostTransactions() {
iterator.remove();
addedUnconfirmedTransactions.add(unconfirmedTransaction.getTransaction());
} catch (NxtException.NotCurrentlyValidException ignore) {
- if (unconfirmedTransaction.getExpiration() < Nxt.getEpochTime() || lostTransactions.size() > 1000) {
+ if (unconfirmedTransaction.getExpiration() < Nxt.getEpochTime()) {
iterator.remove();
}
} catch (NxtException.ValidationException|RuntimeException e) {
@@ -14,10 +14,12 @@
private final TransactionImpl transaction;
private final long arrivalTimestamp;
+ private final long feePerByte;
UnconfirmedTransaction(TransactionImpl transaction, long arrivalTimestamp) {
this.transaction = transaction;
this.arrivalTimestamp = arrivalTimestamp;
+ this.feePerByte = transaction.getFeeNQT() / transaction.getFullSize();
}
UnconfirmedTransaction(ResultSet rs) throws SQLException {
@@ -33,6 +35,7 @@
this.transaction = builder.build();
this.transaction.setHeight(rs.getInt("transaction_height"));
this.arrivalTimestamp = rs.getLong("arrival_timestamp");
+ this.feePerByte = rs.getLong("fee_per_byte");
} catch (NxtException.ValidationException e) {
throw new RuntimeException(e.toString(), e);
}
@@ -45,7 +48,7 @@ void save(Connection con) throws SQLException {
int i = 0;
pstmt.setLong(++i, transaction.getId());
pstmt.setInt(++i, transaction.getHeight());
- pstmt.setLong(++i, transaction.getFeeNQT() / transaction.getFullSize());
+ pstmt.setLong(++i, feePerByte);
pstmt.setInt(++i, transaction.getExpiration());
pstmt.setBytes(++i, transaction.bytes());
JSONObject prunableJSON = null;
@@ -77,6 +80,10 @@ long getArrivalTimestamp() {
return arrivalTimestamp;
}
+ long getFeePerByte() {
+ return feePerByte;
+ }
+
@Override
public boolean equals(Object o) {
return o instanceof UnconfirmedTransaction && transaction.equals(((UnconfirmedTransaction)o).getTransaction());
@@ -243,6 +243,7 @@ boolean requirePassword() {
map.put("searchPolls", SearchPolls.instance);
map.put("searchAccounts", SearchAccounts.instance);
map.put("clearUnconfirmedTransactions", ClearUnconfirmedTransactions.instance);
+ map.put("requeueAllUnconfirmedTransactions", RequeueUnconfirmedTransactions.instance);
map.put("fullReset", FullReset.instance);
map.put("popOff", PopOff.instance);
map.put("scan", Scan.instance);
@@ -0,0 +1,38 @@
+package nxt.http;
+
+import nxt.Nxt;
+import org.json.simple.JSONObject;
+import org.json.simple.JSONStreamAware;
+
+import javax.servlet.http.HttpServletRequest;
+
+public final class RequeueUnconfirmedTransactions extends APIServlet.APIRequestHandler {
+
+ static final RequeueUnconfirmedTransactions instance = new RequeueUnconfirmedTransactions();
+
+ private RequeueUnconfirmedTransactions() {
+ super(new APITag[] {APITag.DEBUG});
+ }
+
+ @Override
+ JSONStreamAware processRequest(HttpServletRequest req) {
+ JSONObject response = new JSONObject();
+ try {
+ Nxt.getTransactionProcessor().requeueAllUnconfirmedTransactions();
+ response.put("done", true);
+ } catch (RuntimeException e) {
+ JSONData.putException(response, e);
+ }
+ return response;
+ }
+
+ @Override
+ final boolean requirePost() {
+ return true;
+ }
+
+ @Override
+ boolean requirePassword() {
+ return true;
+ }
+}

0 comments on commit 73b6a75

Please sign in to comment.