/*
* Copyright 2011 Google Inc.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bitcoinj.core;
import org.bitcoinj.net.discovery.PeerDiscovery;
import org.bitcoinj.net.discovery.PeerDiscoveryException;
import org.bitcoinj.params.UnitTestParams;
import org.bitcoinj.testing.FakeTxBuilder;
import org.bitcoinj.testing.InboundMessageQueuer;
import org.bitcoinj.testing.TestWithPeerGroup;
import org.bitcoinj.utils.Threading;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.net.InetAddresses;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static org.bitcoinj.core.Coin.COIN;
import static org.bitcoinj.core.Coin.valueOf;
import static org.junit.Assert.*;
// TX announcement and broadcast is tested in TransactionBroadcastTest.
@RunWith(value = Parameterized.class)
public class PeerGroupTest extends TestWithPeerGroup {
static final NetworkParameters params = UnitTestParams.get();
private BlockingQueue<Peer> connectedPeers;
private BlockingQueue<Peer> disconnectedPeers;
private PeerEventListener listener;
private Map<Peer, AtomicInteger> peerToMessageCount;
@Parameterized.Parameters
public static Collection<ClientType[]> parameters() {
return Arrays.asList(new ClientType[] {ClientType.NIO_CLIENT_MANAGER},
new ClientType[] {ClientType.BLOCKING_CLIENT_MANAGER});
}
public PeerGroupTest(ClientType clientType) {
super(clientType);
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
peerToMessageCount = new HashMap<Peer, AtomicInteger>();
connectedPeers = new LinkedBlockingQueue<Peer>();
disconnectedPeers = new LinkedBlockingQueue<Peer>();
listener = new AbstractPeerEventListener() {
@Override
public void onPeerConnected(Peer peer, int peerCount) {
connectedPeers.add(peer);
}
@Override
public void onPeerDisconnected(Peer peer, int peerCount) {
disconnectedPeers.add(peer);
}
@Override
public Message onPreMessageReceived(Peer peer, Message m) {
AtomicInteger messageCount = peerToMessageCount.get(peer);
if (messageCount == null) {
messageCount = new AtomicInteger(0);
peerToMessageCount.put(peer, messageCount);
}
messageCount.incrementAndGet();
// Just pass the message right through for further processing.
return m;
}
};
}
@Override
@After
public void tearDown() {
super.tearDown();
}
@Test
public void listener() throws Exception {
peerGroup.startAsync();
peerGroup.awaitRunning();
peerGroup.addEventListener(listener);
// Create a couple of peers.
InboundMessageQueuer p1 = connectPeer(1);
InboundMessageQueuer p2 = connectPeer(2);
connectedPeers.take();
connectedPeers.take();
pingAndWait(p1);
pingAndWait(p2);
Threading.waitForUserCode();
assertEquals(0, disconnectedPeers.size());
p1.close();
disconnectedPeers.take();
assertEquals(0, disconnectedPeers.size());
p2.close();
disconnectedPeers.take();
assertEquals(0, disconnectedPeers.size());
assertTrue(peerGroup.removeEventListener(listener));
assertFalse(peerGroup.removeEventListener(listener));
}
@Test
public void peerDiscoveryPolling() throws InterruptedException {
// Check that if peer discovery fails, we keep trying until we have some nodes to talk with.
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean result = new AtomicBoolean();
peerGroup.addPeerDiscovery(new PeerDiscovery() {
@Override
public InetSocketAddress[] getPeers(long unused, TimeUnit unused2) throws PeerDiscoveryException {
if (!result.getAndSet(true)) {
// Pretend we are not connected to the internet.
throw new PeerDiscoveryException("test failure");
} else {
// Return a bogus address.
latch.countDown();
return new InetSocketAddress[]{new InetSocketAddress("localhost", 1)};
}
}
@Override
public void shutdown() {
}
});
peerGroup.startAsync();
peerGroup.awaitRunning();
latch.await();
// Check that we did indeed throw an exception. If we got here it means we threw and then PeerGroup tried
// again a bit later.
assertTrue(result.get());
}
@Test
public void receiveTxBroadcast() throws Exception {
// Check that when we receive transactions on all our peers, we do the right thing.
peerGroup.startAsync();
peerGroup.awaitRunning();
// Create a couple of peers.
InboundMessageQueuer p1 = connectPeer(1);
InboundMessageQueuer p2 = connectPeer(2);
// Check the peer accessors.
assertEquals(2, peerGroup.numConnectedPeers());
Set<Peer> tmp = new HashSet<Peer>(peerGroup.getConnectedPeers());
Set<Peer> expectedPeers = new HashSet<Peer>();
expectedPeers.add(peerOf(p1));
expectedPeers.add(peerOf(p2));
assertEquals(tmp, expectedPeers);
Coin value = COIN;
Transaction t1 = FakeTxBuilder.createFakeTx(unitTestParams, value, address);
InventoryMessage inv = new InventoryMessage(unitTestParams);
inv.addTransaction(t1);
// Note: we start with p2 here to verify that transactions are downloaded from whichever peer announces first
// which does not have to be the same as the download peer (which is really the "block download peer").
inbound(p2, inv);
assertTrue(outbound(p2) instanceof GetDataMessage);
inbound(p1, inv);
assertNull(outbound(p1)); // Only one peer is used to download.
inbound(p2, t1);
assertNull(outbound(p1));
// Asks for dependency.
GetDataMessage getdata = (GetDataMessage) outbound(p2);
assertNotNull(getdata);
inbound(p2, new NotFoundMessage(unitTestParams, getdata.getItems()));
pingAndWait(p2);
assertEquals(value, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
peerGroup.stopAsync();
peerGroup.awaitTerminated();
}
@Test
public void singleDownloadPeer1() throws Exception {
// Check that we don't attempt to retrieve blocks on multiple peers.
peerGroup.startAsync();
peerGroup.awaitRunning();
// Create a couple of peers.
InboundMessageQueuer p1 = connectPeer(1);
InboundMessageQueuer p2 = connectPeer(2);
assertEquals(2, peerGroup.numConnectedPeers());
// Set up a little block chain. We heard about b1 but not b2 (it is pending download). b3 is solved whilst we
// are downloading the chain.
Block b1 = FakeTxBuilder.createFakeBlock(blockStore).block;
blockChain.add(b1);
Block b2 = FakeTxBuilder.makeSolvedTestBlock(b1);
Block b3 = FakeTxBuilder.makeSolvedTestBlock(b2);
// Peer 1 and 2 receives an inv advertising a newly solved block.
InventoryMessage inv = new InventoryMessage(params);
inv.addBlock(b3);
// Only peer 1 tries to download it.
inbound(p1, inv);
pingAndWait(p1);
assertTrue(outbound(p1) instanceof GetDataMessage);
assertNull(outbound(p2));
// Peer 1 goes away, peer 2 becomes the download peer and thus queries the remote mempool.
final SettableFuture<Void> p1CloseFuture = SettableFuture.create();
peerOf(p1).addEventListener(new AbstractPeerEventListener() {
@Override
public void onPeerDisconnected(Peer peer, int peerCount) {
p1CloseFuture.set(null);
}
});
closePeer(peerOf(p1));
p1CloseFuture.get();
// Peer 2 fetches it next time it hears an inv (should it fetch immediately?).
inbound(p2, inv);
assertTrue(outbound(p2) instanceof GetDataMessage);
peerGroup.stopAsync();
}
@Test
public void singleDownloadPeer2() throws Exception {
// Check that we don't attempt multiple simultaneous block chain downloads, when adding a new peer in the
// middle of an existing chain download.
// Create a couple of peers.
peerGroup.startAsync();
peerGroup.awaitRunning();
// Create a couple of peers.
InboundMessageQueuer p1 = connectPeer(1);
// Set up a little block chain.
Block b1 = FakeTxBuilder.createFakeBlock(blockStore).block;
Block b2 = FakeTxBuilder.makeSolvedTestBlock(b1);
Block b3 = FakeTxBuilder.makeSolvedTestBlock(b2);
// Expect a zero hash getblocks on p1. This is how the process starts.
peerGroup.startBlockChainDownload(new AbstractPeerEventListener() {
});
GetBlocksMessage getblocks = (GetBlocksMessage) outbound(p1);
assertEquals(Sha256Hash.ZERO_HASH, getblocks.getStopHash());
// We give back an inv with some blocks in it.
InventoryMessage inv = new InventoryMessage(params);
inv.addBlock(b1);
inv.addBlock(b2);
inv.addBlock(b3);
inbound(p1, inv);
assertTrue(outbound(p1) instanceof GetDataMessage);
// We hand back the first block.
inbound(p1, b1);
// Now we successfully connect to another peer. There should be no messages sent.
InboundMessageQueuer p2 = connectPeer(2);
Message message = (Message)outbound(p2);
assertNull(message == null ? "" : message.toString(), message);
peerGroup.stopAsync();
}
@Test
public void transactionConfidence() throws Exception {
// Checks that we correctly count how many peers broadcast a transaction, so we can establish some measure of
// its trustworthyness assuming an untampered with internet connection.
peerGroup.startAsync();
peerGroup.awaitRunning();
final Transaction[] event = new Transaction[2];
peerGroup.addEventListener(new AbstractPeerEventListener() {
@Override
public void onTransaction(Peer peer, Transaction t) {
event[0] = t;
}
}, Threading.SAME_THREAD);
InboundMessageQueuer p1 = connectPeer(1);
InboundMessageQueuer p2 = connectPeer(2);
InboundMessageQueuer p3 = connectPeer(3);
Transaction tx = FakeTxBuilder.createFakeTx(params, valueOf(20, 0), address);
InventoryMessage inv = new InventoryMessage(params);
inv.addTransaction(tx);
// Peer 2 advertises the tx but does not receive it yet.
inbound(p2, inv);
assertTrue(outbound(p2) instanceof GetDataMessage);
assertEquals(0, tx.getConfidence().numBroadcastPeers());
assertTrue(peerGroup.getMemoryPool().maybeWasSeen(tx.getHash()));
assertNull(event[0]);
// Peer 1 advertises the tx, we don't do anything as it's already been requested.
inbound(p1, inv);
assertNull(outbound(p1));
// Peer 2 gets sent the tx and requests the dependency.
inbound(p2, tx);
assertTrue(outbound(p2) instanceof GetDataMessage);
tx = event[0]; // We want to use the canonical copy delivered by the PeerGroup from now on.
assertNotNull(tx);
event[0] = null;
// Peer 1 (the download peer) advertises the tx, we download it.
inbound(p1, inv); // returns getdata
inbound(p1, tx); // returns nothing after a queue drain.
// Two peers saw this tx hash.
assertEquals(2, tx.getConfidence().numBroadcastPeers());
assertTrue(tx.getConfidence().wasBroadcastBy(peerOf(p1).getAddress()));
assertTrue(tx.getConfidence().wasBroadcastBy(peerOf(p2).getAddress()));
tx.getConfidence().addEventListener(new TransactionConfidence.Listener() {
@Override
public void onConfidenceChanged(Transaction tx, TransactionConfidence.Listener.ChangeReason reason) {
event[1] = tx;
}
});
// A straggler reports in.
inbound(p3, inv);
pingAndWait(p3);
Threading.waitForUserCode();
assertEquals(tx, event[1]);
assertEquals(3, tx.getConfidence().numBroadcastPeers());
assertTrue(tx.getConfidence().wasBroadcastBy(peerOf(p3).getAddress()));
}
@Test
public void testWalletCatchupTime() throws Exception {
// Check the fast catchup time was initialized to something around the current runtime minus a week.
// The wallet was already added to the peer in setup.
final int WEEK = 86400 * 7;
final long now = Utils.currentTimeSeconds();
peerGroup.startAsync();
peerGroup.awaitRunning();
assertTrue(peerGroup.getFastCatchupTimeSecs() > now - WEEK - 10000);
Wallet w2 = new Wallet(params);
ECKey key1 = new ECKey();
key1.setCreationTimeSeconds(now - 86400); // One day ago.
w2.importKey(key1);
peerGroup.addWallet(w2);
peerGroup.waitForJobQueue();
assertEquals(peerGroup.getFastCatchupTimeSecs(), now - 86400 - WEEK);
// Adding a key to the wallet should update the fast catchup time, but asynchronously and in the background
// due to the need to avoid complicated lock inversions.
ECKey key2 = new ECKey();
key2.setCreationTimeSeconds(now - 100000);
w2.importKey(key2);
peerGroup.waitForJobQueue();
assertEquals(peerGroup.getFastCatchupTimeSecs(), now - WEEK - 100000);
}
@Test
public void noPings() throws Exception {
peerGroup.startAsync();
peerGroup.awaitRunning();
peerGroup.setPingIntervalMsec(0);
VersionMessage versionMessage = new VersionMessage(params, 2);
versionMessage.clientVersion = FilteredBlock.MIN_PROTOCOL_VERSION;
versionMessage.localServices = VersionMessage.NODE_NETWORK;
connectPeer(1, versionMessage);
peerGroup.waitForPeers(1).get();
assertFalse(peerGroup.getConnectedPeers().get(0).getLastPingTime() < Long.MAX_VALUE);
}
@Test
public void pings() throws Exception {
peerGroup.startAsync();
peerGroup.awaitRunning();
peerGroup.setPingIntervalMsec(100);
VersionMessage versionMessage = new VersionMessage(params, 2);
versionMessage.clientVersion = FilteredBlock.MIN_PROTOCOL_VERSION;
versionMessage.localServices = VersionMessage.NODE_NETWORK;
InboundMessageQueuer p1 = connectPeer(1, versionMessage);
Ping ping = (Ping) outbound(p1);
inbound(p1, new Pong(ping.getNonce()));
pingAndWait(p1);
assertTrue(peerGroup.getConnectedPeers().get(0).getLastPingTime() < Long.MAX_VALUE);
// The call to outbound should block until a ping arrives.
ping = (Ping) waitForOutbound(p1);
inbound(p1, new Pong(ping.getNonce()));
assertTrue(peerGroup.getConnectedPeers().get(0).getLastPingTime() < Long.MAX_VALUE);
}
@Test
public void downloadPeerSelection() throws Exception {
peerGroup.startAsync();
peerGroup.awaitRunning();
VersionMessage versionMessage2 = new VersionMessage(params, 2);
versionMessage2.clientVersion = FilteredBlock.MIN_PROTOCOL_VERSION;
versionMessage2.localServices = VersionMessage.NODE_NETWORK;
VersionMessage versionMessage3 = new VersionMessage(params, 3);
versionMessage3.clientVersion = FilteredBlock.MIN_PROTOCOL_VERSION;
versionMessage3.localServices = VersionMessage.NODE_NETWORK;
assertNull(peerGroup.getDownloadPeer());
Peer a = connectPeer(1, versionMessage2).peer;
assertEquals(2, peerGroup.getMostCommonChainHeight());
assertEquals(a, peerGroup.getDownloadPeer());
connectPeer(2, versionMessage2);
assertEquals(2, peerGroup.getMostCommonChainHeight());
assertEquals(a, peerGroup.getDownloadPeer()); // No change.
Peer c = connectPeer(3, versionMessage3).peer;
assertEquals(2, peerGroup.getMostCommonChainHeight());
assertEquals(a, peerGroup.getDownloadPeer()); // No change yet.
connectPeer(4, versionMessage3);
assertEquals(3, peerGroup.getMostCommonChainHeight());
assertEquals(c, peerGroup.getDownloadPeer()); // Switch to first peer advertising new height.
// New peer with a higher protocol version but same chain height.
//TODO: When PeerGroup.selectDownloadPeer.PREFERRED_VERSION is not equal to vMinRequiredProtocolVersion,
// reenable this test
/*VersionMessage versionMessage4 = new VersionMessage(params, 3);
versionMessage4.clientVersion = 100000;
versionMessage4.localServices = VersionMessage.NODE_NETWORK;
InboundMessageQueuer d = connectPeer(5, versionMessage4);
assertEquals(d.peer, peerGroup.getDownloadPeer());*/
}
@Test
public void peerTimeoutTest() throws Exception {
peerGroup.startAsync();
peerGroup.awaitRunning();
peerGroup.setConnectTimeoutMillis(100);
final SettableFuture<Void> peerConnectedFuture = SettableFuture.create();
final SettableFuture<Void> peerDisconnectedFuture = SettableFuture.create();
peerGroup.addEventListener(new AbstractPeerEventListener() {
@Override
public void onPeerConnected(Peer peer, int peerCount) {
peerConnectedFuture.set(null);
}
@Override
public void onPeerDisconnected(Peer peer, int peerCount) {
peerDisconnectedFuture.set(null);
}
}, Threading.SAME_THREAD);
connectPeerWithoutVersionExchange(0);
Thread.sleep(50);
assertFalse(peerConnectedFuture.isDone() || peerDisconnectedFuture.isDone());
Thread.sleep(60);
assertTrue(!peerConnectedFuture.isDone());
assertTrue(!peerConnectedFuture.isDone() && peerDisconnectedFuture.isDone());
}
@Test
public void peerPriority() throws Exception {
final List<InetSocketAddress> addresses = Lists.newArrayList(
new InetSocketAddress("localhost", 2000),
new InetSocketAddress("localhost", 2001),
new InetSocketAddress("localhost", 2002)
);
peerGroup.addEventListener(listener);
peerGroup.addPeerDiscovery(new PeerDiscovery() {
@Override
public InetSocketAddress[] getPeers(long unused, TimeUnit unused2) throws PeerDiscoveryException {
return addresses.toArray(new InetSocketAddress[addresses.size()]);
}
@Override
public void shutdown() {
}
});
peerGroup.setMaxConnections(3);
Utils.setMockSleep(true);
peerGroup.startAsync();
peerGroup.awaitRunning();
handleConnectToPeer(0);
handleConnectToPeer(1);
handleConnectToPeer(2);
connectedPeers.take();
connectedPeers.take();
connectedPeers.take();
addresses.clear();
addresses.addAll(Lists.newArrayList(new InetSocketAddress("localhost", 2003)));
stopPeerServer(2);
assertEquals(2002, disconnectedPeers.take().getAddress().getPort()); // peer died
// discovers, connects to new peer
handleConnectToPeer(3);
assertEquals(2003, connectedPeers.take().getAddress().getPort());
stopPeerServer(1);
assertEquals(2001, disconnectedPeers.take().getAddress().getPort()); // peer died
// Alternates trying two offline peers
Utils.passMockSleep();
assertEquals(2001, disconnectedPeers.take().getAddress().getPort());
Utils.passMockSleep();
assertEquals(2002, disconnectedPeers.take().getAddress().getPort());
Utils.passMockSleep();
assertEquals(2001, disconnectedPeers.take().getAddress().getPort());
Utils.passMockSleep();
assertEquals(2002, disconnectedPeers.take().getAddress().getPort());
Utils.passMockSleep();
assertEquals(2001, disconnectedPeers.take().getAddress().getPort());
// Peer 2 comes online
startPeerServer(2);
Utils.passMockSleep();
handleConnectToPeer(2);
assertEquals(2002, connectedPeers.take().getAddress().getPort());
stopPeerServer(2);
assertEquals(2002, disconnectedPeers.take().getAddress().getPort()); // peer died
// Peer 2 is tried before peer 1, since it has a lower backoff due to recent success
Utils.passMockSleep();
assertEquals(2002, disconnectedPeers.take().getAddress().getPort());
Utils.passMockSleep();
assertEquals(2001, disconnectedPeers.take().getAddress().getPort());
}
@Test
public void testBloomOnP2Pubkey() throws Exception {
// Cover bug 513. When a relevant transaction with a p2pubkey output is found, the Bloom filter should be
// recalculated to include that transaction hash but not re-broadcast as the remote nodes should have followed
// the same procedure. However a new node that's connected should get the fresh filter.
peerGroup.startAsync();
peerGroup.awaitRunning();
final ECKey key = wallet.currentReceiveKey();
// Create a couple of peers.
InboundMessageQueuer p1 = connectPeer(1);
InboundMessageQueuer p2 = connectPeer(2);
// Create a pay to pubkey tx.
Transaction tx = FakeTxBuilder.createFakeTx(params, COIN, key);
Transaction tx2 = new Transaction(params);
tx2.addInput(tx.getOutput(0));
TransactionOutPoint outpoint = tx2.getInput(0).getOutpoint();
assertTrue(p1.lastReceivedFilter.contains(key.getPubKey()));
assertFalse(p1.lastReceivedFilter.contains(tx.getHash().getBytes()));
inbound(p1, tx);
// p1 requests dep resolution, p2 is quiet.
assertTrue(outbound(p1) instanceof GetDataMessage);
final Sha256Hash dephash = tx.getInput(0).getOutpoint().getHash();
final InventoryItem inv = new InventoryItem(InventoryItem.Type.Transaction, dephash);
inbound(p1, new NotFoundMessage(params, ImmutableList.of(inv)));
assertNull(outbound(p1));
assertNull(outbound(p2));
peerGroup.waitForJobQueue();
// Now we connect p3 and there is a new bloom filter sent, that DOES match the relevant outpoint.
InboundMessageQueuer p3 = connectPeer(3);
assertTrue(p3.lastReceivedFilter.contains(key.getPubKey()));
assertTrue(p3.lastReceivedFilter.contains(outpoint.bitcoinSerialize()));
}
@Test
public void testBloomResendOnNewKey() throws Exception {
// Check that when we add a new key to the wallet, the Bloom filter is re-calculated and re-sent but only once
// we exceed the lookahead threshold.
wallet.setKeychainLookaheadSize(5);
wallet.setKeychainLookaheadThreshold(4);
peerGroup.startAsync();
peerGroup.awaitRunning();
// Create a couple of peers.
InboundMessageQueuer p1 = connectPeer(1);
InboundMessageQueuer p2 = connectPeer(2);
peerGroup.waitForJobQueue();
BloomFilter f1 = p1.lastReceivedFilter;
ECKey key = null;
// We have to run ahead of the lookahead zone for this test. There should only be one bloom filter recalc.
for (int i = 0; i < wallet.getKeychainLookaheadSize() + wallet.getKeychainLookaheadThreshold() + 1; i++) {
key = wallet.freshReceiveKey();
}
peerGroup.waitForJobQueue();
BloomFilter bf, f2 = null;
while ((bf = (BloomFilter) outbound(p1)) != null) {
assertEquals(MemoryPoolMessage.class, outbound(p1).getClass());
f2 = bf;
}
assertNotNull(key);
assertNotNull(f2);
assertNull(outbound(p1));
// Check the last filter received.
assertNotEquals(f1, f2);
assertTrue(f2.contains(key.getPubKey()));
assertTrue(f2.contains(key.getPubKeyHash()));
assertFalse(f1.contains(key.getPubKey()));
assertFalse(f1.contains(key.getPubKeyHash()));
}
@Test
public void waitForNumPeers1() throws Exception {
ListenableFuture<List<Peer>> future = peerGroup.waitForPeers(3);
peerGroup.startAsync();
peerGroup.awaitRunning();
assertFalse(future.isDone());
connectPeer(1);
assertFalse(future.isDone());
connectPeer(2);
assertFalse(future.isDone());
assertTrue(peerGroup.waitForPeers(2).isDone()); // Immediate completion.
connectPeer(3);
future.get();
assertTrue(future.isDone());
}
@Test
public void waitForPeersOfVersion() throws Exception {
final int baseVer = peerGroup.getMinRequiredProtocolVersion() + 3000;
final int newVer = baseVer + 1000;
ListenableFuture<List<Peer>> future = peerGroup.waitForPeersOfVersion(2, newVer);
VersionMessage ver1 = new VersionMessage(params, 10);
ver1.clientVersion = baseVer;
ver1.localServices = VersionMessage.NODE_NETWORK;
VersionMessage ver2 = new VersionMessage(params, 10);
ver2.clientVersion = newVer;
ver2.localServices = VersionMessage.NODE_NETWORK;
peerGroup.startAsync();
peerGroup.awaitRunning();
assertFalse(future.isDone());
connectPeer(1, ver1);
assertFalse(future.isDone());
connectPeer(2, ver2);
assertFalse(future.isDone());
assertTrue(peerGroup.waitForPeersOfVersion(1, newVer).isDone()); // Immediate completion.
connectPeer(3, ver2);
future.get();
assertTrue(future.isDone());
}
@Test
public void preferLocalPeer() throws IOException {
// Check that if we have a localhost port 8333 then it's used instead of the p2p network.
ServerSocket local = new ServerSocket(params.getPort(), 100, InetAddresses.forString("127.0.0.1"));
try {
peerGroup.startAsync();
peerGroup.awaitRunning();
local.accept().close(); // Probe connect
local.accept(); // Real connect
// If we get here it used the local peer. Check no others are in use.
assertEquals(1, peerGroup.getMaxConnections());
assertEquals(PeerAddress.localhost(params), peerGroup.getPendingPeers().get(0).getAddress());
} finally {
local.close();
}
}
private <T extends Message> T assertNextMessageIs(InboundMessageQueuer q, Class<T> klass) throws Exception {
Message outbound = waitForOutbound(q);
assertEquals(klass, outbound.getClass());
return (T) outbound;
}
@Test
public void autoRescanOnKeyExhaustion() throws Exception {
// Check that if the last key that was inserted into the bloom filter is seen in some requested blocks,
// that the exhausting block is discarded, a new filter is calculated and sent, and then the download resumes.
final int NUM_KEYS = 9;
// First, grab a load of keys from the wallet, and then recreate it so it forgets that those keys were issued.
Wallet shadow = Wallet.fromSeed(wallet.getParams(), wallet.getKeyChainSeed());
List<ECKey> keys = new ArrayList<ECKey>(NUM_KEYS);
for (int i = 0; i < NUM_KEYS; i++) {
keys.add(shadow.freshReceiveKey());
}
// Reduce the number of keys we need to work with to speed up this test.
wallet.setKeychainLookaheadSize(4);
wallet.setKeychainLookaheadThreshold(2);
peerGroup.startAsync();
peerGroup.awaitRunning();
InboundMessageQueuer p1 = connectPeer(1);
assertTrue(p1.lastReceivedFilter.contains(keys.get(0).getPubKey()));
assertTrue(p1.lastReceivedFilter.contains(keys.get(5).getPubKeyHash()));
assertFalse(p1.lastReceivedFilter.contains(keys.get(keys.size() - 1).getPubKey()));
peerGroup.startBlockChainDownload(null);
assertNextMessageIs(p1, GetBlocksMessage.class);
// Make some transactions and blocks that send money to the wallet thus using up all the keys.
List<Block> blocks = Lists.newArrayList();
Coin expectedBalance = Coin.ZERO;
Block prev = blockStore.getChainHead().getHeader();
for (ECKey key1 : keys) {
Address addr = key1.toAddress(params);
Block next = FakeTxBuilder.makeSolvedTestBlock(prev, FakeTxBuilder.createFakeTx(params, Coin.FIFTY_COINS, addr));
expectedBalance = expectedBalance.add(next.getTransactions().get(2).getOutput(0).getValue());
blocks.add(next);
prev = next;
}
// Send the chain that doesn't have all the transactions in it. The blocks after the exhaustion point should all
// be ignored.
int epoch = wallet.keychain.getCombinedKeyLookaheadEpochs();
BloomFilter filter = new BloomFilter(params, p1.lastReceivedFilter.bitcoinSerialize());
filterAndSend(p1, blocks, filter);
Block exhaustionPoint = blocks.get(3);
pingAndWait(p1);
assertNotEquals(epoch, wallet.keychain.getCombinedKeyLookaheadEpochs());
// 4th block was end of the lookahead zone and thus was discarded, so we got 3 blocks worth of money (50 each).
assertEquals(Coin.FIFTY_COINS.multiply(3), wallet.getBalance());
assertEquals(exhaustionPoint.getPrevBlockHash(), blockChain.getChainHead().getHeader().getHash());
// Await the new filter.
peerGroup.waitForJobQueue();
BloomFilter newFilter = assertNextMessageIs(p1, BloomFilter.class);
assertNotEquals(filter, newFilter);
assertNextMessageIs(p1, MemoryPoolMessage.class);
Ping ping = assertNextMessageIs(p1, Ping.class);
inbound(p1, new Pong(ping.getNonce()));
// Await restart of the chain download.
GetDataMessage getdata = assertNextMessageIs(p1, GetDataMessage.class);
assertEquals(exhaustionPoint.getHash(), getdata.getHashOf(0));
assertEquals(InventoryItem.Type.FilteredBlock, getdata.getItems().get(0).type);
List<Block> newBlocks = blocks.subList(3, blocks.size());
filterAndSend(p1, newBlocks, newFilter);
assertNextMessageIs(p1, Ping.class);
// It happened again.
peerGroup.waitForJobQueue();
newFilter = assertNextMessageIs(p1, BloomFilter.class);
assertNextMessageIs(p1, MemoryPoolMessage.class);
inbound(p1, new Pong(assertNextMessageIs(p1, Ping.class).getNonce()));
assertNextMessageIs(p1, GetDataMessage.class);
newBlocks = blocks.subList(6, blocks.size());
filterAndSend(p1, newBlocks, newFilter);
// Send a non-tx message so the peer knows the filtered block is over and force processing.
inbound(p1, new Ping());
pingAndWait(p1);
assertEquals(expectedBalance, wallet.getBalance());
assertEquals(blocks.get(blocks.size() - 1).getHash(), blockChain.getChainHead().getHeader().getHash());
}
private void filterAndSend(InboundMessageQueuer p1, List<Block> blocks, BloomFilter filter) {
for (Block block : blocks) {
FilteredBlock fb = filter.applyAndUpdate(block);
inbound(p1, fb);
for (Transaction tx : fb.getAssociatedTransactions().values())
inbound(p1, tx);
}
}
}