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; + } }