Adds a services bitmask to PeerDiscovery.getPeers(), so we can query HTTP seeds for specific services.

As this is a breaking change to the API, it removes IrcDiscovery (there is no point in keeping it).
Also updates OkHttp to 2.4.0.
This commit is contained in:
Andreas Schildbach
2015-08-24 14:20:15 +02:00
parent d4c7ce5c77
commit 1be65483b4
12 changed files with 45 additions and 285 deletions

View File

@@ -214,8 +214,8 @@
<urn>org.hamcrest:hamcrest-core:1.3:jar:null:test:42a25dc3219429f0e5d060061f71acb49bf010a0</urn>
<urn>org.jacoco:jacoco-maven-plugin:0.7.5.201505241946:maven-plugin:null:runtime:0a5e4dbbcd9b00e5ee42d928e10ab84f6f0b0835</urn>
<urn>org.objenesis:objenesis:1.2:jar:null:test:bfcb0539a071a4c5a30690388903ac48c0667f2a</urn>
<urn>com.squareup.okhttp:okhttp:2.2.0:jar:null:runtime:959c454243581fdf730abfd4f4745441724bcf2c</urn>
<urn>com.squareup.okio:okio:1.2.0:jar:null:runtime:c0b52915a48fa91b1b94a28d4a2997bac5f524df</urn>
<urn>com.squareup.okhttp:okhttp:2.4.0:jar:null:runtime:40340c0748190fe897baf7bffbc1b282734294e5</urn>
<urn>com.squareup.okio:okio:1.4.0:jar:null:runtime:5b72bf48563ea8410e650de14aa33ff69a3e8c35</urn>
<urn>org.slf4j:slf4j-api:1.7.7:jar:null:compile:2b8019b6249bb05d81d3a3094e468753e2b21311</urn>
<urn>org.slf4j:slf4j-jdk14:1.7.7:jar:null:test:25d160723ea37a6cb84e87cd70773ff02997e857</urn>
<urn>org.sonatype.plugins:nexus-staging-maven-plugin:1.6.5:maven-plugin:null:runtime:455ca2aa8cd14a06608f1538bd6a1efd09561563</urn>
@@ -501,7 +501,7 @@
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.2.0</version>
<version>2.4.0</version>
</dependency>
</dependencies>

View File

@@ -77,6 +77,8 @@ public class PeerGroup implements TransactionBroadcaster {
// All members in this class should be marked with final, volatile, @GuardedBy or a mix as appropriate to define
// their thread safety semantics. Volatile requires a Hungarian-style v prefix.
// By default we don't require any services because any peer will do.
private long requiredServices = 0;
/**
* The default number of connections to the p2p network the library will try to build. This is set to 12 empirically.
* It used to be 4, but because we divide the connection pool in two for broadcasting transactions, that meant we
@@ -837,7 +839,7 @@ public class PeerGroup implements TransactionBroadcaster {
final List<PeerAddress> addressList = Lists.newLinkedList();
for (PeerDiscovery peerDiscovery : peerDiscoverers /* COW */) {
InetSocketAddress[] addresses;
addresses = peerDiscovery.getPeers(5, TimeUnit.SECONDS);
addresses = peerDiscovery.getPeers(requiredServices, 5, TimeUnit.SECONDS);
for (InetSocketAddress address : addresses) addressList.add(new PeerAddress(address));
if (addressList.size() >= maxPeersToDiscoverCount) break;
}

View File

