mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-14 11:15:51 +00:00
Implement support for IRC peer discovery. Patch contributed by John Sample.
This commit is contained in:
parent
37cb9cb6e5
commit
e43ad1f754
208
src/com/google/bitcoin/core/IrcDiscovery.java
Normal file
208
src/com/google/bitcoin/core/IrcDiscovery.java
Normal file
@ -0,0 +1,208 @@
|
||||
/**
|
||||
* 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 com.google.bitcoin.core;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* IrcDiscovery provides a way to find network peers by joining a pre-agreed rendevouz point on the LFnet IRC network.
|
||||
*/
|
||||
public class IrcDiscovery implements PeerDiscovery {
|
||||
private String channel;
|
||||
private int port = 6667;
|
||||
private String server;
|
||||
|
||||
private BufferedWriter writer = null;
|
||||
|
||||
/**
|
||||
* 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 "#bitcoinTEST" for the production 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 "#bitcoinTEST" for the production 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) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public InetSocketAddress[] getPeers() throws PeerDiscoveryException {
|
||||
ArrayList<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>();
|
||||
Socket connection = null;
|
||||
try {
|
||||
connection = new Socket(server, port);
|
||||
writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
|
||||
// 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 = null;
|
||||
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);
|
||||
writer.flush();
|
||||
|
||||
// A list of the users should be returned when we join. 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.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 {
|
||||
// No matter what try to close the connection.
|
||||
connection.close();
|
||||
} catch (Exception e2) {}
|
||||
}
|
||||
return addresses.toArray(new InetSocketAddress[]{});
|
||||
}
|
||||
|
||||
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 BitCoin 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 BitCoin encoded address.
|
||||
//
|
||||
// decodeChecked removes the checksum from the returned bytes.
|
||||
addressBytes = Base58.decodeChecked(user.substring(1));
|
||||
} catch (AddressFormatException e) {
|
||||
Utils.LOG("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 = new byte[]{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.
|
||||
if (response.indexOf(statusCode + " ", startIndex) == startIndex) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (response.startsWith(statusCode + " ")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
30
src/com/google/bitcoin/core/PeerDiscovery.java
Normal file
30
src/com/google/bitcoin/core/PeerDiscovery.java
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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 com.google.bitcoin.core;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* A PeerDiscovery object is responsible for finding addresses of other nodes in the BitCoin P2P network. Note that
|
||||
* the addresses returned may or may not be accepting connections.
|
||||
*/
|
||||
public interface PeerDiscovery {
|
||||
// TODO: Flesh out this interface a lot more.
|
||||
|
||||
/** Returns an array of addresses. This method may block. */
|
||||
InetSocketAddress[] getPeers() throws PeerDiscoveryException;
|
||||
}
|
41
src/com/google/bitcoin/core/PeerDiscoveryException.java
Normal file
41
src/com/google/bitcoin/core/PeerDiscoveryException.java
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright 2011 Google Inc.
|
||||
*
|
||||
* 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 com.google.bitcoin.core;
|
||||
|
||||
public class PeerDiscoveryException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -2863411151549391392L;
|
||||
|
||||
public PeerDiscoveryException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PeerDiscoveryException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PeerDiscoveryException(Throwable arg0)
|
||||
{
|
||||
super(arg0);
|
||||
}
|
||||
|
||||
public PeerDiscoveryException(String message, Throwable arg0) {
|
||||
super(message, arg0);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -190,6 +190,10 @@ public class Utils {
|
||||
((bytes[offset + 2] & 0xFFL) << 8) |
|
||||
((bytes[offset + 3] & 0xFFL) << 0);
|
||||
}
|
||||
|
||||
public static int readUint16BE(byte[] bytes, int offset) {
|
||||
return ((bytes[offset] & 0xff) << 8) | bytes[offset + 1] & 0xff;
|
||||
}
|
||||
|
||||
static void LOG(String msg) {
|
||||
// Set this to true to see debug prints from the library.
|
||||
|
46
src/com/google/bitcoin/examples/IRCPeers.java
Normal file
46
src/com/google/bitcoin/examples/IRCPeers.java
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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 com.google.bitcoin.examples;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import com.google.bitcoin.core.*;
|
||||
|
||||
/**
|
||||
* Prints a list of IP addresses connected to the rendezvous point on the LFnet IRC channel.
|
||||
*/
|
||||
public class IRCPeers {
|
||||
public static void main(String[] args) throws PeerDiscoveryException {
|
||||
IrcDiscovery d = new IrcDiscovery("#bitcoin") {
|
||||
@Override
|
||||
protected void onIRCReceive(String message) {
|
||||
System.out.println(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIRCSend(String message) {
|
||||
System.out.println(message);
|
||||
}
|
||||
};
|
||||
|
||||
InetSocketAddress[] addresses = d.getPeers();
|
||||
for (InetSocketAddress address : addresses) {
|
||||
String hostAddress = address.getAddress().getHostAddress();
|
||||
System.out.println(String.format("%s:%d", hostAddress.toString(), address.getPort()));
|
||||
}
|
||||
}
|
||||
}
|
49
tests/com/google/bitcoin/core/IrcDiscoveryTest.java
Normal file
49
tests/com/google/bitcoin/core/IrcDiscoveryTest.java
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright 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 com.google.bitcoin.core;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class IrcDiscoveryTest {
|
||||
// TODO: Inject a mock IRC server and more thoroughly exercise this class.
|
||||
|
||||
@Test
|
||||
public void testParseUserList() throws UnknownHostException {
|
||||
// Test some random addresses grabbed from the channel.
|
||||
String[] userList = new String[]{ "x201500200","u4stwEBjT6FYyVV", "u5BKEqDApa8SbA7"};
|
||||
|
||||
ArrayList<InetSocketAddress> addresses = IrcDiscovery.parseUserList(userList);
|
||||
|
||||
// Make sure the "x" address is excluded.
|
||||
assertEquals("Too many addresses.", 2, addresses.size());
|
||||
|
||||
String[] ips = new String[]{"69.4.98.82:8333","74.92.222.129:8333"};
|
||||
InetSocketAddress[] decoded = addresses.toArray(new InetSocketAddress[]{});
|
||||
|
||||
for (int i = 0; i < decoded.length; i++) {
|
||||
String formattedIP = decoded[0].getAddress().getHostAddress() + ":" + ((Integer)decoded[i].getPort()).toString();
|
||||
assertEquals("IPs decoded improperly", ips[0], formattedIP);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user