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