@@ -1,4 +1,4 @@
/**
/*
* Copyright 2011 John Sample
* Copyright 2014 Andreas Schildbach
*
@@ -84,7 +84,9 @@ public class DnsDiscovery extends MultiplexingDiscovery {
}
@Override
public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
public InetSocketAddress[] getPeers(long services, long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
if (services != 0)
throw new PeerDiscoveryException("DNS seeds cannot filter by services: " + services);
try {
InetAddress[] response = InetAddress.getAllByName(hostname);
InetSocketAddress[] result = new InetSocketAddress[response.length];

View File

@@ -1,5 +1,6 @@
/**
/*
* Copyright 2014 Mike Hearn
* Copyright 2015 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bitcoinj.net.discovery;
import com.google.common.annotations.*;
@@ -78,10 +80,13 @@ public class HttpDiscovery implements PeerDiscovery {
}
@Override
public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
public InetSocketAddress[] getPeers(long services, long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
try {
log.info("Requesting seeds from {}", details.uri);
Response response = client.newCall(new Request.Builder().url(details.uri.toURL()).build()).execute();
HttpUrl.Builder url = HttpUrl.get(details.uri).newBuilder();
if (services != 0)
url.addQueryParameter("srvmask", Long.toString(services));
log.info("Requesting seeds from {}", url);
Response response = client.newCall(new Request.Builder().url(url.build()).build()).execute();
if (!response.isSuccessful())
throw new PeerDiscoveryException("HTTP request failed: " + response.code() + " " + response.message());
InputStream stream = response.body().byteStream();

View File

@@ -1,260 +0,0 @@
/**
* Copyright 2011 John Sample
*
* 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.net.discovery;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Base58;
import org.bitcoinj.core.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* IrcDiscovery provides a way to find network peers by joining a pre-agreed rendevouz point on the LFnet IRC network.
* <b>This class is deprecated because LFnet has ceased to operate and DNS seeds now exist for both main and test
* networks.</b> It may conceivably still be useful for running small ad-hoc networks by yourself.
*/
@Deprecated
public class IrcDiscovery implements PeerDiscovery {
private static final Logger log = LoggerFactory.getLogger(IrcDiscovery.class);
private String channel;
private int port = 6667;
private String server;
private BufferedWriter writer = null;
private Socket connection;
/**
* Finds a list of peers by connecting to an IRC network, joining a channel, decoding the nicks and then
* disconnecting.
*
* @param channel The IRC channel to join, either "#bitcoin" or "#bitcoinTEST3" for the main and test networks
* respectively.
*/
public IrcDiscovery(String channel) {
this(channel, "irc.lfnet.org", 6667);
}
/**
* Finds a list of peers by connecting to an IRC network, joining a channel, decoding the nicks and then
* disconnecting.
*
* @param server Name or textual IP address of the IRC server to join.
* @param channel The IRC channel to join, either "#bitcoin" or "#bitcoinTEST3" for the main and test networks
*/
public IrcDiscovery(String channel, String server, int port) {
this.channel = channel;
this.server = server;
this.port = port;
}
protected void onIRCSend(String message) {
}
protected void onIRCReceive(String message) {
}
@Override
public void shutdown() {
try {
if (connection != null) {
connection.close();
}
} catch (IOException ex) {
// ignore
}
}
/**
* Returns a list of peers that were found in the IRC channel. Note that just because a peer appears in the list
* does not mean it is accepting connections. The given time out value is applied for every IP returned by DNS
* for the given server, so a timeout value of 1 second may result in 5 seconds delay if 5 servers are advertised.
*/
@Override
public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
ArrayList<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>();
connection = null;
BufferedReader reader = null;
try {
InetAddress[] ips = InetAddress.getAllByName(server);
// Pick a random server for load balancing reasons. Try the rest if
int ipCursorStart = (int)(Math.random()*ips.length);
int ipCursor = ipCursorStart;
do {
connection = new Socket();
int timeoutMsec = (int) TimeUnit.MILLISECONDS.convert(timeoutValue, timeoutUnit);
connection.setSoTimeout(timeoutMsec);
try {
InetAddress ip = ips[ipCursor];
log.info("Connecting to IRC with " + ip);
connection.connect(new InetSocketAddress(ip, port), timeoutMsec);
} catch (SocketTimeoutException e) {
connection = null;
} catch (IOException e) {
connection = null;
}
ipCursor = (ipCursor + 1) % ips.length;
if (ipCursor == ipCursorStart) {
throw new PeerDiscoveryException("Could not connect to " + server);
}
} while (connection == null);
writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream(), "UTF-8"));
reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
// Generate a random nick for the connection. This is chosen to be clearly identifiable as coming from
// bitcoinj but not match the standard nick format, so full peers don't try and connect to us.
String nickRnd = String.format("bcj%d", new Random().nextInt(Integer.MAX_VALUE));
String command = "NICK " + nickRnd;
logAndSend(command);
// USER <user> <mode> <unused> <realname> (RFC 2812)
command = "USER " + nickRnd + " 8 *: " + nickRnd;
logAndSend(command);
writer.flush();
// Wait to be logged in. Worst case we end up blocked until the server PING/PONGs us out.
String currLine;
while ((currLine = reader.readLine()) != null) {
onIRCReceive(currLine);
// 004 tells us we are connected
// TODO: add common exception conditions (nick already in use, etc..)
// these aren't bullet proof checks but they should do for our purposes.
if (checkLineStatus("004", currLine)) {
break;
}
}
// Join the channel.
logAndSend("JOIN " + channel);
// List users in channel.
logAndSend("NAMES " + channel);
writer.flush();
// A list of the users should be returned. Look for code 353 and parse until code 366.
while ((currLine = reader.readLine()) != null) {
onIRCReceive(currLine);
if (checkLineStatus("353", currLine)) {
// Line contains users. List follows ":" (second ":" if line starts with ":")
int subIndex = 0;
if (currLine.startsWith(":")) {
subIndex = 1;
}
String spacedList = currLine.substring(currLine.indexOf(":", subIndex));
addresses.addAll(parseUserList(spacedList.substring(1).split(" ")));
} else if (checkLineStatus("366", currLine)) {
// End of user list.
break;
}
}
// Quit the server.
logAndSend("PART " + channel);
logAndSend("QUIT");
writer.flush();
} catch (Exception e) {
// Throw the original error wrapped in the discovery error.
throw new PeerDiscoveryException(e.getMessage(), e);
} finally {
try {
if (reader != null) reader.close();
if (writer != null) writer.close();
// No matter what try to close the connection.
if (connection != null) connection.close();
} catch (IOException e) {
log.warn("Exception whilst closing IRC discovery: " + e.toString());
}
}
return addresses.toArray(new InetSocketAddress[addresses.size()]);
}
private void logAndSend(String command) throws Exception {
onIRCSend(command);
writer.write(command + "\n");
}
// Visible for testing.
static ArrayList<InetSocketAddress> parseUserList(String[] userNames) throws UnknownHostException {
ArrayList<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>();
for (String user : userNames) {
// All peers start their nicknames with a 'u' character.
if (!user.startsWith("u")) {
continue;
}
// After "u" is stripped from the beginning array contains unsigned chars of:
// 4 byte ip address, 2 byte port, 4 byte hash check (ipv4)
byte[] addressBytes;
try {
// Strip off the "u" before decoding. Note that it's possible for anyone to join these IRC channels and
// so simply beginning with "u" does not imply this is a valid encoded address.
//
// decodeChecked removes the checksum from the returned bytes.
addressBytes = Base58.decodeChecked(user.substring(1));
} catch (AddressFormatException e) {
log.warn("IRC nick does not parse as base58: " + user);
continue;
}
// TODO: Handle IPv6 if one day the official client uses it. It may be that IRC discovery never does.
if (addressBytes.length != 6) {
continue;
}
byte[] ipBytes = {addressBytes[0], addressBytes[1], addressBytes[2], addressBytes[3]};
int port = Utils.readUint16BE(addressBytes, 4);
InetAddress ip;
try {
ip = InetAddress.getByAddress(ipBytes);
} catch (UnknownHostException e) {
// Bytes are not a valid IP address.
continue;
}
InetSocketAddress address = new InetSocketAddress(ip, port);
addresses.add(address);
}
return addresses;
}
private static boolean checkLineStatus(String statusCode, String response) {
// Lines can either start with the status code or an optional :<source>
//
// All the testing shows the servers for this purpose use :<source> but plan for either.
// TODO: Consider whether regex would be worth it here.
if (response.startsWith(":")) {
// Look for first space.
int startIndex = response.indexOf(" ") + 1;
// Next part should be status code.
return response.indexOf(statusCode + " ", startIndex) == startIndex;
} else {
if (response.startsWith(statusCode + " ")) {
return true;
}
}
return false;
}
}

