diff --git a/core/src/main/java/org/bitcoinj/coinjoin/utils/CoinJoinManager.java b/core/src/main/java/org/bitcoinj/coinjoin/utils/CoinJoinManager.java
index 7477db337..443606543 100644
--- a/core/src/main/java/org/bitcoinj/coinjoin/utils/CoinJoinManager.java
+++ b/core/src/main/java/org/bitcoinj/coinjoin/utils/CoinJoinManager.java
@@ -71,6 +71,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -507,10 +508,14 @@ public void processTransaction(Transaction tx) {
public final PreMessageReceivedEventListener preMessageReceivedEventListener = (peer, m) -> {
if (m instanceof CoinJoinQueue) {
// Offload DSQueue message processing to thread pool to avoid blocking network I/O thread
- messageProcessingExecutor.execute(() -> {
- processMessage(peer, m);
- });
- // Return null as dsq meessages are only processed above
+ try {
+ messageProcessingExecutor.execute(() -> {
+ processMessage(peer, m);
+ });
+ } catch (RejectedExecutionException e) {
+ // Executor was shutdown - ignore, we're shutting down
+ }
+ // Return null as dsq messages are only processed above
return null;
} else if (isCoinJoinMessage(m)) {
// Process other CoinJoin messages synchronously
diff --git a/core/src/main/java/org/bitcoinj/core/AbstractBlockChain.java b/core/src/main/java/org/bitcoinj/core/AbstractBlockChain.java
index 338465622..e41bb9d71 100644
--- a/core/src/main/java/org/bitcoinj/core/AbstractBlockChain.java
+++ b/core/src/main/java/org/bitcoinj/core/AbstractBlockChain.java
@@ -1134,8 +1134,8 @@ public void handleChainLock(StoredBlock chainLockedBlock) {
handleNewBestChain(prevBlock, newTip, newTip.getHeader(), shouldVerifyTransactions());
}
} catch (BlockStoreException | PrunedException x) {
- log.info("handle chain lock exception:", x);
- // swallow
+ log.debug("handle chain lock exception:", x);
+ // swallow - expected during shutdown
}
}
} finally {
diff --git a/core/src/main/java/org/bitcoinj/core/TxConfidenceTable.java b/core/src/main/java/org/bitcoinj/core/TxConfidenceTable.java
index f8d1f6636..ba351a530 100644
--- a/core/src/main/java/org/bitcoinj/core/TxConfidenceTable.java
+++ b/core/src/main/java/org/bitcoinj/core/TxConfidenceTable.java
@@ -202,14 +202,19 @@ public TransactionConfidence get(Sha256Hash hash) {
}
public ArrayList get(StoredBlock block) {
- ArrayList results = Lists.newArrayList();
- for (Map.Entry entry: table.entrySet()) {
- TransactionConfidence confidence = entry.getValue().get();
- if (confidence != null && confidence.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING &&
- confidence.getAppearedAtChainHeight() <= block.getHeight()) {
- results.add(confidence);
+ lock.lock();
+ try {
+ ArrayList results = Lists.newArrayList();
+ for (Map.Entry entry: table.entrySet()) {
+ TransactionConfidence confidence = entry.getValue().get();
+ if (confidence != null && confidence.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING &&
+ confidence.getAppearedAtChainHeight() <= block.getHeight()) {
+ results.add(confidence);
+ }
}
+ return results;
+ } finally {
+ lock.unlock();
}
- return results;
}
}
diff --git a/core/src/main/java/org/bitcoinj/utils/Threading.java b/core/src/main/java/org/bitcoinj/utils/Threading.java
index 9e60575da..e2b1fae45 100644
--- a/core/src/main/java/org/bitcoinj/utils/Threading.java
+++ b/core/src/main/java/org/bitcoinj/utils/Threading.java
@@ -31,6 +31,7 @@
import java.lang.management.ThreadMXBean;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Various threading related utilities. Provides a wrapper around explicit lock creation that lets you control whether
@@ -192,6 +193,13 @@ public static CycleDetectingLockFactory.Policy getPolicy() {
return policy;
}
+ public static ReentrantReadWriteLock readWriteLock(String name) {
+ if (Utils.isAndroidRuntime() && useDefaultAndroidPolicy)
+ return new ReentrantReadWriteLock(true);
+ else
+ return factory.newReentrantReadWriteLock(name, true);
+ }
+
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Generic worker pool.
diff --git a/core/src/main/java/org/bitcoinj/wallet/AbstractKeyChainGroupExtension.java b/core/src/main/java/org/bitcoinj/wallet/AbstractKeyChainGroupExtension.java
index f075239a4..05ff0502d 100644
--- a/core/src/main/java/org/bitcoinj/wallet/AbstractKeyChainGroupExtension.java
+++ b/core/src/main/java/org/bitcoinj/wallet/AbstractKeyChainGroupExtension.java
@@ -544,4 +544,9 @@ public void setWallet(Wallet wallet) {
public int getCombinedKeyLookaheadEpochs() {
return hasKeyChains() ? getKeyChainGroup().getCombinedKeyLookaheadEpochs() : 0;
}
+
+ @Override
+ public int getTotalIssuedKeys() {
+ return hasKeyChains() ? getKeyChainGroup().getTotalIssuedKeys() : 0;
+ }
}
diff --git a/core/src/main/java/org/bitcoinj/wallet/AnyKeyChainGroup.java b/core/src/main/java/org/bitcoinj/wallet/AnyKeyChainGroup.java
index 09cc45118..596ec6da5 100644
--- a/core/src/main/java/org/bitcoinj/wallet/AnyKeyChainGroup.java
+++ b/core/src/main/java/org/bitcoinj/wallet/AnyKeyChainGroup.java
@@ -651,6 +651,17 @@ public int numKeys() {
return result;
}
+ /**
+ * Returns number of keys used on external and internal paths.
+ */
+ public int getTotalIssuedKeys() {
+ int result = basic.numKeys();
+ if (chains != null)
+ for (AnyDeterministicKeyChain chain : chains)
+ result += chain.numLeafKeysIssued();
+ return result;
+ }
+
/**
* Removes a key that was imported into the basic key chain. You cannot remove deterministic keys.
* @throws IllegalArgumentException if the key is deterministic.
diff --git a/core/src/main/java/org/bitcoinj/wallet/CoinJoinExtension.java b/core/src/main/java/org/bitcoinj/wallet/CoinJoinExtension.java
index 42aee8f25..b7299807c 100644
--- a/core/src/main/java/org/bitcoinj/wallet/CoinJoinExtension.java
+++ b/core/src/main/java/org/bitcoinj/wallet/CoinJoinExtension.java
@@ -227,6 +227,9 @@ public void deserializeWalletExtension(Wallet containingWallet, byte[] data) thr
if (containingWallet instanceof WalletEx && coinJoinProto.hasOutpointRoundsCache()) {
WalletEx walletEx = (WalletEx) containingWallet;
Protos.OutpointRoundsCache cacheProto = coinJoinProto.getOutpointRoundsCache();
+ // Pre-size HashMap to avoid resizing during deserialization
+ int entryCount = cacheProto.getEntriesCount();
+ walletEx.mapOutpointRoundsCache = new HashMap<>(entryCount * 4 / 3 + 1);
for (Protos.OutpointRoundsEntry entryProto : cacheProto.getEntriesList()) {
Sha256Hash txHash = Sha256Hash.wrap(entryProto.getTransactionHash().toByteArray());
long outputIndex = entryProto.getOutputIndex();
diff --git a/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java b/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java
index 7ed2241f9..633919cf3 100644
--- a/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java
+++ b/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java
@@ -627,6 +627,17 @@ public int numKeys() {
return result;
}
+ /**
+ * Returns number of keys used on external and internal paths.
+ */
+ public int getTotalIssuedKeys() {
+ int result = basic.numKeys();
+ if (chains != null)
+ for (DeterministicKeyChain chain : chains)
+ result += chain.numLeafKeysIssued();
+ return result;
+ }
+
/**
* Removes a key that was imported into the basic key chain. You cannot remove deterministic keys.
* @throws java.lang.IllegalArgumentException if the key is deterministic.
diff --git a/core/src/main/java/org/bitcoinj/wallet/KeyChainGroupExtension.java b/core/src/main/java/org/bitcoinj/wallet/KeyChainGroupExtension.java
index 20f36be09..5b3ee19be 100644
--- a/core/src/main/java/org/bitcoinj/wallet/KeyChainGroupExtension.java
+++ b/core/src/main/java/org/bitcoinj/wallet/KeyChainGroupExtension.java
@@ -96,4 +96,5 @@ default String toString(boolean includeLookahead, boolean includePrivateKeys, @N
boolean isTransactionRevelant(Transaction tx);
int getCombinedKeyLookaheadEpochs();
+ int getTotalIssuedKeys();
}
diff --git a/core/src/main/java/org/bitcoinj/wallet/Wallet.java b/core/src/main/java/org/bitcoinj/wallet/Wallet.java
index be17cca03..6974f841f 100644
--- a/core/src/main/java/org/bitcoinj/wallet/Wallet.java
+++ b/core/src/main/java/org/bitcoinj/wallet/Wallet.java
@@ -133,7 +133,7 @@ public class Wallet extends BaseTaggableObject
// Ordering: lock > keyChainGroupLock. KeyChainGroup is protected separately to allow fast querying of current receive address
// even if the wallet itself is busy e.g. saving or processing a big reorg. Useful for reducing UI latency.
- protected final ReentrantLock lock = Threading.lock("wallet");
+ protected final ReentrantReadWriteLock lock = Threading.readWriteLock("wallet");
protected final ReentrantLock keyChainGroupLock = Threading.lock("wallet-keychaingroup");
// The various pools below give quick access to wallet-relevant transactions by the state they're in:
@@ -521,17 +521,17 @@ public void onConfidenceChanged(TransactionConfidence confidence, TransactionCon
// to us to listen for that. Other types of confidence changes (type, etc) are triggered by us,
// so we'll queue up a wallet change event in other parts of the code.
if (reason == ChangeReason.SEEN_PEERS) {
- lock.lock();
+ lock.writeLock().lock();
try {
checkBalanceFuturesLocked(null);
Transaction tx = getTransaction(confidence.getTransactionHash());
queueOnTransactionConfidenceChanged(tx);
maybeQueueOnWalletChanged();
} finally {
- lock.unlock();
+ lock.writeLock().unlock();
}
} else if(reason == ChangeReason.IX_TYPE || reason == ChangeReason.REJECT) {
- lock.lock();
+ lock.writeLock().lock();
try {
Transaction tx = getTransaction(confidence.getTransactionHash());
queueOnTransactionConfidenceChanged(tx);
@@ -539,7 +539,7 @@ public void onConfidenceChanged(TransactionConfidence confidence, TransactionCon
//save the wallet when an InstantSend transaction is locked
saveLater();
} finally {
- lock.unlock();;
+ lock.writeLock().unlock();
}
}
}
@@ -583,23 +583,23 @@ public DeterministicKeyChain getActiveKeyChain() {
* will be thrown
*/
public final void addTransactionSigner(TransactionSigner signer) {
- lock.lock();
+ lock.writeLock().lock();
try {
if (signer.isReady())
signers.add(signer);
else
throw new IllegalStateException("Signer instance is not ready to be added into Wallet: " + signer.getClass());
} finally {
- lock.unlock();
+ lock.writeLock().unlock();
}
}
public List getTransactionSigners() {
- lock.lock();
+ lock.readLock().lock();
try {
return ImmutableList.copyOf(signers);
} finally {
- lock.unlock();
+ lock.readLock().unlock();
}
}
@@ -859,20 +859,20 @@ public boolean removeKey(ECKey key) {
* Returns the number of keys in the key chain group, including lookahead keys.
*/
public int getKeyChainGroupSize() {
- lock.lock();
+ lock.readLock().lock();
try {
keyChainGroupLock.lock();
try {
- int walletKeys = keyChainGroup.numKeys();
- if (receivingFromFriendsGroup != null) walletKeys += receivingFromFriendsGroup.numKeys();
- if (sendingToFriendsGroup != null) walletKeys += sendingToFriendsGroup.numKeys();
- for (KeyChainGroupExtension ext : keyChainExtensions.values()) walletKeys += ext.numKeys();
+ int walletKeys = keyChainGroup.getTotalIssuedKeys();
+ if (receivingFromFriendsGroup != null) walletKeys += receivingFromFriendsGroup.getTotalIssuedKeys();
+ if (sendingToFriendsGroup != null) walletKeys += sendingToFriendsGroup.getTotalIssuedKeys();
+ for (KeyChainGroupExtension ext : keyChainExtensions.values()) walletKeys += ext.getTotalIssuedKeys();
return walletKeys;
} finally {
keyChainGroupLock.unlock();
}
} finally {
- lock.unlock();
+ lock.readLock().unlock();
}
}
@@ -1142,7 +1142,7 @@ public boolean removeWatchedAddresses(final List addresses) {
* @return true if successful
*/
public boolean removeWatchedScripts(final List