mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-01-30 23:02:15 +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:
parent
bfcf67ee5a
commit
35d6084bbf
@ -69,6 +69,7 @@ public class Peer {
|
||||
private int blocksAnnounced;
|
||||
// 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.
|
||||
// TODO: Make this final and unsynchronized.
|
||||
private MemoryPool memoryPool;
|
||||
// Each wallet added to the peer will be notified of downloaded transaction data.
|
||||
private CopyOnWriteArrayList<Wallet> wallets;
|
||||
@ -415,13 +416,43 @@ public class Peer {
|
||||
// We may get back a different transaction object.
|
||||
tx = memoryPool.seen(tx, getAddress());
|
||||
}
|
||||
if (maybeHandleRequestedData(tx))
|
||||
final Transaction fTx = tx;
|
||||
if (maybeHandleRequestedData(fTx))
|
||||
return;
|
||||
// Tell all wallets about this tx so they can check if it's relevant or not.
|
||||
for (ListIterator<Wallet> it = wallets.listIterator(); it.hasNext();) {
|
||||
Wallet wallet = it.next();
|
||||
final Wallet wallet = it.next();
|
||||
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) {
|
||||
log.error("Wallet failed to verify tx", e);
|
||||
// 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
|
||||
* 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
|
||||
* 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()) {
|
||||
// There may be multiple inputs that connect to the same transaction.
|
||||
Sha256Hash hash = input.getOutpoint().getHash();
|
||||
Transaction dep = memoryPool.get(hash);
|
||||
if (dep == null) {
|
||||
needToRequest.add(hash);
|
||||
} else {
|
||||
dependencies.add(dep);
|
||||
synchronized (this) {
|
||||
Transaction dep = memoryPool.get(hash);
|
||||
if (dep == null) {
|
||||
needToRequest.add(hash);
|
||||
} else {
|
||||
dependencies.add(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
results.addAll(dependencies);
|
||||
|
@ -1074,7 +1074,10 @@ public class PeerGroup extends AbstractIdleService {
|
||||
synchronized (PeerGroup.this) {
|
||||
for (Wallet wallet : wallets) {
|
||||
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) {
|
||||
future.setException(t);
|
||||
return;
|
||||
@ -1106,7 +1109,10 @@ public class PeerGroup extends AbstractIdleService {
|
||||
// wallet now we know it's valid.
|
||||
for (Wallet wallet : wallets) {
|
||||
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) {
|
||||
future.setException(t);
|
||||
return;
|
||||
|
@ -192,6 +192,8 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
// 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.
|
||||
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,
|
||||
@ -226,6 +228,7 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
invokeOnWalletChanged();
|
||||
}
|
||||
};
|
||||
acceptTimeLockedTransactions = false;
|
||||
}
|
||||
|
||||
public NetworkParameters getNetworkParameters() {
|
||||
@ -290,6 +293,28 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
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
|
||||
// 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
|
||||
@ -640,35 +665,35 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
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
|
||||
* 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
|
||||
* be double spent as an optimization.
|
||||
* be double spent as an optimization.</p>
|
||||
*
|
||||
* @param tx
|
||||
* @throws VerificationException
|
||||
* <p>Before this method is called, {@link Wallet#isPendingTransactionRelevant(Transaction)} should have been
|
||||
* 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 {
|
||||
// Can run in a peer thread.
|
||||
|
||||
// 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());
|
||||
public synchronized void receivePending(Transaction tx, List<Transaction> dependencies) throws VerificationException {
|
||||
// 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
|
||||
// spend against one of our other pending transactions.
|
||||
//
|
||||
// Do a brief risk analysis of the transaction and its dependencies to check for any possible attacks.
|
||||
AnalysisResult analysis = analyzeTransactionAndDependencies(tx, dependencies);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 valueSentFromMe = tx.getValueSentFromMe(this);
|
||||
if (log.isInfoEnabled()) {
|
||||
@ -676,7 +701,6 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
" and sends us %s BTC", tx.getHashAsString(), Utils.bitcoinValueToFriendlyString(valueSentFromMe),
|
||||
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
|
||||
// the Peer before we got to this point, but in some cases (unit tests, other sources of transactions) it may
|
||||
// have been missed out.
|
||||
@ -687,7 +711,6 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
// txConfidenceListener wasn't added.
|
||||
invokeOnTransactionConfidenceChanged(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
|
||||
// timestamp on the transaction and registers/runs event listeners.
|
||||
@ -696,6 +719,51 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
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.
|
||||
private void invokeOnCoinsReceived(final Transaction tx, final BigInteger balance, final BigInteger newBalance) {
|
||||
EventListenerInvoker.invoke(eventListeners, new EventListenerInvoker<WalletEventListener>() {
|
||||
|
@ -119,6 +119,9 @@ public class PeerGroupTest extends TestWithPeerGroup {
|
||||
assertNull(outbound(p2)); // Only one peer is used to download.
|
||||
inbound(p1, t1);
|
||||
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));
|
||||
peerGroup.stop();
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
@ -252,11 +253,13 @@ public class PeerTest extends TestWithNetworkConnections {
|
||||
inv.addItem(item);
|
||||
inbound(peer, inv);
|
||||
// Peer hasn't seen it before, so will ask for it.
|
||||
|
||||
GetDataMessage message = (GetDataMessage) event.getValue().getMessage();
|
||||
assertEquals(1, message.getItems().size());
|
||||
assertEquals(tx.getHash(), message.getItems().get(0).hash);
|
||||
GetDataMessage getdata = (GetDataMessage) outbound();
|
||||
assertEquals(1, getdata.getItems().size());
|
||||
assertEquals(tx.getHash(), getdata.getItems().get(0).hash);
|
||||
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));
|
||||
}
|
||||
|
||||
@ -475,19 +478,15 @@ public class PeerTest extends TestWithNetworkConnections {
|
||||
|
||||
@Test
|
||||
public void recursiveDownload() throws Exception {
|
||||
// Our peer announces a normal transaction that depends on a different transaction that is time locked such
|
||||
// that it will never confirm. There's not currently any use case for doing that to us, so it's an attack.
|
||||
//
|
||||
// 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).
|
||||
// Check that we can download all dependencies of an unconfirmed relevant transaction from the mempool.
|
||||
ECKey to = new ECKey();
|
||||
control.replay();
|
||||
connect();
|
||||
|
||||
final Transaction[] onTx = new Transaction[1];
|
||||
peer.addEventListener(new AbstractPeerEventListener() {
|
||||
@Override
|
||||
public void onTransaction(Peer peer, Transaction t) {
|
||||
public void onTransaction(Peer peer1, Transaction t) {
|
||||
onTx[0] = t;
|
||||
}
|
||||
});
|
||||
@ -496,7 +495,6 @@ public class PeerTest extends TestWithNetworkConnections {
|
||||
// t1 -> t2 -> [t5]
|
||||
// -> t3 -> t4 -> [t6]
|
||||
// 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);
|
||||
Sha256Hash t5 = t2.getInput(0).getOutpoint().getHash();
|
||||
Transaction t4 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), new ECKey());
|
||||
@ -559,6 +557,113 @@ public class PeerTest extends TestWithNetworkConnections {
|
||||
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.
|
||||
private Message outbound() {
|
||||
List<DownstreamMessageEvent> messages = event.getValues();
|
||||
|
@ -443,7 +443,7 @@ public class WalletTest {
|
||||
|
||||
TestUtils.DoubleSpends doubleSpends = TestUtils.createFakeDoubleSpendTxns(params, myAddress);
|
||||
// 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,
|
||||
doubleSpends.t1.getConfidence().getConfidenceType());
|
||||
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[1]); // is pending
|
||||
flags[0] = false;
|
||||
// Check we don't get notified if we receive it again.
|
||||
wallet.receivePending(t1);
|
||||
assertFalse(wallet.isPendingTransactionRelevant(t1));
|
||||
assertFalse(flags[0]);
|
||||
// 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.
|
||||
@ -513,7 +514,8 @@ public class WalletTest {
|
||||
flags[0] = false;
|
||||
flags[1] = false;
|
||||
Transaction irrelevant = createFakeTx(params, nanos, new ECKey().toAddress(params));
|
||||
wallet.receivePending(irrelevant);
|
||||
if (wallet.isPendingTransactionRelevant(irrelevant))
|
||||
wallet.receivePending(irrelevant, null);
|
||||
assertFalse(flags[0]);
|
||||
assertEquals(2, walletChanged[0]);
|
||||
}
|
||||
@ -542,7 +544,8 @@ public class WalletTest {
|
||||
BigInteger halfNanos = Utils.toNanoCoins(0, 50);
|
||||
Transaction t2 = wallet.createSend(new ECKey().toAddress(params), halfNanos);
|
||||
// Now receive it as pending.
|
||||
wallet.receivePending(t2);
|
||||
if (wallet.isPendingTransactionRelevant(t2))
|
||||
wallet.receivePending(t2, null);
|
||||
// We received an onCoinsSent() callback.
|
||||
assertEquals(t2, txn[0]);
|
||||
assertEquals(nanos, bigints[0]);
|
||||
@ -590,7 +593,8 @@ public class WalletTest {
|
||||
});
|
||||
|
||||
assertEquals(BigInteger.ZERO, wallet.getBalance());
|
||||
wallet.receivePending(t1);
|
||||
if (wallet.isPendingTransactionRelevant(t1))
|
||||
wallet.receivePending(t1, null);
|
||||
assertEquals(t1, called[0]);
|
||||
assertEquals(nanos, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
|
||||
// Now receive a double spend on the main chain.
|
||||
@ -724,7 +728,8 @@ public class WalletTest {
|
||||
wallet.addKey(key1);
|
||||
BigInteger value = toNanoCoins(5, 0);
|
||||
Transaction t1 = createFakeTx(params, value, key1);
|
||||
wallet.receivePending(t1);
|
||||
if (wallet.isPendingTransactionRelevant(t1))
|
||||
wallet.receivePending(t1, null);
|
||||
// TX should have been seen as relevant.
|
||||
assertEquals(value, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
|
||||
assertEquals(BigInteger.ZERO, wallet.getBalance(Wallet.BalanceType.AVAILABLE));
|
||||
@ -754,7 +759,8 @@ public class WalletTest {
|
||||
assertFalse(hash1.equals(hash2)); // File has changed.
|
||||
|
||||
Transaction t1 = createFakeTx(params, toNanoCoins(5, 0), key);
|
||||
wallet.receivePending(t1);
|
||||
if (wallet.isPendingTransactionRelevant(t1))
|
||||
wallet.receivePending(t1, null);
|
||||
Sha256Hash hash3 = Sha256Hash.hashFileContents(f);
|
||||
assertFalse(hash2.equals(hash3)); // File has changed again.
|
||||
|
||||
@ -803,7 +809,8 @@ public class WalletTest {
|
||||
results[0] = results[1] = null;
|
||||
|
||||
Transaction t1 = createFakeTx(params, toNanoCoins(5, 0), key);
|
||||
wallet.receivePending(t1);
|
||||
if (wallet.isPendingTransactionRelevant(t1))
|
||||
wallet.receivePending(t1, null);
|
||||
Sha256Hash hash3 = Sha256Hash.hashFileContents(f);
|
||||
assertTrue(hash2.equals(hash3)); // File has NOT changed.
|
||||
assertNull(results[0]);
|
||||
|
@ -87,7 +87,7 @@ public class WalletProtobufSerializerTest {
|
||||
// Check that we can serialize double spends correctly, as this is a slightly tricky case.
|
||||
TestUtils.DoubleSpends doubleSpends = TestUtils.createFakeDoubleSpendTxns(params, myAddress);
|
||||
// t1 spends to our wallet.
|
||||
myWallet.receivePending(doubleSpends.t1);
|
||||
myWallet.receivePending(doubleSpends.t1, null);
|
||||
// t2 rolls back t1 and spends somewhere else.
|
||||
myWallet.receiveFromBlock(doubleSpends.t2, null, BlockChain.NewBlockType.BEST_CHAIN);
|
||||
Wallet wallet1 = roundTrip(myWallet);
|
||||
|
Loading…
Reference in New Issue
Block a user