View File

@@ -1,5 +1,6 @@
/**
/*
* Copyright 2014 Mike Hearn
* Copyright 2015 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,7 +53,7 @@ public class MultiplexingDiscovery implements PeerDiscovery {
}
@Override
public InetSocketAddress[] getPeers(final long timeoutValue, final TimeUnit timeoutUnit) throws PeerDiscoveryException {
public InetSocketAddress[] getPeers(final long services, final long timeoutValue, final TimeUnit timeoutUnit) throws PeerDiscoveryException {
vThreadPool = createExecutor();
try {
List<Callable<InetSocketAddress[]>> tasks = Lists.newArrayList();
@@ -60,7 +61,7 @@ public class MultiplexingDiscovery implements PeerDiscovery {
tasks.add(new Callable<InetSocketAddress[]>() {
@Override
public InetSocketAddress[] call() throws Exception {
return seed.getPeers(timeoutValue, timeoutUnit);
return seed.getPeers(services, timeoutValue, timeoutUnit);
}
});
}

View File

@@ -1,5 +1,6 @@
/**
/*
* Copyright 2011 John Sample.
* Copyright 2015 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,8 +27,11 @@ import java.util.concurrent.TimeUnit;
public interface PeerDiscovery {
// TODO: Flesh out this interface a lot more.
/** Returns an array of addresses. This method may block. */
InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException;
/**
* Returns an array of addresses. This method may block.
* @param services Required services as a bitmask, e.g. {@link VersionMessage#NODE_NETWORK}.
*/
InetSocketAddress[] getPeers(long services, long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException;
/** Stops any discovery in progress when we want to shut down quickly. */
void shutdown();

View File

@@ -83,7 +83,9 @@ public class SeedPeers implements PeerDiscovery {
* Returns an array containing all the Bitcoin nodes within the list.
*/
@Override
public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
public InetSocketAddress[] getPeers(long services, long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
if (services != 0)
throw new PeerDiscoveryException("Pre-determined peers cannot be filtered by services: " + services);
try {
return allPeers();
} catch (UnknownHostException e) {

View File

@@ -1,5 +1,6 @@
/**
/*
* Copyright 2014 Miron Cuperman
* Copyright 2015 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -96,9 +97,11 @@ public class TorDiscovery implements PeerDiscovery {
}
@Override
public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
public InetSocketAddress[] getPeers(long services, long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
if (hostNames == null)
throw new PeerDiscoveryException("Unable to find any peers via DNS");
if (services != 0)
throw new PeerDiscoveryException("DNS seeds cannot filter by services: " + services);
Set<Router> routers = Sets.newHashSet();
ArrayList<ExitTarget> dummyTargets = Lists.newArrayList();

View File

@@ -134,7 +134,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
final AtomicBoolean result = new AtomicBoolean();
peerGroup.addPeerDiscovery(new PeerDiscovery() {
@Override
public InetSocketAddress[] getPeers(long unused, TimeUnit unused2) throws PeerDiscoveryException {
public InetSocketAddress[] getPeers(long services, long unused, TimeUnit unused2) throws PeerDiscoveryException {
if (!result.getAndSet(true)) {
// Pretend we are not connected to the internet.
throw new PeerDiscoveryException("test failure");
@@ -164,7 +164,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
addresses[addressNr] = new InetSocketAddress("localhost", port + addressNr);
}
return new PeerDiscovery() {
public InetSocketAddress[] getPeers(long unused, TimeUnit unused2) throws PeerDiscoveryException {
public InetSocketAddress[] getPeers(long services, long unused, TimeUnit unused2) throws PeerDiscoveryException {
return addresses;
}
public void shutdown() {
@@ -534,7 +534,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
peerGroup.addDataEventListener(listener);
peerGroup.addPeerDiscovery(new PeerDiscovery() {
@Override
public InetSocketAddress[] getPeers(long unused, TimeUnit unused2) throws PeerDiscoveryException {
public InetSocketAddress[] getPeers(long services, long unused, TimeUnit unused2) throws PeerDiscoveryException {
return addresses.toArray(new InetSocketAddress[addresses.size()]);
}

View File

@@ -1,5 +1,6 @@
/**
/*
* Copyright 2011 Micheal Swiggs
* Copyright 2015 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,7 +45,7 @@ public class SeedPeersTest {
@Test
public void getPeers_length() throws Exception{
SeedPeers seedPeers = new SeedPeers(MainNetParams.get());
InetSocketAddress[] addresses = seedPeers.getPeers(0, TimeUnit.SECONDS);
InetSocketAddress[] addresses = seedPeers.getPeers(0, 0, TimeUnit.SECONDS);
assertThat(addresses.length, equalTo(MainNetParams.get().getAddrSeeds().length));
}
}

View File

@@ -58,7 +58,7 @@ public class PrintPeers {
private static void printDNS() throws PeerDiscoveryException {
long start = System.currentTimeMillis();
DnsDiscovery dns = new DnsDiscovery(MainNetParams.get());
dnsPeers = dns.getPeers(10, TimeUnit.SECONDS);
dnsPeers = dns.getPeers(0, 10, TimeUnit.SECONDS);
printPeers(dnsPeers);
printElapsed(start);
}