mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-02-25 07:07:39 +01:00
Introduce a user code thread which is used for running event listeners.
This ensures that when user provided event listeners are invoked, we're not holding any locks at that time and thus event listeners can do whatever they want with no risk of accidental inversions or deadlocks. A utility method is available to wait for all preceding events that were triggered to complete, which is useful for unit tests. Reimplement how balance futures work in order to avoid the wallet registering an event handler on itself, this means you cannot accidentally deadlock yourself by running getBalanceFuture().get() inside an event listener. Future changes will modify how transaction confidence listeners are run to work the same way, and make other kinds of event listener run in the user code thread as well. The user code mechanism is usable with any executor, opening up the possibility of automatically relaying event listeners into GUI threads for some kinds of apps.
This commit is contained in:
parent
b87879fd42
commit
5de80dfedf
7 changed files with 206 additions and 89 deletions
|
@ -28,11 +28,14 @@ import com.google.common.base.Preconditions;
|
|||
import com.google.common.collect.*;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import com.google.common.util.concurrent.Uninterruptibles;
|
||||
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
|
@ -294,7 +297,8 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||
public void onConfidenceChanged(Transaction tx) {
|
||||
lock.lock();
|
||||
// The invokers unlock us immediately so if an exception is thrown, the lock will be already open.
|
||||
invokeOnTransactionConfidenceChanged(tx);
|
||||
checkBalanceFuturesLocked(null);
|
||||
queueOnTransactionConfidenceChanged(tx);
|
||||
// Many onWalletChanged events will not occur because they are suppressed, eg, because:
|
||||
// - we are inside a re-org
|
||||
// - we are in the middle of processing a block
|
||||
|
@ -304,7 +308,7 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||
// - the tx is pending and was killed by a detected double spend that was not in a block
|
||||
// The latter case cannot happen today because we won't hear about it, but in future this may
|
||||
// become more common if conflict notices are implemented.
|
||||
invokeOnWalletChanged();
|
||||
maybeQueueOnWalletChanged();
|
||||
lock.unlock();
|
||||
}
|
||||
};
|
||||
|
@ -856,7 +860,8 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||
tx.getConfidence().setConfidenceType(ConfidenceType.PENDING);
|
||||
// Manually invoke the wallet tx confidence listener here as we didn't yet commit therefore the
|
||||
// txConfidenceListener wasn't added.
|
||||
invokeOnTransactionConfidenceChanged(tx);
|
||||
checkBalanceFuturesLocked(null);
|
||||
queueOnTransactionConfidenceChanged(tx);
|
||||
}
|
||||
// If this tx spends any of our unspent outputs, mark them as spent now, then add to the pending pool. This
|
||||
// ensures that if some other client that has our keys broadcasts a spend we stay in sync. Also updates the
|
||||
|
@ -1088,12 +1093,13 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||
// We pick one callback based on the value difference, though a tx can of course both send and receive
|
||||
// coins from the wallet.
|
||||
if (diff > 0) {
|
||||
invokeOnCoinsReceived(tx, prevBalance, newBalance);
|
||||
checkBalanceFuturesLocked(newBalance);
|
||||
queueOnCoinsReceived(tx, prevBalance, newBalance);
|
||||
} else if (diff < 0) {
|
||||
invokeOnCoinsSent(tx, prevBalance, newBalance);
|
||||
queueOnCoinsSent(tx, prevBalance, newBalance);
|
||||
} else {
|
||||
// We have a transaction that didn't change our balance. Probably we sent coins between our own keys.
|
||||
invokeOnWalletChanged();
|
||||
maybeQueueOnWalletChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1139,7 +1145,7 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||
}
|
||||
queueAutoSave();
|
||||
onWalletChangedSuppressions--;
|
||||
invokeOnWalletChanged();
|
||||
maybeQueueOnWalletChanged();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
@ -1383,12 +1389,14 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||
BigInteger valueSentFromMe = tx.getValueSentFromMe(this);
|
||||
BigInteger valueSentToMe = tx.getValueSentToMe(this);
|
||||
BigInteger newBalance = balance.add(valueSentToMe).subtract(valueSentFromMe);
|
||||
if (valueSentToMe.compareTo(BigInteger.ZERO) > 0)
|
||||
invokeOnCoinsReceived(tx, balance, newBalance);
|
||||
if (valueSentToMe.compareTo(BigInteger.ZERO) > 0) {
|
||||
checkBalanceFuturesLocked(null);
|
||||
queueOnCoinsReceived(tx, balance, newBalance);
|
||||
}
|
||||
if (valueSentFromMe.compareTo(BigInteger.ZERO) > 0)
|
||||
invokeOnCoinsSent(tx, balance, newBalance);
|
||||
queueOnCoinsSent(tx, balance, newBalance);
|
||||
|
||||
invokeOnWalletChanged();
|
||||
maybeQueueOnWalletChanged();
|
||||
} catch (ScriptException e) {
|
||||
// Cannot happen as we just created this transaction ourselves.
|
||||
throw new RuntimeException(e);
|
||||
|
@ -2444,11 +2452,13 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||
}
|
||||
notifyNewBestBlock(block);
|
||||
}
|
||||
log.info("post-reorg balance is {}", Utils.bitcoinValueToFriendlyString(getBalance()));
|
||||
final BigInteger balance = getBalance();
|
||||
log.info("post-reorg balance is {}", Utils.bitcoinValueToFriendlyString(balance));
|
||||
// Inform event listeners that a re-org took place. They should save the wallet at this point.
|
||||
invokeOnReorganize();
|
||||
checkBalanceFuturesLocked(balance);
|
||||
queueOnReorganize();
|
||||
onWalletChangedSuppressions--;
|
||||
invokeOnWalletChanged();
|
||||
maybeQueueOnWalletChanged();
|
||||
checkState(isConsistent());
|
||||
} finally {
|
||||
lock.unlock();
|
||||
|
@ -2934,43 +2944,78 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||
setCoinSelector(Wallet.AllowUnconfirmedCoinSelector.get());
|
||||
}
|
||||
|
||||
private static class BalanceFutureRequest {
|
||||
public SettableFuture<BigInteger> future;
|
||||
public BigInteger value;
|
||||
public BalanceType type;
|
||||
}
|
||||
@GuardedBy("lock") private List<BalanceFutureRequest> balanceFutureRequests = Lists.newLinkedList();
|
||||
|
||||
/**
|
||||
* Returns a future that will complete when the balance of the given type is equal or larger to the given value.
|
||||
* If the wallet already has a large enough balance the future is returned in a pre-completed state. Note that this
|
||||
* method is not blocking, if you want to <i>actually</i> wait immediately, you have to call .get() on the result.
|
||||
* <p>Returns a future that will complete when the balance of the given type has becom equal or larger to the given
|
||||
* value. If the wallet already has a large enough balance the future is returned in a pre-completed state. Note
|
||||
* that this method is not blocking, if you want to actually wait immediately, you have to call .get() on
|
||||
* the result.</p>
|
||||
*
|
||||
* <p>Also note that by the time the future completes, the wallet may have changed yet again if something else
|
||||
* is going on in parallel, so you should treat the returned balance as advisory and be prepared for sending
|
||||
* money to fail! Finally please be aware that any listeners on the future will run either on the calling thread
|
||||
* if it completes immediately, or eventually on a background thread if the balance is not yet at the right
|
||||
* level. If you do something that means you know the balance should be sufficient to trigger the future,
|
||||
* you can use {@link com.google.bitcoin.utils.Threading#waitForUserCode()} to block until the future had a
|
||||
* chance to be updated.</p>
|
||||
*/
|
||||
public ListenableFuture<BigInteger> getBalanceFuture(final BigInteger value, final BalanceType type) {
|
||||
lock.lock();
|
||||
try {
|
||||
final SettableFuture<BigInteger> future = SettableFuture.create();
|
||||
final BigInteger current = getBalance(type);
|
||||
if (current.compareTo(value) >= 0) {
|
||||
// Already have enough.
|
||||
future.set(current);
|
||||
} else {
|
||||
// Will be checked later in checkBalanceFutures. We don't just add an event listener for ourselves
|
||||
// here so that running getBalanceFuture().get() in the user code thread works - generally we must
|
||||
// avoid giving the user back futures that require the user code thread to be free.
|
||||
BalanceFutureRequest req = new BalanceFutureRequest();
|
||||
req.future = future;
|
||||
req.value = value;
|
||||
req.type = type;
|
||||
balanceFutureRequests.add(req);
|
||||
}
|
||||
return future;
|
||||
}
|
||||
addEventListener(new AbstractWalletEventListener() {
|
||||
private boolean done = false;
|
||||
|
||||
@Override
|
||||
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
|
||||
check();
|
||||
}
|
||||
|
||||
private void check() {
|
||||
final BigInteger newBalance = getBalance(type);
|
||||
if (!done && newBalance.compareTo(value) >= 0) {
|
||||
// Have enough now.
|
||||
done = true;
|
||||
removeEventListener(this);
|
||||
future.set(newBalance);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCoinsReceived(Wallet w, Transaction t, BigInteger b1, BigInteger b2) {
|
||||
check();
|
||||
// Runs any balance futures in the user code thread.
|
||||
private void checkBalanceFuturesLocked(@Nullable BigInteger avail) {
|
||||
checkState(lock.isLocked());
|
||||
BigInteger estimated = null;
|
||||
final ListIterator<BalanceFutureRequest> it = balanceFutureRequests.listIterator();
|
||||
while (it.hasNext()) {
|
||||
final BalanceFutureRequest req = it.next();
|
||||
BigInteger val = null;
|
||||
if (req.type == BalanceType.AVAILABLE) {
|
||||
if (avail == null) avail = getBalance(BalanceType.AVAILABLE);
|
||||
if (avail.compareTo(req.value) < 0) continue;
|
||||
val = avail;
|
||||
} else if (req.type == BalanceType.ESTIMATED) {
|
||||
if (estimated == null) estimated = getBalance(BalanceType.ESTIMATED);
|
||||
if (estimated.compareTo(req.value) < 0) continue;
|
||||
val = estimated;
|
||||
}
|
||||
// Found one that's finished.
|
||||
it.remove();
|
||||
final BigInteger v = checkNotNull(val);
|
||||
// Don't run any user-provided future listeners with our lock held.
|
||||
Threading.userCode.execute(new Runnable() {
|
||||
@Override public void run() {
|
||||
req.future.set(v);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -3041,70 +3086,66 @@ public class Wallet implements Serializable, BlockChainListener {
|
|||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Boilerplate for running event listeners - unlocks the wallet, runs, re-locks.
|
||||
// Boilerplate for running event listeners - dispatches events onto the user code thread (where we don't do
|
||||
// anything and hold no locks).
|
||||
|
||||
private void invokeOnTransactionConfidenceChanged(Transaction tx) {
|
||||
private void queueOnTransactionConfidenceChanged(final Transaction tx) {
|
||||
checkState(lock.isLocked());
|
||||
lock.unlock();
|
||||
try {
|
||||
for (WalletEventListener listener : eventListeners) {
|
||||
listener.onTransactionConfidenceChanged(this, tx);
|
||||
for (final WalletEventListener listener : eventListeners) {
|
||||
Threading.userCode.execute(new Runnable() {
|
||||
@Override public void run() {
|
||||
listener.onTransactionConfidenceChanged(Wallet.this, tx);
|
||||
}
|
||||
} finally {
|
||||
lock.lock();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private int onWalletChangedSuppressions = 0;
|
||||
private void invokeOnWalletChanged() {
|
||||
private void maybeQueueOnWalletChanged() {
|
||||
// Don't invoke the callback in some circumstances, eg, whilst we are re-organizing or fiddling with
|
||||
// transactions due to a new block arriving. It will be called later instead.
|
||||
checkState(lock.isLocked());
|
||||
Preconditions.checkState(onWalletChangedSuppressions >= 0);
|
||||
checkState(onWalletChangedSuppressions >= 0);
|
||||
if (onWalletChangedSuppressions > 0) return;
|
||||
lock.unlock();
|
||||
try {
|
||||
for (WalletEventListener listener : eventListeners) {
|
||||
listener.onWalletChanged(this);
|
||||
for (final WalletEventListener listener : eventListeners) {
|
||||
Threading.userCode.execute(new Runnable() {
|
||||
@Override public void run() {
|
||||
listener.onWalletChanged(Wallet.this);
|
||||
}
|
||||
} finally {
|
||||
lock.lock();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeOnCoinsReceived(Transaction tx, BigInteger balance, BigInteger newBalance) {
|
||||
private void queueOnCoinsReceived(final Transaction tx, final BigInteger balance, final BigInteger newBalance) {
|
||||
checkState(lock.isLocked());
|
||||
lock.unlock();
|
||||
try {
|
||||
for (WalletEventListener listener : eventListeners) {
|
||||
for (final WalletEventListener listener : eventListeners) {
|
||||
Threading.userCode.execute(new Runnable() {
|
||||
@Override public void run() {
|
||||
listener.onCoinsReceived(Wallet.this, tx, balance, newBalance);
|
||||
}
|
||||
} finally {
|
||||
lock.lock();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeOnCoinsSent(Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
|
||||
private void queueOnCoinsSent(final Transaction tx, final BigInteger prevBalance, final BigInteger newBalance) {
|
||||
checkState(lock.isLocked());
|
||||
lock.unlock();
|
||||
try {
|
||||
for (WalletEventListener listener : eventListeners) {
|
||||
for (final WalletEventListener listener : eventListeners) {
|
||||
Threading.userCode.execute(new Runnable() {
|
||||
@Override public void run() {
|
||||
listener.onCoinsSent(Wallet.this, tx, prevBalance, newBalance);
|
||||
}
|
||||
} finally {
|
||||
lock.lock();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeOnReorganize() {
|
||||
private void queueOnReorganize() {
|
||||
checkState(lock.isLocked());
|
||||
lock.unlock();
|
||||
try {
|
||||
for (WalletEventListener listener : eventListeners) {
|
||||
for (final WalletEventListener listener : eventListeners) {
|
||||
Threading.userCode.execute(new Runnable() {
|
||||
@Override public void run() {
|
||||
listener.onReorganize(Wallet.this);
|
||||
}
|
||||
} finally {
|
||||
lock.lock();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,22 +16,60 @@
|
|||
|
||||
package com.google.bitcoin.utils;
|
||||
|
||||
import com.google.common.util.concurrent.Callables;
|
||||
import com.google.common.util.concurrent.CycleDetectingLockFactory;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
/**
|
||||
* A wrapper around explicit lock creation that lets you control whether bitcoinj performs cycle detection or not.
|
||||
* Various threading related utilities. Provides a wrapper around explicit lock creation that lets you control whether
|
||||
* bitcoinj performs cycle detection or not. Cycle detection is useful to detect bugs but comes with a small cost.
|
||||
* Also provides a worker thread that is designed for event listeners to be dispatched on.
|
||||
*/
|
||||
public class Threading {
|
||||
/**
|
||||
* A single-threaded executor that is intended for running event listeners on. This ensures all event listener code
|
||||
* runs without any locks being held.
|
||||
*/
|
||||
public static final ExecutorService userCode;
|
||||
// For safety reasons keep track of the thread we use to run user-provided event listeners to avoid deadlock.
|
||||
private static final Thread executorThread;
|
||||
|
||||
/**
|
||||
* Put a dummy task into the queue and wait for it to be run. Because it's single threaded, this means all
|
||||
* tasks submitted before this point are now completed.
|
||||
*/
|
||||
public static void waitForUserCode() {
|
||||
// If this assert fires it means you have a bug in your code - you can't call this method inside your own
|
||||
// event handlers because it would never return. If you aren't calling this method explicitly, then that
|
||||
// means there's a bug in bitcoinj.
|
||||
checkState(executorThread != Thread.currentThread(), "waitForUserCode() run on user code thread would deadlock.");
|
||||
Futures.getUnchecked(userCode.submit(Callables.returning(null)));
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static {
|
||||
// Default policy goes here. If you want to change this, use one of the static methods before
|
||||
// instantiating any bitcoinj objects. The policy change will take effect only on new objects
|
||||
// from that point onwards.
|
||||
throwOnLockCycles();
|
||||
|
||||
userCode = Executors.newSingleThreadExecutor();
|
||||
// We can't directly get the thread that was just created, but we can fetch it indirectly. We'll use this
|
||||
// for deadlock detection by checking for waits on the user code thread.
|
||||
executorThread = Futures.getUnchecked(userCode.submit(new Callable<Thread>() {
|
||||
@Override public Thread call() throws Exception {
|
||||
Thread.currentThread().setName("bitcoinj user code thread");
|
||||
return Thread.currentThread();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private static CycleDetectingLockFactory.Policy policy;
|
||||
|
|
|
@ -20,6 +20,7 @@ import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
|
|||
import com.google.bitcoin.params.UnitTestParams;
|
||||
import com.google.bitcoin.store.MemoryBlockStore;
|
||||
import com.google.bitcoin.utils.BriefLogFormatter;
|
||||
import com.google.bitcoin.utils.Threading;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -78,6 +79,7 @@ public class ChainSplitTest {
|
|||
Block b2 = b1.createNextBlock(coinsTo);
|
||||
assertTrue(chain.add(b1));
|
||||
assertTrue(chain.add(b2));
|
||||
Threading.waitForUserCode();
|
||||
assertFalse(reorgHappened[0]);
|
||||
assertEquals(2, walletChanged[0]);
|
||||
// We got two blocks which generated 50 coins each, to us.
|
||||
|
@ -93,6 +95,7 @@ public class ChainSplitTest {
|
|||
// Nothing should happen at this point. We saw b2 first so it takes priority.
|
||||
Block b3 = b1.createNextBlock(someOtherGuy);
|
||||
assertTrue(chain.add(b3));
|
||||
Threading.waitForUserCode();
|
||||
assertFalse(reorgHappened[0]); // No re-org took place.
|
||||
assertEquals(2, walletChanged[0]);
|
||||
assertEquals("100.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
|
||||
|
@ -108,11 +111,13 @@ public class ChainSplitTest {
|
|||
b8.addTransaction(b7.getTransactions().get(1));
|
||||
b8.solve();
|
||||
assertTrue(chain.add(b8));
|
||||
Threading.waitForUserCode();
|
||||
assertFalse(reorgHappened[0]); // No re-org took place.
|
||||
assertEquals(2, walletChanged[0]);
|
||||
assertEquals("100.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
|
||||
// Now we add another block to make the alternative chain longer.
|
||||
assertTrue(chain.add(b3.createNextBlock(someOtherGuy)));
|
||||
Threading.waitForUserCode();
|
||||
assertTrue(reorgHappened[0]); // Re-org took place.
|
||||
assertEquals(3, walletChanged[0]);
|
||||
reorgHappened[0] = false;
|
||||
|
@ -131,6 +136,7 @@ public class ChainSplitTest {
|
|||
// genesis -> b1 -> b2 -> b5 -> b6
|
||||
// \-> b3 -> b4
|
||||
//
|
||||
Threading.waitForUserCode();
|
||||
assertTrue(reorgHappened[0]);
|
||||
assertEquals(4, walletChanged[0]);
|
||||
assertEquals("200.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
|
||||
|
@ -292,7 +298,7 @@ public class ChainSplitTest {
|
|||
chain.add(b3); // Side chain.
|
||||
Block b4 = b3.createNextBlock(new ECKey().toAddress(unitTestParams));
|
||||
chain.add(b4); // New best chain.
|
||||
|
||||
Threading.waitForUserCode();
|
||||
// Should have seen a double spend.
|
||||
assertTrue(eventCalled[0]);
|
||||
assertEquals(Utils.toNanoCoins(30, 0), wallet.getBalance());
|
||||
|
@ -339,7 +345,7 @@ public class ChainSplitTest {
|
|||
chain.add(b3); // Side chain.
|
||||
Block b4 = b3.createNextBlock(new ECKey().toAddress(unitTestParams));
|
||||
chain.add(b4); // New best chain.
|
||||
|
||||
Threading.waitForUserCode();
|
||||
// Should have seen a double spend against the pending pool.
|
||||
// genesis -> b1 -> b2 [t1 dead and exited the miners mempools]
|
||||
// \-> b3 (t2) -> b4
|
||||
|
@ -384,7 +390,7 @@ public class ChainSplitTest {
|
|||
assertTrue(chain.add(b1));
|
||||
assertTrue(chain.add(b2));
|
||||
assertTrue(chain.add(b3));
|
||||
|
||||
Threading.waitForUserCode();
|
||||
// Check the transaction confidence levels are correct.
|
||||
assertEquals(3, txns.size());
|
||||
|
||||
|
@ -417,6 +423,7 @@ public class ChainSplitTest {
|
|||
|
||||
assertTrue(chain.add(b4));
|
||||
assertTrue(chain.add(b5));
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(3, txns.size());
|
||||
|
||||
assertEquals(1, txns.get(0).getConfidence().getAppearedAtChainHeight());
|
||||
|
@ -535,6 +542,7 @@ public class ChainSplitTest {
|
|||
//
|
||||
|
||||
// Check we have seen the three transactions.
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(3, txns.size());
|
||||
|
||||
// Check the coinbase transaction is building and in the unspent pool only.
|
||||
|
@ -560,6 +568,7 @@ public class ChainSplitTest {
|
|||
assertTrue(chain.add(b5));
|
||||
log.debug("Adding block b6");
|
||||
assertTrue(chain.add(b6));
|
||||
Threading.waitForUserCode();
|
||||
|
||||
// Transaction 1 (in block b2) is now on a side chain and should have confidence type of dead and be in the dead pool only
|
||||
assertEquals(TransactionConfidence.ConfidenceType.DEAD, txns.get(1).getConfidence().getConfidenceType());
|
||||
|
@ -576,6 +585,7 @@ public class ChainSplitTest {
|
|||
assertTrue(chain.add(b7));
|
||||
log.debug("Adding block b8");
|
||||
assertTrue(chain.add(b8));
|
||||
Threading.waitForUserCode();
|
||||
|
||||
//
|
||||
// genesis -> b1 -> b2 -> b3 -> b7 -> b8
|
||||
|
@ -597,6 +607,8 @@ public class ChainSplitTest {
|
|||
assertTrue(chain.add(b9));
|
||||
log.debug("Adding block b10");
|
||||
assertTrue(chain.add(b10));
|
||||
Threading.waitForUserCode();
|
||||
|
||||
//
|
||||
// genesis -> b1 -> b2 -> b3 -> b7 -> b8
|
||||
// \-> b4 -> b5 -> b6 -> b9 -> b10
|
||||
|
|
|
@ -20,6 +20,7 @@ import com.google.bitcoin.discovery.PeerDiscovery;
|
|||
import com.google.bitcoin.discovery.PeerDiscoveryException;
|
||||
import com.google.bitcoin.params.UnitTestParams;
|
||||
import com.google.bitcoin.store.MemoryBlockStore;
|
||||
import com.google.bitcoin.utils.Threading;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -285,6 +286,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
|
|||
Address dest = new ECKey().toAddress(params);
|
||||
Wallet.SendResult sendResult = wallet.sendCoins(peerGroup, dest, Utils.toNanoCoins(1, 0));
|
||||
assertNotNull(sendResult.tx);
|
||||
Threading.waitForUserCode();
|
||||
assertFalse(sendResult.broadcastComplete.isDone());
|
||||
assertEquals(transactions[0], sendResult.tx);
|
||||
assertEquals(transactions[0].getConfidence().numBroadcastPeers(), 1);
|
||||
|
@ -298,6 +300,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
|
|||
inv.addTransaction(t1);
|
||||
inbound(p2, inv);
|
||||
assertTrue(sendResult.broadcastComplete.isDone());
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(transactions[0], sendResult.tx);
|
||||
assertEquals(transactions[0].getConfidence().numBroadcastPeers(), 2);
|
||||
// Confirm it.
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package com.google.bitcoin.core;
|
||||
|
||||
import com.google.bitcoin.core.Peer.PeerHandler;
|
||||
import com.google.bitcoin.utils.Threading;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import org.easymock.Capture;
|
||||
|
@ -635,12 +636,14 @@ public class PeerTest extends TestWithNetworkConnections {
|
|||
} else {
|
||||
bouncePing();
|
||||
}
|
||||
Threading.waitForUserCode();
|
||||
assertNotNull(vtx[0]);
|
||||
vtx[0] = null;
|
||||
// Send a timelocked transaction, nothing happens.
|
||||
Transaction t2 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(2, 0), key);
|
||||
t2.setLockTime(999999);
|
||||
inbound(peer, t2);
|
||||
Threading.waitForUserCode();
|
||||
assertNull(vtx[0]);
|
||||
// Now we want to hear about them. Send another, we are told about it.
|
||||
wallet.setAcceptTimeLockedTransactions(true);
|
||||
|
@ -651,6 +654,7 @@ public class PeerTest extends TestWithNetworkConnections {
|
|||
} else {
|
||||
bouncePing();
|
||||
}
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(t2, vtx[0]);
|
||||
}
|
||||
|
||||
|
@ -734,6 +738,7 @@ public class PeerTest extends TestWithNetworkConnections {
|
|||
} else {
|
||||
bouncePing();
|
||||
}
|
||||
Threading.waitForUserCode();
|
||||
// We're done but still not notified because it was timelocked.
|
||||
if (shouldAccept)
|
||||
assertNotNull(vtx[0]);
|
||||
|
|
|
@ -22,6 +22,7 @@ import com.google.bitcoin.core.WalletTransaction.Pool;
|
|||
import com.google.bitcoin.crypto.KeyCrypter;
|
||||
import com.google.bitcoin.crypto.KeyCrypterException;
|
||||
import com.google.bitcoin.crypto.KeyCrypterScrypt;
|
||||
import com.google.bitcoin.utils.Threading;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
@ -179,6 +180,7 @@ public class WalletTest extends TestWithWallet {
|
|||
assertFalse(availFuture.isDone());
|
||||
assertFalse(estimatedFuture.isDone());
|
||||
Transaction t1 = sendMoneyToWallet(wallet, v1, toAddress, null);
|
||||
Threading.waitForUserCode();
|
||||
final ListenableFuture<Transaction> depthFuture = t1.getConfidence().getDepthFuture(1);
|
||||
assertFalse(depthFuture.isDone());
|
||||
assertEquals(BigInteger.ZERO, wallet.getBalance());
|
||||
|
@ -192,6 +194,7 @@ public class WalletTest extends TestWithWallet {
|
|||
assertEquals("Incorrect confirmed tx PENDING pool size", 0, wallet.getPoolSize(WalletTransaction.Pool.PENDING));
|
||||
assertEquals("Incorrect confirmed tx UNSPENT pool size", 1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
|
||||
assertEquals("Incorrect confirmed tx ALL pool size", 1, wallet.getPoolSize(WalletTransaction.Pool.ALL));
|
||||
Threading.waitForUserCode();
|
||||
assertTrue(availFuture.isDone());
|
||||
assertTrue(estimatedFuture.isDone());
|
||||
assertTrue(depthFuture.isDone());
|
||||
|
@ -221,6 +224,7 @@ public class WalletTest extends TestWithWallet {
|
|||
t.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByAddress(new byte[]{1,2,3,4})));
|
||||
t.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByAddress(new byte[]{10,2,3,4})));
|
||||
wallet.commitTx(t);
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.PENDING));
|
||||
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.SPENT));
|
||||
assertEquals(2, wallet.getPoolSize(WalletTransaction.Pool.ALL));
|
||||
|
@ -374,6 +378,7 @@ public class WalletTest extends TestWithWallet {
|
|||
// Receive some money.
|
||||
BigInteger oneCoin = Utils.toNanoCoins(1, 0);
|
||||
Transaction tx1 = sendMoneyToWallet(oneCoin, AbstractBlockChain.NewBlockType.BEST_CHAIN);
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(null, txn[1]); // onCoinsSent not called.
|
||||
assertEquals(tx1, confTxns.getFirst()); // onTransactionConfidenceChanged called
|
||||
assertEquals(txn[0].getHash(), tx1.getHash());
|
||||
|
@ -389,6 +394,7 @@ public class WalletTest extends TestWithWallet {
|
|||
txn[0] = txn[1] = null;
|
||||
confTxns.clear();
|
||||
sendMoneyToWallet(send1, AbstractBlockChain.NewBlockType.BEST_CHAIN);
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(bitcoinValueToFriendlyString(wallet.getBalance()), "0.90");
|
||||
assertEquals(null, txn[0]);
|
||||
assertEquals(2, confTxns.size());
|
||||
|
@ -401,9 +407,11 @@ public class WalletTest extends TestWithWallet {
|
|||
wallet.commitTx(send2);
|
||||
sendMoneyToWallet(send2, AbstractBlockChain.NewBlockType.BEST_CHAIN);
|
||||
assertEquals(bitcoinValueToFriendlyString(wallet.getBalance()), "0.80");
|
||||
Threading.waitForUserCode();
|
||||
BlockPair b4 = createFakeBlock(blockStore);
|
||||
confTxns.clear();
|
||||
wallet.notifyNewBestBlock(b4.storedBlock);
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(3, confTxns.size());
|
||||
}
|
||||
|
||||
|
@ -581,6 +589,7 @@ public class WalletTest extends TestWithWallet {
|
|||
assertEquals(send1, received.getOutput(0).getSpentBy().getParentTransaction());
|
||||
// Receive a block that overrides it.
|
||||
sendMoneyToWallet(send2, AbstractBlockChain.NewBlockType.BEST_CHAIN);
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(send1, eventDead[0]);
|
||||
assertEquals(send2, eventReplacement[0]);
|
||||
assertEquals(TransactionConfidence.ConfidenceType.DEAD,
|
||||
|
@ -593,6 +602,7 @@ public class WalletTest extends TestWithWallet {
|
|||
assertEquals(TransactionConfidence.ConfidenceType.PENDING,
|
||||
doubleSpends.t1.getConfidence().getConfidenceType());
|
||||
sendMoneyToWallet(doubleSpends.t2, AbstractBlockChain.NewBlockType.BEST_CHAIN);
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(TransactionConfidence.ConfidenceType.DEAD,
|
||||
doubleSpends.t1.getConfidence().getConfidenceType());
|
||||
assertEquals(doubleSpends.t2, doubleSpends.t1.getConfidence().getOverridingTransaction());
|
||||
|
@ -630,6 +640,7 @@ public class WalletTest extends TestWithWallet {
|
|||
|
||||
if (wallet.isPendingTransactionRelevant(t1))
|
||||
wallet.receivePending(t1, null);
|
||||
Threading.waitForUserCode();
|
||||
assertTrue(flags[0]);
|
||||
assertTrue(flags[1]); // is pending
|
||||
flags[0] = false;
|
||||
|
@ -649,6 +660,7 @@ public class WalletTest extends TestWithWallet {
|
|||
notifiedTx[0].getConfidence().getConfidenceType());
|
||||
final Transaction t1Copy = new Transaction(params, t1.bitcoinSerialize());
|
||||
sendMoneyToWallet(t1Copy, AbstractBlockChain.NewBlockType.BEST_CHAIN);
|
||||
Threading.waitForUserCode();
|
||||
assertFalse(flags[0]);
|
||||
assertTrue(flags[1]);
|
||||
assertEquals(TransactionConfidence.ConfidenceType.BUILDING, notifiedTx[0].getConfidence().getConfidenceType());
|
||||
|
@ -658,6 +670,7 @@ public class WalletTest extends TestWithWallet {
|
|||
Transaction irrelevant = createFakeTx(params, nanos, new ECKey().toAddress(params));
|
||||
if (wallet.isPendingTransactionRelevant(irrelevant))
|
||||
wallet.receivePending(irrelevant, null);
|
||||
Threading.waitForUserCode();
|
||||
assertFalse(flags[0]);
|
||||
assertEquals(2, walletChanged[0]);
|
||||
}
|
||||
|
@ -686,6 +699,7 @@ public class WalletTest extends TestWithWallet {
|
|||
if (wallet.isPendingTransactionRelevant(t2))
|
||||
wallet.receivePending(t2, null);
|
||||
// We received an onCoinsSent() callback.
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(t2, txn[0]);
|
||||
assertEquals(nanos, bigints[0]);
|
||||
assertEquals(halfNanos, bigints[1]);
|
||||
|
@ -734,11 +748,13 @@ public class WalletTest extends TestWithWallet {
|
|||
assertEquals(BigInteger.ZERO, wallet.getBalance());
|
||||
if (wallet.isPendingTransactionRelevant(t1))
|
||||
wallet.receivePending(t1, null);
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(t1, called[0]);
|
||||
assertEquals(nanos, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
|
||||
// Now receive a double spend on the main chain.
|
||||
called[0] = called[1] = null;
|
||||
sendMoneyToWallet(t2, AbstractBlockChain.NewBlockType.BEST_CHAIN);
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(BigInteger.ZERO, wallet.getBalance());
|
||||
assertEquals(t1, called[0]); // dead
|
||||
assertEquals(t2, called[1]); // replacement
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
|
|||
import com.google.bitcoin.params.MainNetParams;
|
||||
import com.google.bitcoin.params.UnitTestParams;
|
||||
import com.google.bitcoin.utils.BriefLogFormatter;
|
||||
import com.google.bitcoin.utils.Threading;
|
||||
import com.google.protobuf.ByteString;
|
||||
import org.bitcoinj.wallet.Protos;
|
||||
import org.junit.Before;
|
||||
|
@ -180,6 +181,7 @@ public class WalletProtobufSerializerTest {
|
|||
// genesis -> b1 -> b2
|
||||
|
||||
// Check the transaction confidence levels are correct before wallet roundtrip.
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(2, txns.size());
|
||||
|
||||
TransactionConfidence confidence0 = txns.get(0).getConfidence();
|
||||
|
|
Loading…
Add table
Reference in a new issue