diff --git a/core/src/main/java/com/google/bitcoin/core/MemoryPool.java b/core/src/main/java/com/google/bitcoin/core/MemoryPool.java index 6767e297..b17bf5b0 100644 --- a/core/src/main/java/com/google/bitcoin/core/MemoryPool.java +++ b/core/src/main/java/com/google/bitcoin/core/MemoryPool.java @@ -16,7 +16,7 @@ package com.google.bitcoin.core; -import com.google.common.base.Preconditions; +import com.google.bitcoin.utils.Locks; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,6 +27,10 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; /** *
Tracks transactions that are being announced across the network. Typically one is created for you by a
@@ -41,6 +45,7 @@ import java.util.Set;
*/
public class MemoryPool {
private static final Logger log = LoggerFactory.getLogger(MemoryPool.class);
+ protected ReentrantLock lock = Locks.lock("mempool");
// For each transaction we may have seen:
// - only its hash in an inv packet
@@ -108,41 +113,51 @@ public class MemoryPool {
* which bothered to keep a reference. Typically, this is because the transaction does not involve any keys that
* are relevant to any of our wallets.
*/
- private synchronized void cleanPool() {
- Reference extends Transaction> ref;
- while ((ref = referenceQueue.poll()) != null) {
- // Find which transaction got deleted by the GC.
- WeakTransactionReference txRef = (WeakTransactionReference) ref;
- // And remove the associated map entry so the other bits of memory can also be reclaimed.
- memoryPool.remove(txRef.hash);
+ private void cleanPool() {
+ lock.lock();
+ try {
+ Reference extends Transaction> ref;
+ while ((ref = referenceQueue.poll()) != null) {
+ // Find which transaction got deleted by the GC.
+ WeakTransactionReference txRef = (WeakTransactionReference) ref;
+ // And remove the associated map entry so the other bits of memory can also be reclaimed.
+ memoryPool.remove(txRef.hash);
+ }
+ } finally {
+ lock.unlock();
}
}
/**
* Returns the number of peers that have seen the given hash recently.
*/
- public synchronized int numBroadcastPeers(Sha256Hash txHash) {
- cleanPool();
- Entry entry = memoryPool.get(txHash);
- if (entry == null) {
- // No such TX known.
- return 0;
- } else if (entry.tx == null) {
- // We've seen at least one peer announce with an inv.
- Preconditions.checkNotNull(entry.addresses);
- return entry.addresses.size();
- } else {
- final Transaction tx = entry.tx.get();
- if (tx == null) {
- // We previously downloaded this transaction, but nothing cared about it so the garbage collector threw
- // it away. We also deleted the set that tracked which peers had seen it. Treat this case as a zero and
- // just delete it from the map.
- memoryPool.remove(txHash);
+ public int numBroadcastPeers(Sha256Hash txHash) {
+ lock.lock();
+ try {
+ cleanPool();
+ Entry entry = memoryPool.get(txHash);
+ if (entry == null) {
+ // No such TX known.
return 0;
+ } else if (entry.tx == null) {
+ // We've seen at least one peer announce with an inv.
+ checkNotNull(entry.addresses);
+ return entry.addresses.size();
} else {
- Preconditions.checkState(entry.addresses == null);
- return tx.getConfidence().numBroadcastPeers();
+ final Transaction tx = entry.tx.get();
+ if (tx == null) {
+ // We previously downloaded this transaction, but nothing cared about it so the garbage collector threw
+ // it away. We also deleted the set that tracked which peers had seen it. Treat this case as a zero and
+ // just delete it from the map.
+ memoryPool.remove(txHash);
+ return 0;
+ } else {
+ checkState(entry.addresses == null);
+ return tx.getConfidence().numBroadcastPeers();
+ }
}
+ } finally {
+ lock.unlock();
}
}
@@ -152,54 +167,65 @@ public class MemoryPool {
* @param byPeer The Peer that received it.
* @return An object that is semantically the same TX but may be a different object instance.
*/
- public synchronized Transaction seen(Transaction tx, PeerAddress byPeer) {
- cleanPool();
- Entry entry = memoryPool.get(tx.getHash());
- if (entry != null) {
- // This TX or its hash have been previously announced.
- if (entry.tx != null) {
- // We already downloaded it.
- Preconditions.checkState(entry.addresses == null);
- // We only want one canonical object instance for a transaction no matter how many times it is
- // deserialized.
- Transaction transaction = entry.tx.get();
- if (transaction == null) {
- // We previously downloaded this transaction, but the garbage collector threw it away because
- // no other part of the system cared enough to keep it around (it's not relevant to us).
- // Given the lack of interest last time we probably don't need to track it this time either.
- log.info("{}: Provided with a transaction that we previously threw away: {}", byPeer, tx.getHash());
+ public Transaction seen(Transaction tx, PeerAddress byPeer) {
+ lock.lock();
+ try {
+ cleanPool();
+ Entry entry = memoryPool.get(tx.getHash());
+ if (entry != null) {
+ // This TX or its hash have been previously announced.
+ if (entry.tx != null) {
+ // We already downloaded it.
+ checkState(entry.addresses == null);
+ // We only want one canonical object instance for a transaction no matter how many times it is
+ // deserialized.
+ Transaction transaction = entry.tx.get();
+ if (transaction == null) {
+ // We previously downloaded this transaction, but the garbage collector threw it away because
+ // no other part of the system cared enough to keep it around (it's not relevant to us).
+ // Given the lack of interest last time we probably don't need to track it this time either.
+ log.info("{}: Provided with a transaction that we previously threw away: {}", byPeer, tx.getHash());
+ } else {
+ // We saw it before and kept it around. Hand back the canonical copy.
+ tx = transaction;
+ log.info("{}: Provided with a transaction downloaded before: [{}] {}",
+ new Object[]{byPeer, tx.getConfidence().numBroadcastPeers(), tx.getHash()});
+ }
+ markBroadcast(byPeer, tx);
+ return tx;
} else {
- // We saw it before and kept it around. Hand back the canonical copy.
- tx = transaction;
- log.info("{}: Provided with a transaction downloaded before: [{}] {}",
- new Object[] { byPeer, tx.getConfidence().numBroadcastPeers(), tx.getHash() });
+ // We received a transaction that we have previously seen announced but not downloaded until now.
+ checkNotNull(entry.addresses);
+ entry.tx = new WeakTransactionReference(tx, referenceQueue);
+ // Copy the previously announced peers into the confidence and then clear it out. Unlock here
+ // because markBroadcastBy can trigger event listeners and thus inversions.
+ lock.unlock();
+ try {
+ TransactionConfidence confidence = tx.getConfidence();
+ for (PeerAddress a : entry.addresses) {
+ confidence.markBroadcastBy(a);
+ }
+ entry.addresses = null;
+ log.debug("{}: Adding tx [{}] {} to the memory pool",
+ new Object[]{byPeer, confidence.numBroadcastPeers(), tx.getHashAsString()});
+ } finally {
+ lock.lock();
+ }
+ return tx;
}
- tx.getConfidence().markBroadcastBy(byPeer);
- return tx;
} else {
- // We received a transaction that we have previously seen announced but not downloaded until now.
- Preconditions.checkNotNull(entry.addresses);
+ // This often happens when we are downloading a Bloom filtered chain, or recursively downloading
+ // dependencies of a relevant transaction (see Peer.downloadDependencies).
+ log.debug("{}: Provided with a downloaded transaction we didn't see announced yet: {}",
+ byPeer, tx.getHashAsString());
+ entry = new Entry();
entry.tx = new WeakTransactionReference(tx, referenceQueue);
- // Copy the previously announced peers into the confidence and then clear it out.
- TransactionConfidence confidence = tx.getConfidence();
- for (PeerAddress a : entry.addresses) {
- confidence.markBroadcastBy(a);
- }
- entry.addresses = null;
- log.debug("{}: Adding tx [{}] {} to the memory pool",
- new Object[] { byPeer, confidence.numBroadcastPeers(), tx.getHashAsString() });
+ memoryPool.put(tx.getHash(), entry);
+ markBroadcast(byPeer, tx);
return tx;
}
- } else {
- // This often happens when we are downloading a Bloom filtered chain, or recursively downloading
- // dependencies of a relevant transaction (see Peer.downloadDependencies).
- log.debug("{}: Provided with a downloaded transaction we didn't see announced yet: {}",
- byPeer, tx.getHashAsString());
- entry = new Entry();
- entry.tx = new WeakTransactionReference(tx, referenceQueue);
- memoryPool.put(tx.getHash(), entry);
- tx.getConfidence().markBroadcastBy(byPeer);
- return tx;
+ } finally {
+ lock.unlock();
}
}
@@ -207,36 +233,53 @@ public class MemoryPool {
* Called by peers when they see a transaction advertised in an "inv" message. It either will increase the
* confidence of the pre-existing transaction or will just keep a record of the address for future usage.
*/
- public synchronized void seen(Sha256Hash hash, PeerAddress byPeer) {
- cleanPool();
- Entry entry = memoryPool.get(hash);
- if (entry != null) {
- // This TX or its hash have been previously announced.
- if (entry.tx != null) {
- Preconditions.checkState(entry.addresses == null);
- Transaction tx = entry.tx.get();
- if (tx != null) {
- tx.getConfidence().markBroadcastBy(byPeer);
- log.debug("{}: Announced transaction we have seen before [{}] {}",
- new Object[] { byPeer, tx.getConfidence().numBroadcastPeers(), tx.getHashAsString() });
+ public void seen(Sha256Hash hash, PeerAddress byPeer) {
+ lock.lock();
+ try {
+ cleanPool();
+ Entry entry = memoryPool.get(hash);
+ if (entry != null) {
+ // This TX or its hash have been previously announced.
+ if (entry.tx != null) {
+ checkState(entry.addresses == null);
+ Transaction tx = entry.tx.get();
+ if (tx != null) {
+ markBroadcast(byPeer, tx);
+ log.debug("{}: Announced transaction we have seen before [{}] {}",
+ new Object[]{byPeer, tx.getConfidence().numBroadcastPeers(), tx.getHashAsString()});
+ } else {
+ // The inv is telling us about a transaction that we previously downloaded, and threw away because
+ // nothing found it interesting enough to keep around. So do nothing.
+ }
} else {
- // The inv is telling us about a transaction that we previously downloaded, and threw away because
- // nothing found it interesting enough to keep around. So do nothing.
+ checkNotNull(entry.addresses);
+ entry.addresses.add(byPeer);
+ log.debug("{}: Announced transaction we have seen announced before [{}] {}",
+ new Object[]{byPeer, entry.addresses.size(), hash});
}
} else {
- Preconditions.checkNotNull(entry.addresses);
+ // This TX has never been seen before.
+ entry = new Entry();
+ // TODO: Using hashsets here is inefficient compared to just having an array.
+ entry.addresses = new HashSet