diff --git a/core/src/main/java/com/google/bitcoin/core/Peer.java b/core/src/main/java/com/google/bitcoin/core/Peer.java index cf332f85..5250df2e 100644 --- a/core/src/main/java/com/google/bitcoin/core/Peer.java +++ b/core/src/main/java/com/google/bitcoin/core/Peer.java @@ -89,7 +89,8 @@ public class Peer { // It is important to avoid a nasty edge case where we can end up with parallel chain downloads proceeding // simultaneously if we were to receive a newly solved block whilst parts of the chain are streaming to us. private HashSet pendingBlockDownloads = new HashSet(); - + // The lowest version number we're willing to accept. Lower than this will result in an immediate disconnect. + private int minProtocolVersion = Pong.MIN_PROTOCOL_VERSION; // When an API user explicitly requests a block or transaction from a peer, the InventoryItem is put here // whilst waiting for the response. Synchronized on itself. Is not used for downloads Peer generates itself. private static class GetDataRequest { @@ -279,6 +280,11 @@ public class Peer { listener.onPeerConnected(Peer.this); } }); + if (peerVersionMessage.clientVersion < minProtocolVersion) { + log.warn("Connected to a peer speaking protocol version {} but need {}, closing", + peerVersionMessage.clientVersion, minProtocolVersion); + e.getChannel().close(); + } } else if (m instanceof VersionAck) { synchronized (Peer.this) { if (peerVersionMessage == null) { @@ -295,7 +301,6 @@ public class Peer { } else if (m instanceof Pong) { processPong((Pong)m); } else { - // TODO: Handle the other messages we can receive. log.warn("Received unhandled message: {}", m); } } @@ -1203,4 +1208,21 @@ public class Peer { public synchronized long getBestHeight() { return peerVersionMessage.bestHeight + blocksAnnounced; } + + /** + * The minimum P2P protocol version that is accepted. If the peer speaks a protocol version lower than this, it + * will be disconnected. + * @return if not-null then this is the future for the Peer disconnection event. + */ + public ChannelFuture setMinProtocolVersion(int minProtocolVersion) { + synchronized (this) { + this.minProtocolVersion = minProtocolVersion; + } + if (getVersionMessage().clientVersion < minProtocolVersion) { + log.warn("{}: Disconnecting due to new min protocol version {}", this, minProtocolVersion); + return Channels.close(channel); + } else { + return null; + } + } } diff --git a/core/src/test/java/com/google/bitcoin/core/PeerTest.java b/core/src/test/java/com/google/bitcoin/core/PeerTest.java index 122c4c48..18debab1 100644 --- a/core/src/test/java/com/google/bitcoin/core/PeerTest.java +++ b/core/src/test/java/com/google/bitcoin/core/PeerTest.java @@ -630,8 +630,7 @@ public class PeerTest extends TestWithNetworkConnections { 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.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)); @@ -664,6 +663,33 @@ public class PeerTest extends TestWithNetworkConnections { assertNull(vtx[0]); } + @Test + public void disconnectOldVersions1() throws Exception { + expect(channel.close()).andReturn(null); + control.replay(); + // Set up the connection with an old version. + handler.connectRequested(ctx, new UpstreamChannelStateEvent(channel, ChannelState.CONNECTED, socketAddress)); + VersionMessage peerVersion = new VersionMessage(unitTestParams, OTHER_PEER_CHAIN_HEIGHT); + peerVersion.clientVersion = 500; + DownstreamMessageEvent versionEvent = + new DownstreamMessageEvent(channel, Channels.future(channel), peerVersion, null); + handler.messageReceived(ctx, versionEvent); + } + + @Test + public void disconnectOldVersions2() throws Exception { + expect(channel.close()).andReturn(null); + control.replay(); + // Set up the connection with an old version. + handler.connectRequested(ctx, new UpstreamChannelStateEvent(channel, ChannelState.CONNECTED, socketAddress)); + VersionMessage peerVersion = new VersionMessage(unitTestParams, OTHER_PEER_CHAIN_HEIGHT); + peerVersion.clientVersion = 70000; + DownstreamMessageEvent versionEvent = + new DownstreamMessageEvent(channel, Channels.future(channel), peerVersion, null); + handler.messageReceived(ctx, versionEvent); + peer.setMinProtocolVersion(500); + } + // TODO: Use generics here to avoid unnecessary casting. private Message outbound() { List messages = event.getValues();