3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-31 07:12:17 +00:00

Refactor some of the wallet/peer interaction. Analyze dependencies of relevant pending transactions.

The Peer object now asks each connected Wallet if it cares about a transaction. If it does, then receivePending() is not called immediately, but rather after downloading of dependencies and with those dependencies. If any dependencies are time locked, a new wallet property controls whether they are discarded or not.
This commit is contained in:
Mike Hearn 2013-01-20 23:23:50 +01:00
parent bfcf67ee5a
commit 35d6084bbf
7 changed files with 278 additions and 56 deletions

View File

@ -69,6 +69,7 @@ public class Peer {
private int blocksAnnounced; private int blocksAnnounced;
// A class that tracks recent transactions that have been broadcast across the network, counts how many // A class that tracks recent transactions that have been broadcast across the network, counts how many
// peers announced them and updates the transaction confidence data. It is passed to each Peer. // peers announced them and updates the transaction confidence data. It is passed to each Peer.
// TODO: Make this final and unsynchronized.
private MemoryPool memoryPool; private MemoryPool memoryPool;
// Each wallet added to the peer will be notified of downloaded transaction data. // Each wallet added to the peer will be notified of downloaded transaction data.
private CopyOnWriteArrayList<Wallet> wallets; private CopyOnWriteArrayList<Wallet> wallets;
@ -415,13 +416,43 @@ public class Peer {
// We may get back a different transaction object. // We may get back a different transaction object.
tx = memoryPool.seen(tx, getAddress()); tx = memoryPool.seen(tx, getAddress());
} }
if (maybeHandleRequestedData(tx)) final Transaction fTx = tx;
if (maybeHandleRequestedData(fTx))
return; return;
// Tell all wallets about this tx so they can check if it's relevant or not. // Tell all wallets about this tx so they can check if it's relevant or not.
for (ListIterator<Wallet> it = wallets.listIterator(); it.hasNext();) { for (ListIterator<Wallet> it = wallets.listIterator(); it.hasNext();) {
Wallet wallet = it.next(); final Wallet wallet = it.next();
try { try {
wallet.receivePending(tx); if (wallet.isPendingTransactionRelevant(fTx)) {
// This transaction seems interesting to us, so let's download its dependencies. This has several
// purposes: we can check that the sender isn't attacking us by engaging in protocol abuse games,
// like depending on a time-locked transaction that will never confirm, or building huge chains
// of unconfirmed transactions (again - so they don't confirm and the money can be taken
// back with a Finney attack). Knowing the dependencies also lets us store them in a serialized
// wallet so we always have enough data to re-announce to the network and get the payment into
// the chain, in case the sender goes away and the network starts to forget.
// TODO: Not all the above things are implemented.
Futures.addCallback(downloadDependencies(fTx), new FutureCallback<List<Transaction>>() {
public void onSuccess(List<Transaction> dependencies) {
try {
log.info("Dependency download complete!");
wallet.receivePending(fTx, dependencies);
} catch (VerificationException e) {
log.error("Wallet failed to process pending transaction {}", fTx.getHashAsString());
log.error("Error was: ", e);
// Not much more we can do at this point.
}
}
public void onFailure(Throwable throwable) {
log.error("Could not download dependencies of tx {}", fTx.getHashAsString());
log.error("Error was: ", throwable);
// Not much more we can do at this point.
}
});
}
} catch (VerificationException e) { } catch (VerificationException e) {
log.error("Wallet failed to verify tx", e); log.error("Wallet failed to verify tx", e);
// Carry on, listeners may still want to know. // Carry on, listeners may still want to know.
@ -446,7 +477,7 @@ public class Peer {
* *
* <p>For example, if tx has 2 inputs that connect to transactions A and B, and transaction B is unconfirmed and * <p>For example, if tx has 2 inputs that connect to transactions A and B, and transaction B is unconfirmed and
* has one input connecting to transaction C that is unconfirmed, and transaction C connects to transaction D * has one input connecting to transaction C that is unconfirmed, and transaction C connects to transaction D
* that is in the chain, then this method will return either {B, C} or {C, B}.</p> * that is in the chain, then this method will return either {B, C} or {C, B}. No ordering is guaranteed.</p>
* *
* <p>This method is useful for apps that want to learn about how long an unconfirmed transaction might take * <p>This method is useful for apps that want to learn about how long an unconfirmed transaction might take
* to confirm, by checking for unexpectedly time locked transactions, unusually deep dependency trees or fee-paying * to confirm, by checking for unexpectedly time locked transactions, unusually deep dependency trees or fee-paying
@ -492,11 +523,13 @@ public class Peer {
for (TransactionInput input : tx.getInputs()) { for (TransactionInput input : tx.getInputs()) {
// There may be multiple inputs that connect to the same transaction. // There may be multiple inputs that connect to the same transaction.
Sha256Hash hash = input.getOutpoint().getHash(); Sha256Hash hash = input.getOutpoint().getHash();
Transaction dep = memoryPool.get(hash); synchronized (this) {
if (dep == null) { Transaction dep = memoryPool.get(hash);
needToRequest.add(hash); if (dep == null) {
} else { needToRequest.add(hash);
dependencies.add(dep); } else {
dependencies.add(dep);
}
} }
} }
results.addAll(dependencies); results.addAll(dependencies);

View File

@ -1074,7 +1074,10 @@ public class PeerGroup extends AbstractIdleService {
synchronized (PeerGroup.this) { synchronized (PeerGroup.this) {
for (Wallet wallet : wallets) { for (Wallet wallet : wallets) {
try { try {
wallet.receivePending(pinnedTx); if (wallet.isPendingTransactionRelevant(pinnedTx)) {
// Assumption here is there are no dependencies of the created transaction.
wallet.receivePending(pinnedTx, null);
}
} catch (Throwable t) { } catch (Throwable t) {
future.setException(t); future.setException(t);
return; return;
@ -1106,7 +1109,10 @@ public class PeerGroup extends AbstractIdleService {
// wallet now we know it's valid. // wallet now we know it's valid.
for (Wallet wallet : wallets) { for (Wallet wallet : wallets) {
try { try {
wallet.receivePending(pinnedTx); if (wallet.isPendingTransactionRelevant(pinnedTx)) {
// Assumption here is there are no dependencies of the created transaction.
wallet.receivePending(pinnedTx, null);
}
} catch (Throwable t) { } catch (Throwable t) {
future.setException(t); future.setException(t);
return; return;

View File

@ -192,6 +192,8 @@ public class Wallet implements Serializable, BlockChainListener {
// in receive() via Transaction.setBlockAppearance(). As the BlockChain always calls notifyNewBestBlock even if // in receive() via Transaction.setBlockAppearance(). As the BlockChain always calls notifyNewBestBlock even if
// it sent transactions to the wallet, without this we'd double count. // it sent transactions to the wallet, without this we'd double count.
private transient HashSet<Sha256Hash> ignoreNextNewBlock; private transient HashSet<Sha256Hash> ignoreNextNewBlock;
// Whether or not to ignore nLockTime > 0 transactions that are received to the mempool.
private boolean acceptTimeLockedTransactions;
/** /**
* Creates a new, empty wallet with no keys and no transactions. If you want to restore a wallet from disk instead, * Creates a new, empty wallet with no keys and no transactions. If you want to restore a wallet from disk instead,
@ -226,6 +228,7 @@ public class Wallet implements Serializable, BlockChainListener {
invokeOnWalletChanged(); invokeOnWalletChanged();
} }
}; };
acceptTimeLockedTransactions = false;
} }
public NetworkParameters getNetworkParameters() { public NetworkParameters getNetworkParameters() {
@ -290,6 +293,28 @@ public class Wallet implements Serializable, BlockChainListener {
saveToFile(temp, f); saveToFile(temp, f);
} }
/**
* <p>Whether or not the wallet will ignore transactions that have a lockTime parameter > 0. By default, all such
* transactions are ignored, because they are useful only in special protocols and such a transaction may not
* confirm as fast as an app typically expects. By setting this property to true, you are acknowledging that
* you understand what time-locked transactions are, and that your code is capable of handling them without risk.
* For instance you are not providing anything valuable in return for an unconfirmed transaction that has a lock
* time far in the future (which opens you up to Finney attacks).</p>
*
* <p>Note that this property is not serialized. So you have to set it to true each time you load or create a
* wallet.</p>
*/
public void setAcceptTimeLockedTransactions(boolean acceptTimeLockedTransactions) {
this.acceptTimeLockedTransactions = acceptTimeLockedTransactions;
}
/**
* See {@link Wallet#setAcceptTimeLockedTransactions(boolean)} for an explanation of this property.
*/
public boolean doesAcceptTimeLockedTransactions() {
return acceptTimeLockedTransactions;
}
// Auto-saving can be done on a background thread if the user wishes it, this is to avoid stalling threads calling // Auto-saving can be done on a background thread if the user wishes it, this is to avoid stalling threads calling
// into the wallet on serialization/disk access all the time which is important in GUI apps where you don't want // into the wallet on serialization/disk access all the time which is important in GUI apps where you don't want
// the main thread to ever wait on disk (otherwise you lose a lot of responsiveness). The primary case where it // the main thread to ever wait on disk (otherwise you lose a lot of responsiveness). The primary case where it
@ -640,35 +665,35 @@ public class Wallet implements Serializable, BlockChainListener {
receive(tx, block, blockType, false); receive(tx, block, blockType, false);
} }
protected static class AnalysisResult {
// Which tx, if any, had a non-zero lock time.
Transaction timeLocked;
// In future, depth, fees, if any are non-standard, anything else that's interesting ...
}
/** /**
* Called when we have found a transaction (via network broadcast or otherwise) that is relevant to this wallet * <p>Called when we have found a transaction (via network broadcast or otherwise) that is relevant to this wallet
* and want to record it. Note that we <b>cannot verify these transactions at all</b>, they may spend fictional * and want to record it. Note that we <b>cannot verify these transactions at all</b>, they may spend fictional
* coins or be otherwise invalid. They are useful to inform the user about coins they can expect to receive soon, * coins or be otherwise invalid. They are useful to inform the user about coins they can expect to receive soon,
* and if you trust the sender of the transaction you can choose to assume they are in fact valid and will not * and if you trust the sender of the transaction you can choose to assume they are in fact valid and will not
* be double spent as an optimization. * be double spent as an optimization.</p>
* *
* @param tx * <p>Before this method is called, {@link Wallet#isPendingTransactionRelevant(Transaction)} should have been
* @throws VerificationException * called to decide whether the wallet cares about the transaction - if it does, then this method expects the
* transaction and any dependencies it has which are still in the memory pool.</p>
*/ */
public synchronized void receivePending(Transaction tx) throws VerificationException { public synchronized void receivePending(Transaction tx, List<Transaction> dependencies) throws VerificationException {
// Can run in a peer thread. // Can run in a peer thread. This method will only be called if a prior call to isPendingTransactionRelevant
// returned true, so we already know by this point that it sends coins to or from our wallet, or is a double
// Ignore it if we already know about this transaction. Receiving a pending transaction never moves it // spend against one of our other pending transactions.
// between pools. //
EnumSet<Pool> containingPools = getContainingPools(tx); // Do a brief risk analysis of the transaction and its dependencies to check for any possible attacks.
if (!containingPools.equals(EnumSet.noneOf(Pool.class))) { AnalysisResult analysis = analyzeTransactionAndDependencies(tx, dependencies);
log.debug("Received tx we already saw in a block or created ourselves: " + tx.getHashAsString()); if (analysis.timeLocked != null && !doesAcceptTimeLockedTransactions()) {
log.warn("Transaction {}, dependency of {} has a time lock value of {}", new Object[] {
analysis.timeLocked.getHashAsString(), tx.getHashAsString(), analysis.timeLocked.getLockTime()});
return; return;
} }
// We only care about transactions that:
// - Send us coins
// - Spend our coins
if (!isTransactionRelevant(tx)) {
log.debug("Received tx that isn't relevant to this wallet, discarding.");
return;
}
BigInteger valueSentToMe = tx.getValueSentToMe(this); BigInteger valueSentToMe = tx.getValueSentToMe(this);
BigInteger valueSentFromMe = tx.getValueSentFromMe(this); BigInteger valueSentFromMe = tx.getValueSentFromMe(this);
if (log.isInfoEnabled()) { if (log.isInfoEnabled()) {
@ -676,7 +701,6 @@ public class Wallet implements Serializable, BlockChainListener {
" and sends us %s BTC", tx.getHashAsString(), Utils.bitcoinValueToFriendlyString(valueSentFromMe), " and sends us %s BTC", tx.getHashAsString(), Utils.bitcoinValueToFriendlyString(valueSentFromMe),
Utils.bitcoinValueToFriendlyString(valueSentToMe))); Utils.bitcoinValueToFriendlyString(valueSentToMe)));
} }
// Mark the tx as having been seen but is not yet in the chain. This will normally have been done already by // Mark the tx as having been seen but is not yet in the chain. This will normally have been done already by
// the Peer before we got to this point, but in some cases (unit tests, other sources of transactions) it may // the Peer before we got to this point, but in some cases (unit tests, other sources of transactions) it may
// have been missed out. // have been missed out.
@ -687,7 +711,6 @@ public class Wallet implements Serializable, BlockChainListener {
// txConfidenceListener wasn't added. // txConfidenceListener wasn't added.
invokeOnTransactionConfidenceChanged(tx); invokeOnTransactionConfidenceChanged(tx);
} }
// If this tx spends any of our unspent outputs, mark them as spent now, then add to the pending pool. This // 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 // ensures that if some other client that has our keys broadcasts a spend we stay in sync. Also updates the
// timestamp on the transaction and registers/runs event listeners. // timestamp on the transaction and registers/runs event listeners.
@ -696,6 +719,51 @@ public class Wallet implements Serializable, BlockChainListener {
commitTx(tx); commitTx(tx);
} }
private AnalysisResult analyzeTransactionAndDependencies(Transaction tx, List<Transaction> dependencies) {
AnalysisResult result = new AnalysisResult();
if (tx.getLockTime() > 0)
result.timeLocked = tx;
if (dependencies != null) {
for (Transaction dep : dependencies) {
if (dep.getLockTime() > 0) {
result.timeLocked = dep;
}
}
}
return result;
}
/**
* This method is used by a {@link Peer} to find out if a transaction that has been announced is interesting,
* that is, whether we should bother downloading its dependencies and exploring the transaction to decide how
* risky it is. If this method returns true then {@link Wallet#receivePending(Transaction, java.util.List)}
* will soon be called with the transactions dependencies as well.
*/
boolean isPendingTransactionRelevant(Transaction tx) throws ScriptException {
// Ignore it if we already know about this transaction. Receiving a pending transaction never moves it
// between pools.
EnumSet<Pool> containingPools = getContainingPools(tx);
if (!containingPools.equals(EnumSet.noneOf(Pool.class))) {
log.debug("Received tx we already saw in a block or created ourselves: " + tx.getHashAsString());
return false;
}
// We only care about transactions that:
// - Send us coins
// - Spend our coins
if (!isTransactionRelevant(tx)) {
log.debug("Received tx that isn't relevant to this wallet, discarding.");
return false;
}
if (tx.getLockTime() > 0 && !acceptTimeLockedTransactions) {
log.warn("Received transaction {} with a lock time of {}, but not configured to accept these, discarding",
tx.getHashAsString(), tx.getLockTime());
return false;
}
return true;
}
// Boilerplate that allows event listeners to delete themselves during execution, and auto locks the listener. // Boilerplate that allows event listeners to delete themselves during execution, and auto locks the listener.
private void invokeOnCoinsReceived(final Transaction tx, final BigInteger balance, final BigInteger newBalance) { private void invokeOnCoinsReceived(final Transaction tx, final BigInteger balance, final BigInteger newBalance) {
EventListenerInvoker.invoke(eventListeners, new EventListenerInvoker<WalletEventListener>() { EventListenerInvoker.invoke(eventListeners, new EventListenerInvoker<WalletEventListener>() {

View File

@ -119,6 +119,9 @@ public class PeerGroupTest extends TestWithPeerGroup {
assertNull(outbound(p2)); // Only one peer is used to download. assertNull(outbound(p2)); // Only one peer is used to download.
inbound(p1, t1); inbound(p1, t1);
assertNull(outbound(p2)); assertNull(outbound(p2));
// Asks for dependency.
GetDataMessage getdata = (GetDataMessage) outbound(p1);
inbound(p1, new NotFoundMessage(unitTestParams, getdata.getItems()));
assertEquals(value, wallet.getBalance(Wallet.BalanceType.ESTIMATED)); assertEquals(value, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
peerGroup.stop(); peerGroup.stop();
} }

View File

@ -26,6 +26,7 @@ import org.junit.Test;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@ -252,11 +253,13 @@ public class PeerTest extends TestWithNetworkConnections {
inv.addItem(item); inv.addItem(item);
inbound(peer, inv); inbound(peer, inv);
// Peer hasn't seen it before, so will ask for it. // Peer hasn't seen it before, so will ask for it.
GetDataMessage getdata = (GetDataMessage) outbound();
GetDataMessage message = (GetDataMessage) event.getValue().getMessage(); assertEquals(1, getdata.getItems().size());
assertEquals(1, message.getItems().size()); assertEquals(tx.getHash(), getdata.getItems().get(0).hash);
assertEquals(tx.getHash(), message.getItems().get(0).hash);
inbound(peer, tx); inbound(peer, tx);
// Ask for the dependency, it's not in the mempool (in chain).
getdata = (GetDataMessage) outbound();
inbound(peer, new NotFoundMessage(unitTestParams, getdata.getItems()));
assertEquals(value, wallet.getBalance(Wallet.BalanceType.ESTIMATED)); assertEquals(value, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
} }
@ -475,19 +478,15 @@ public class PeerTest extends TestWithNetworkConnections {
@Test @Test
public void recursiveDownload() throws Exception { public void recursiveDownload() throws Exception {
// Our peer announces a normal transaction that depends on a different transaction that is time locked such // Check that we can download all dependencies of an unconfirmed relevant transaction from the mempool.
// that it will never confirm. There's not currently any use case for doing that to us, so it's an attack. ECKey to = new ECKey();
//
// Peer should notice this by downloading all transaction dependencies and searching for timelocked ones.
// Also, if a dependency chain is absurdly deep, the wallet shouldn't hear about it because it may just be
// a different way to achieve the same thing (a payment that will not confirm for a very long time).
control.replay(); control.replay();
connect(); connect();
final Transaction[] onTx = new Transaction[1]; final Transaction[] onTx = new Transaction[1];
peer.addEventListener(new AbstractPeerEventListener() { peer.addEventListener(new AbstractPeerEventListener() {
@Override @Override
public void onTransaction(Peer peer, Transaction t) { public void onTransaction(Peer peer1, Transaction t) {
onTx[0] = t; onTx[0] = t;
} }
}); });
@ -496,7 +495,6 @@ public class PeerTest extends TestWithNetworkConnections {
// t1 -> t2 -> [t5] // t1 -> t2 -> [t5]
// -> t3 -> t4 -> [t6] // -> t3 -> t4 -> [t6]
// The ones in brackets are assumed to be in the chain and are represented only by hashes. // The ones in brackets are assumed to be in the chain and are represented only by hashes.
ECKey to = new ECKey();
Transaction t2 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), to); Transaction t2 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), to);
Sha256Hash t5 = t2.getInput(0).getOutpoint().getHash(); Sha256Hash t5 = t2.getInput(0).getOutpoint().getHash();
Transaction t4 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), new ECKey()); Transaction t4 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), new ECKey());
@ -559,6 +557,113 @@ public class PeerTest extends TestWithNetworkConnections {
assertTrue(results.contains(t4)); assertTrue(results.contains(t4));
} }
@Test
public void timeLockedTransaction() throws Exception {
// Test that if we receive a relevant transaction that has a lock time, it doesn't result in a notification
// until we explicitly opt in to seeing those.
control.replay();
connect();
// Initial setup.
ECKey key = new ECKey();
Wallet wallet = new Wallet(unitTestParams);
wallet.addKey(key);
peer.addWallet(wallet);
final Transaction[] vtx = new Transaction[1];
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
vtx[0] = tx;
}
});
// Send a normal relevant transaction, it's received correctly.
Transaction t1 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), key);
inbound(peer, t1);
GetDataMessage getdata = (GetDataMessage) outbound();
inbound(peer, new NotFoundMessage(unitTestParams, getdata.getItems()));
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);
assertNull(vtx[0]);
// Now we want to hear about them. Send another, we are told about it.
wallet.setAcceptTimeLockedTransactions(true);
inbound(peer, t2);
getdata = (GetDataMessage) outbound();
inbound(peer, new NotFoundMessage(unitTestParams, getdata.getItems()));
assertEquals(t2, vtx[0]);
}
@Test
public void rejectTimeLockedDependency() throws Exception {
// Check that we also verify the lock times of dependencies. Otherwise an attacker could still build a tx that
// looks legitimate and useful but won't actually ever confirm, by sending us a normal tx that spends a
// timelocked tx.
checkTimeLockedDependency(false);
}
@Test
public void acceptTimeLockedDependency() throws Exception {
checkTimeLockedDependency(true);
}
private void checkTimeLockedDependency(boolean shouldAccept) throws Exception {
// Initial setup.
control.replay();
connect();
ECKey key = new ECKey();
Wallet wallet = new Wallet(unitTestParams);
wallet.addKey(key);
wallet.setAcceptTimeLockedTransactions(shouldAccept);
peer.addWallet(wallet);
final Transaction[] vtx = new Transaction[1];
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
vtx[0] = tx;
}
});
// t1 -> t2 [locked] -> t3 (not available)
Transaction t2 = new Transaction(unitTestParams);
t2.setLockTime(999999);
// Add a fake input to t3 that goes nowhere.
Sha256Hash t3 = Sha256Hash.create("abc".getBytes(Charset.forName("UTF-8")));
t2.addInput(new TransactionInput(unitTestParams, t2, new byte[]{}, new TransactionOutPoint(unitTestParams, 0,
t3)));
t2.addOutput(Utils.toNanoCoins(1, 0), new ECKey());
Transaction t1 = new Transaction(unitTestParams);
t1.addInput(t2.getOutput(0));
t1.addOutput(Utils.toNanoCoins(1, 0), key); // Make it relevant.
// Announce t1.
InventoryMessage inv = new InventoryMessage(unitTestParams);
inv.addTransaction(t1);
inbound(peer, inv);
// Send it.
GetDataMessage getdata = (GetDataMessage) outbound();
assertEquals(t1.getHash(), getdata.getItems().get(0).hash);
inbound(peer, t1);
// Nothing arrived at our event listener yet.
assertNull(vtx[0]);
// We request t2.
getdata = (GetDataMessage) outbound();
assertEquals(t2.getHash(), getdata.getItems().get(0).hash);
inbound(peer, t2);
// We request t3.
getdata = (GetDataMessage) outbound();
assertEquals(t3, getdata.getItems().get(0).hash);
// Can't find it: bottom of tree.
NotFoundMessage notFound = new NotFoundMessage(unitTestParams);
notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t3));
inbound(peer, notFound);
// We're done but still not notified because it was timelocked.
if (shouldAccept)
assertNotNull(vtx[0]);
else
assertNull(vtx[0]);
}
// TODO: Use generics here to avoid unnecessary casting. // TODO: Use generics here to avoid unnecessary casting.
private Message outbound() { private Message outbound() {
List<DownstreamMessageEvent> messages = event.getValues(); List<DownstreamMessageEvent> messages = event.getValues();

View File

@ -443,7 +443,7 @@ public class WalletTest {
TestUtils.DoubleSpends doubleSpends = TestUtils.createFakeDoubleSpendTxns(params, myAddress); TestUtils.DoubleSpends doubleSpends = TestUtils.createFakeDoubleSpendTxns(params, myAddress);
// t1 spends to our wallet. t2 double spends somewhere else. // t1 spends to our wallet. t2 double spends somewhere else.
wallet.receivePending(doubleSpends.t1); wallet.receivePending(doubleSpends.t1, null);
assertEquals(TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN, assertEquals(TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN,
doubleSpends.t1.getConfidence().getConfidenceType()); doubleSpends.t1.getConfidence().getConfidenceType());
BlockPair bp3 = createFakeBlock(params, blockStore, doubleSpends.t2); BlockPair bp3 = createFakeBlock(params, blockStore, doubleSpends.t2);
@ -484,12 +484,13 @@ public class WalletTest {
} }
}); });
wallet.receivePending(t1); if (wallet.isPendingTransactionRelevant(t1))
wallet.receivePending(t1, null);
assertTrue(flags[0]); assertTrue(flags[0]);
assertTrue(flags[1]); // is pending assertTrue(flags[1]); // is pending
flags[0] = false; flags[0] = false;
// Check we don't get notified if we receive it again. // Check we don't get notified if we receive it again.
wallet.receivePending(t1); assertFalse(wallet.isPendingTransactionRelevant(t1));
assertFalse(flags[0]); assertFalse(flags[0]);
// Now check again, that we should NOT be notified when we receive it via a block (we were already notified). // Now check again, that we should NOT be notified when we receive it via a block (we were already notified).
// However the confidence should be updated. // However the confidence should be updated.
@ -513,7 +514,8 @@ public class WalletTest {
flags[0] = false; flags[0] = false;
flags[1] = false; flags[1] = false;
Transaction irrelevant = createFakeTx(params, nanos, new ECKey().toAddress(params)); Transaction irrelevant = createFakeTx(params, nanos, new ECKey().toAddress(params));
wallet.receivePending(irrelevant); if (wallet.isPendingTransactionRelevant(irrelevant))
wallet.receivePending(irrelevant, null);
assertFalse(flags[0]); assertFalse(flags[0]);
assertEquals(2, walletChanged[0]); assertEquals(2, walletChanged[0]);
} }
@ -542,7 +544,8 @@ public class WalletTest {
BigInteger halfNanos = Utils.toNanoCoins(0, 50); BigInteger halfNanos = Utils.toNanoCoins(0, 50);
Transaction t2 = wallet.createSend(new ECKey().toAddress(params), halfNanos); Transaction t2 = wallet.createSend(new ECKey().toAddress(params), halfNanos);
// Now receive it as pending. // Now receive it as pending.
wallet.receivePending(t2); if (wallet.isPendingTransactionRelevant(t2))
wallet.receivePending(t2, null);
// We received an onCoinsSent() callback. // We received an onCoinsSent() callback.
assertEquals(t2, txn[0]); assertEquals(t2, txn[0]);
assertEquals(nanos, bigints[0]); assertEquals(nanos, bigints[0]);
@ -590,7 +593,8 @@ public class WalletTest {
}); });
assertEquals(BigInteger.ZERO, wallet.getBalance()); assertEquals(BigInteger.ZERO, wallet.getBalance());
wallet.receivePending(t1); if (wallet.isPendingTransactionRelevant(t1))
wallet.receivePending(t1, null);
assertEquals(t1, called[0]); assertEquals(t1, called[0]);
assertEquals(nanos, wallet.getBalance(Wallet.BalanceType.ESTIMATED)); assertEquals(nanos, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
// Now receive a double spend on the main chain. // Now receive a double spend on the main chain.
@ -724,7 +728,8 @@ public class WalletTest {
wallet.addKey(key1); wallet.addKey(key1);
BigInteger value = toNanoCoins(5, 0); BigInteger value = toNanoCoins(5, 0);
Transaction t1 = createFakeTx(params, value, key1); Transaction t1 = createFakeTx(params, value, key1);
wallet.receivePending(t1); if (wallet.isPendingTransactionRelevant(t1))
wallet.receivePending(t1, null);
// TX should have been seen as relevant. // TX should have been seen as relevant.
assertEquals(value, wallet.getBalance(Wallet.BalanceType.ESTIMATED)); assertEquals(value, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
assertEquals(BigInteger.ZERO, wallet.getBalance(Wallet.BalanceType.AVAILABLE)); assertEquals(BigInteger.ZERO, wallet.getBalance(Wallet.BalanceType.AVAILABLE));
@ -754,7 +759,8 @@ public class WalletTest {
assertFalse(hash1.equals(hash2)); // File has changed. assertFalse(hash1.equals(hash2)); // File has changed.
Transaction t1 = createFakeTx(params, toNanoCoins(5, 0), key); Transaction t1 = createFakeTx(params, toNanoCoins(5, 0), key);
wallet.receivePending(t1); if (wallet.isPendingTransactionRelevant(t1))
wallet.receivePending(t1, null);
Sha256Hash hash3 = Sha256Hash.hashFileContents(f); Sha256Hash hash3 = Sha256Hash.hashFileContents(f);
assertFalse(hash2.equals(hash3)); // File has changed again. assertFalse(hash2.equals(hash3)); // File has changed again.
@ -803,7 +809,8 @@ public class WalletTest {
results[0] = results[1] = null; results[0] = results[1] = null;
Transaction t1 = createFakeTx(params, toNanoCoins(5, 0), key); Transaction t1 = createFakeTx(params, toNanoCoins(5, 0), key);
wallet.receivePending(t1); if (wallet.isPendingTransactionRelevant(t1))
wallet.receivePending(t1, null);
Sha256Hash hash3 = Sha256Hash.hashFileContents(f); Sha256Hash hash3 = Sha256Hash.hashFileContents(f);
assertTrue(hash2.equals(hash3)); // File has NOT changed. assertTrue(hash2.equals(hash3)); // File has NOT changed.
assertNull(results[0]); assertNull(results[0]);

View File

@ -87,7 +87,7 @@ public class WalletProtobufSerializerTest {
// Check that we can serialize double spends correctly, as this is a slightly tricky case. // Check that we can serialize double spends correctly, as this is a slightly tricky case.
TestUtils.DoubleSpends doubleSpends = TestUtils.createFakeDoubleSpendTxns(params, myAddress); TestUtils.DoubleSpends doubleSpends = TestUtils.createFakeDoubleSpendTxns(params, myAddress);
// t1 spends to our wallet. // t1 spends to our wallet.
myWallet.receivePending(doubleSpends.t1); myWallet.receivePending(doubleSpends.t1, null);
// t2 rolls back t1 and spends somewhere else. // t2 rolls back t1 and spends somewhere else.
myWallet.receiveFromBlock(doubleSpends.t2, null, BlockChain.NewBlockType.BEST_CHAIN); myWallet.receiveFromBlock(doubleSpends.t2, null, BlockChain.NewBlockType.BEST_CHAIN);
Wallet wallet1 = roundTrip(myWallet); Wallet wallet1 = roundTrip(myWallet);