/**
* 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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Date;
/**
* A NetworkConnection handles talking to a remote BitCoin peer at a low level. It understands how to read and write
* messages off the network, but doesn't asynchronously communicate with the peer or handle the higher level details
* of the protocol. After constructing a NetworkConnection, use a {@link Peer} to hand off communication to a
* background thread.
*
* Construction is blocking whilst the protocol version is negotiated.
*/
public class NetworkConnection {
private static final Logger log = LoggerFactory.getLogger(NetworkConnection.class);
private final Socket socket;
private final OutputStream out;
private final InputStream in;
// The IP address to which we are connecting.
private final InetAddress remoteIp;
private final NetworkParameters params;
private final VersionMessage versionMessage;
private static final boolean PROTOCOL_LOG = false;
private BitcoinSerializer serializer = null;
/**
* Connect to the given IP address using the port specified as part of the network parameters. Once construction
* is complete a functioning network channel is set up and running.
*
* @param remoteIp IP address to connect to. IPv6 is not currently supported by BitCoin.
* @param params Defines which network to connect to and details of the protocol.
* @param bestHeight How many blocks are in our best chain
* @param connectTimeout Timeout in milliseconds when initially connecting to peer
* @throws IOException if there is a network related failure.
* @throws ProtocolException if the version negotiation failed.
*/
public NetworkConnection(InetAddress remoteIp, NetworkParameters params, int bestHeight, int connectTimeout)
throws IOException, ProtocolException {
this.params = params;
this.remoteIp = remoteIp;
InetSocketAddress address = new InetSocketAddress(remoteIp, params.port);
System.out.println("before connect");
socket = new Socket();
socket.connect(address, connectTimeout);
System.out.println("after connect");
out = socket.getOutputStream();
in = new BufferedInputStream(socket.getInputStream());
// the version message never uses checksumming. Update checkumming property after version is read.
this.serializer = new GraphBitcoinSerializer(params, true);
// Announce ourselves. This has to come first to connect to clients beyond v0.30.20.2 which wait to hear
// from us until they send their version message back.
writeMessage(new VersionMessage(params, bestHeight));
// When connecting, the remote peer sends us a version message with various bits of
// useful data in it. We need to know the peer protocol version before we can talk to it.
versionMessage = (VersionMessage) readMessage();
// Now it's our turn ...
// Send an ACK message stating we accept the peers protocol version.
writeMessage(new VersionAck());
// And get one back ...
readMessage();
// Switch to the new protocol version.
int peerVersion = versionMessage.clientVersion;
log.info("Connected to peer: version={}, subVer='{}', services=0x{}, time={}, blocks={}", new Object[] {
peerVersion,
versionMessage.subVer,
versionMessage.localServices,
new Date(versionMessage.time * 1000),
versionMessage.bestHeight
});
// BitCoinJ is a client mode implementation. That means there's not much point in us talking to other client
// mode nodes because we can't download the data from them we need to find/verify transactions.
if (!versionMessage.hasBlockChain())
throw new ProtocolException("Peer does not have a copy of the block chain.");
// newer clients use checksumming
serializer.useChecksumming(peerVersion >= 209);
// Handshake is done!
}
/**
* Sends a "ping" message to the remote node. The protocol doesn't presently use this feature much.
* @throws IOException
*/
public void ping() throws IOException {
writeMessage(new Ping());
}
/**
* Shuts down the network socket. Note that there's no way to wait for a socket to be fully flushed out to the
* wire, so if you call this immediately after sending a message it might not get sent.
*/
public void shutdown() throws IOException {
socket.shutdownOutput();
socket.shutdownInput();
socket.close();
}
@Override
public String toString() {
return "[" + remoteIp.getHostAddress() + "]:" + params.port + " (" + (socket.isConnected() ? "connected" :
"disconnected") + ")";
}
/**
* Reads a network message from the wire, blocking until the message is fully received.
*
* @return An instance of a Message subclass
* @throws ProtocolException if the message is badly formatted, failed checksum or there was a TCP failure.
*/
public Message readMessage() throws IOException, ProtocolException {
return serializer.deserialize(in);
}
/**
* Writes the given message out over the network using the protocol tag. For a Transaction
* this should be "tx" for example. It's safe to call this from multiple threads simultaneously,
* the actual writing will be serialized.
*
* @throws IOException
*/
public void writeMessage(Message message) throws IOException {
synchronized (out) {
serializer.serialize(message, out);
}
}
/** Returns the version message received from the other end of the connection during the handshake. */
public VersionMessage getVersionMessage() {
return versionMessage;
}
}