diff --git a/pom.xml b/pom.xml
index 3a82c4a8..39d93c7d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -249,6 +249,24 @@
jar
test
+
+ org.easymock
+ easymock
+ 3.0
+ test
+
+
+ cglib
+ cglib
+ 2.2.2
+ test
+
+
+ org.objenesis
+ objenesis
+ 1.2
+ test
+
org.slf4j
slf4j-api
diff --git a/src/com/google/bitcoin/core/GetBlocksMessage.java b/src/com/google/bitcoin/core/GetBlocksMessage.java
index 654b4fa4..9b71765b 100644
--- a/src/com/google/bitcoin/core/GetBlocksMessage.java
+++ b/src/com/google/bitcoin/core/GetBlocksMessage.java
@@ -33,6 +33,14 @@ public class GetBlocksMessage extends Message {
public void parse() {
}
+
+ public List getLocator() {
+ return locator;
+ }
+
+ public Sha256Hash getStopHash() {
+ return stopHash;
+ }
public String toString() {
StringBuffer b = new StringBuffer();
diff --git a/src/com/google/bitcoin/core/Peer.java b/src/com/google/bitcoin/core/Peer.java
index 49808227..52b64647 100644
--- a/src/com/google/bitcoin/core/Peer.java
+++ b/src/com/google/bitcoin/core/Peer.java
@@ -81,8 +81,8 @@ public class Peer {
eventListeners.add(listener);
}
- public synchronized void removeEventListener(PeerEventListener listener) {
- eventListeners.remove(listener);
+ public synchronized boolean removeEventListener(PeerEventListener listener) {
+ return eventListeners.remove(listener);
}
@Override
@@ -105,6 +105,11 @@ public class Peer {
}
}
+ // For testing
+ void setConnection(NetworkConnection conn) {
+ this.conn = conn;
+ }
+
/**
* Runs in the peers network loop and manages communication with the peer.
*
diff --git a/tests/com/google/bitcoin/core/PeerTest.java b/tests/com/google/bitcoin/core/PeerTest.java
new file mode 100644
index 00000000..bd8e7369
--- /dev/null
+++ b/tests/com/google/bitcoin/core/PeerTest.java
@@ -0,0 +1,337 @@
+package com.google.bitcoin.core;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Future;
+
+import static org.easymock.EasyMock.*;
+
+import org.easymock.Capture;
+import org.easymock.IAnswer;
+import org.easymock.IMocksControl;
+import org.junit.Before;
+import org.junit.Test;
+
+
+import com.google.bitcoin.store.MemoryBlockStore;
+
+public class PeerTest {
+
+ private Peer peer;
+ private IMocksControl control;
+ private NetworkConnection conn;
+ private NetworkParameters unitTestParams;
+ private MemoryBlockStore blockStore;
+ private BlockChain blockChain;
+
+ @Before
+ public void setUp() throws Exception {
+ control = createStrictControl();
+ control.checkOrder(true);
+ unitTestParams = NetworkParameters.unitTests();
+ blockStore = new MemoryBlockStore(unitTestParams);
+ blockChain = new BlockChain(unitTestParams, new Wallet(unitTestParams), blockStore);
+ PeerAddress address = new PeerAddress(InetAddress.getLocalHost());
+ //peer = new Peer(unitTestParams, address , testNetChain );
+ conn = createMockBuilder(NetworkConnection.class)
+ .addMockedMethod("getVersionMessage")
+ .addMockedMethod("readMessage")
+ .addMockedMethod("writeMessage")
+ .addMockedMethod("shutdown")
+ .addMockedMethod("toString")
+ .createMock(control);
+ peer = new Peer(unitTestParams, address , blockChain);
+ peer.setConnection(conn);
+ }
+
+ @Test
+ public void testAddEventListener() {
+ PeerEventListener listener = new AbstractPeerEventListener();
+ peer.addEventListener(listener);
+ assertTrue(peer.removeEventListener(listener));
+ assertFalse(peer.removeEventListener(listener));
+ }
+
+ @Test
+ public void testRun_exception() throws Exception {
+ expect(conn.readMessage()).andThrow(new IOException("done"));
+ conn.shutdown();
+ expectLastCall();
+
+ control.replay();
+
+ try {
+ peer.run();
+ fail("did not throw");
+ } catch (PeerException e) {
+ // expected
+ assert(e.getCause() instanceof IOException);
+ }
+
+ control.verify();
+
+ control.reset();
+ expect(conn.readMessage()).andThrow(new ProtocolException("proto"));
+ conn.shutdown();
+ expectLastCall();
+
+ control.replay();
+
+ try {
+ peer.run();
+ fail("did not throw");
+ } catch (PeerException e) {
+ // expected
+ assert(e.getCause() instanceof PeerException);
+ }
+
+ control.verify();
+ }
+
+ @Test
+ public void testRun_normal() throws Exception {
+ expectPeerDisconnect();
+
+ control.replay();
+
+ peer.run();
+ control.verify();
+ }
+
+ @Test
+ public void testRun_unconnected_block() throws Exception {
+ PeerEventListener listener = control.createMock(PeerEventListener.class);
+ peer.addEventListener(listener);
+
+ Block b1 = TestUtils.createFakeBlock(unitTestParams, blockStore).block;
+ blockChain.add(b1);
+
+ Block prev = TestUtils.makeSolvedTestBlock(unitTestParams, blockStore);
+ final Block block = TestUtils.makeSolvedTestBlock(unitTestParams, prev);
+
+ expect(conn.readMessage()).andAnswer(new IAnswer() {
+ public Message answer() throws Throwable {
+ return block;
+ }
+ });
+ Capture message = new Capture();
+
+ conn.writeMessage(capture(message));
+ expectLastCall();
+
+ expectPeerDisconnect();
+
+ control.replay();
+
+ peer.run();
+ control.verify();
+
+ List expectedLocator = new ArrayList();
+ expectedLocator.add(b1.getHash());
+ expectedLocator.add(unitTestParams.genesisBlock.getHash());
+
+ assertEquals(message.getValue().getLocator(), expectedLocator);
+ assertEquals(message.getValue().getStopHash(), block.getHash());
+ }
+
+ @Test
+ public void testRun_inv_tickle() throws Exception {
+ PeerEventListener listener = control.createMock(PeerEventListener.class);
+ peer.addEventListener(listener);
+
+ Block b1 = TestUtils.createFakeBlock(unitTestParams, blockStore).block;
+ blockChain.add(b1);
+
+ Block prev = TestUtils.makeSolvedTestBlock(unitTestParams, blockStore);
+ final Block block = TestUtils.makeSolvedTestBlock(unitTestParams, prev);
+
+ expect(conn.readMessage()).andAnswer(new IAnswer() {
+ public Message answer() throws Throwable {
+ return block;
+ }
+ });
+
+ conn.writeMessage(anyObject(Message.class));
+ expectLastCall();
+
+ expect(conn.readMessage()).andAnswer(new IAnswer() {
+ public Message answer() throws Throwable {
+ InventoryMessage inv = new InventoryMessage(unitTestParams);
+ InventoryItem item = new InventoryItem(InventoryItem.Type.Block, block.getHash());
+ inv.addItem(item);
+ return inv;
+ }
+ });
+
+ Capture message = new Capture();
+ conn.writeMessage(capture(message));
+ expectLastCall();
+
+ expectPeerDisconnect();
+
+ control.replay();
+
+ peer.run();
+ control.verify();
+
+ List expectedLocator = new ArrayList();
+ expectedLocator.add(b1.getHash());
+ expectedLocator.add(unitTestParams.genesisBlock.getHash());
+
+ assertEquals(message.getValue().getLocator(), expectedLocator);
+ assertEquals(message.getValue().getStopHash(), block.getHash());
+ }
+
+ @Test
+ public void testRun_inv_block() throws Exception {
+ PeerEventListener listener = control.createMock(PeerEventListener.class);
+ peer.addEventListener(listener);
+
+ Block b1 = TestUtils.createFakeBlock(unitTestParams, blockStore).block;
+ blockChain.add(b1);
+
+ Block prev = TestUtils.makeSolvedTestBlock(unitTestParams, blockStore);
+ final Block b2 = TestUtils.makeSolvedTestBlock(unitTestParams, prev);
+ final Block b3 = TestUtils.makeSolvedTestBlock(unitTestParams, b2);
+
+ expect(conn.readMessage()).andAnswer(new IAnswer() {
+ public Message answer() throws Throwable {
+ return b2;
+ }
+ });
+
+ conn.writeMessage(anyObject(Message.class));
+ expectLastCall();
+
+ expect(conn.readMessage()).andAnswer(new IAnswer() {
+ public Message answer() throws Throwable {
+ InventoryMessage inv = new InventoryMessage(unitTestParams);
+ InventoryItem item = new InventoryItem(InventoryItem.Type.Block, b3.getHash());
+ inv.addItem(item);
+ return inv;
+ }
+ });
+
+ Capture message = new Capture();
+ conn.writeMessage(capture(message));
+ expectLastCall();
+
+ expectPeerDisconnect();
+
+ control.replay();
+
+ peer.run();
+ control.verify();
+
+ List items = message.getValue().getItems();
+ assertEquals(1, items.size());
+ assertEquals(b3.getHash(), items.get(0).hash);
+ assertEquals(InventoryItem.Type.Block, items.get(0).type);
+ }
+
+ @Test
+ public void testStartBlockChainDownload() throws Exception {
+ PeerEventListener listener = control.createMock(PeerEventListener.class);
+ peer.addEventListener(listener);
+
+ Block b1 = TestUtils.createFakeBlock(unitTestParams, blockStore).block;
+ blockChain.add(b1);
+
+ expect(conn.getVersionMessage()).andStubReturn(new VersionMessage(unitTestParams, 100));
+
+ listener.onChainDownloadStarted(peer, 99);
+ expectLastCall();
+
+ Capture message = new Capture();
+ conn.writeMessage(capture(message));
+ expectLastCall();
+
+ control.replay();
+
+ peer.startBlockChainDownload();
+ control.verify();
+
+ List expectedLocator = new ArrayList();
+ expectedLocator.add(b1.getHash());
+ expectedLocator.add(unitTestParams.genesisBlock.getHash());
+
+ assertEquals(message.getValue().getLocator(), expectedLocator);
+ assertEquals(message.getValue().getStopHash(), Sha256Hash.ZERO_HASH);
+ }
+
+ @Test
+ public void testGetBlock() throws Exception {
+ PeerEventListener listener = control.createMock(PeerEventListener.class);
+ peer.addEventListener(listener);
+
+ Block b1 = TestUtils.createFakeBlock(unitTestParams, blockStore).block;
+ blockChain.add(b1);
+
+ Block prev = TestUtils.makeSolvedTestBlock(unitTestParams, blockStore);
+ final Block b2 = TestUtils.makeSolvedTestBlock(unitTestParams, prev);
+
+ expect(conn.getVersionMessage()).andStubReturn(new VersionMessage(unitTestParams, 100));
+
+ Capture message = new Capture();
+ conn.writeMessage(capture(message));
+ expectLastCall();
+
+ expect(conn.readMessage()).andReturn(b2);
+
+ expectPeerDisconnect();
+
+ control.replay();
+
+ Future resultFuture = peer.getBlock(b2.getHash());
+ peer.run();
+
+ assertEquals(b2.getHash(), resultFuture.get().getHash());
+
+ control.verify();
+
+ List expectedLocator = new ArrayList();
+ expectedLocator.add(b1.getHash());
+ expectedLocator.add(unitTestParams.genesisBlock.getHash());
+
+ List items = message.getValue().getItems();
+ assertEquals(1, items.size());
+ assertEquals(b2.getHash(), items.get(0).hash);
+ assertEquals(InventoryItem.Type.Block, items.get(0).type);
+ }
+
+ @Test
+ public void testRun_new_block() throws Exception {
+ PeerEventListener listener = control.createMock(PeerEventListener.class);
+ peer.addEventListener(listener);
+
+ expect(conn.readMessage()).andAnswer(new IAnswer() {
+ public Message answer() throws Throwable {
+ return TestUtils.makeSolvedTestBlock(unitTestParams, blockStore);
+ }
+ });
+ expect(conn.getVersionMessage()).andReturn(new VersionMessage(unitTestParams, 100));
+ listener.onBlocksDownloaded(eq(peer), anyObject(Block.class), eq(99));
+ expectLastCall();
+ expectPeerDisconnect();
+
+ control.replay();
+
+ peer.run();
+ control.verify();
+ }
+
+ private void expectPeerDisconnect() throws IOException, ProtocolException {
+ expect(conn.readMessage()).andAnswer(new IAnswer() {
+ public Message answer() throws Throwable {
+ peer.disconnect();
+ throw new IOException("done");
+ }
+ });
+ conn.shutdown();
+ expectLastCall().times(2);
+ }
+}
diff --git a/tests/com/google/bitcoin/core/TestUtils.java b/tests/com/google/bitcoin/core/TestUtils.java
index 57ae6d5d..523cf905 100644
--- a/tests/com/google/bitcoin/core/TestUtils.java
+++ b/tests/com/google/bitcoin/core/TestUtils.java
@@ -45,7 +45,7 @@ public class TestUtils {
public static BlockPair createFakeBlock(NetworkParameters params, BlockStore blockStore,
Transaction... transactions) {
try {
- Block b = blockStore.getChainHead().getHeader().createNextBlock(new ECKey().toAddress(params));
+ Block b = makeTestBlock(params, blockStore);
// Coinbase tx was already added.
for (Transaction tx : transactions)
b.addTransaction(tx);
@@ -62,4 +62,23 @@ public class TestUtils {
throw new RuntimeException(e); // Cannot happen.
}
}
+
+ public static Block makeTestBlock(NetworkParameters params,
+ BlockStore blockStore) throws BlockStoreException {
+ return blockStore.getChainHead().getHeader().createNextBlock(new ECKey().toAddress(params));
+ }
+
+ public static Block makeSolvedTestBlock(NetworkParameters params,
+ BlockStore blockStore) throws BlockStoreException {
+ Block b = blockStore.getChainHead().getHeader().createNextBlock(new ECKey().toAddress(params));
+ b.solve();
+ return b;
+ }
+
+ public static Block makeSolvedTestBlock(NetworkParameters params,
+ Block prev) throws BlockStoreException {
+ Block b = prev.createNextBlock(new ECKey().toAddress(params));
+ b.solve();
+ return b;
+ }
}