package org.ripple.power.txns.btc; import java.io.IOException; import java.io.InputStream; import java.net.BindException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketException; import java.net.StandardSocketOptions; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.ClosedSelectorException; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import org.ripple.power.Helper; import org.ripple.power.utils.HttpRequest; import org.ripple.power.utils.IP46Utils; public class NetworkHandler implements Runnable { static class PeerAddressComparator implements Comparator<PeerAddress> { @Override public int compare(PeerAddress addr1, PeerAddress addr2) { long t1 = addr1.getTimeStamp(); long t2 = addr2.getTimeStamp(); return (t1 > t2 ? -1 : (t1 < t2 ? 1 : 0)); } } /** Maximum number of pending input messages for a single peer */ private static final int MAX_INPUT_MESSAGES = 10; /** Maximum number of pending output messages for a single peer */ private static final int MAX_OUTPUT_MESSAGES = 500; // Bitcoin Network seed nodes (like ripple UNL) private static final String[] dnsSeeds = new String[] { "seed.bitcoin.sipa.be", // Pieter Wuille "dnsseed.bluematt.me", // Matt Corallo "dnsseed.bitcoin.dashjr.org", // Luke Dashjr "seed.bitcoinstats.com", // Chris Decker "seed.bitnodes.io", // Addy Yeow }; // Test NetWork private static final String[] dnsTestSeeds = new String[] { "testnet-seed.alexykot.me", "testnet-seed.bitcoin.petertodd.org", "testnet-seed.bluematt.me" }; /** Connection listeners */ private final List<ConnectionListener> connectionListeners = new ArrayList<>(); /** Network listener thread */ private Thread listenerThread; /** Network timer */ private Timer timer; /** Maximum number of connections */ private final int maxConnections; /** Maximum number of outbound connections */ private int maxOutbound; /** Current number of outbound connections */ private int outboundCount; /** Host name */ private final String hostName; /** Listen channel */ private ServerSocketChannel listenChannel; /** Listen selection key */ private SelectionKey listenKey; /** Network selector */ private final Selector networkSelector; /** Connections list */ private final List<Peer> connections = new ArrayList<>(128); /** Connection map */ private final Map<InetAddress, Peer> connectionMap = new HashMap<>(); /** Peer blacklist */ private final List<BlacklistEntry> peerBlacklist = new ArrayList<>(); /** Time of Last peer database update */ private long lastPeerUpdateTime; /** Time of last outbound connection attempt */ private long lastOutboundConnectTime; /** Last statistics output time */ private long lastStatsTime; /** Last connection check time */ private long lastConnectionCheckTime; /** Network shutdown */ private boolean networkShutdown = false; /** Static connections */ private boolean staticConnections = false; /** 'getblocks' message sent */ private boolean getblocksSent = false; /** * Creates the network listener * * @param maxConnections * The maximum number of connections * @param maxOutbound * The maximum number of outbound connections * @param hostName * The host name for this port or null * @param listenPort * The port to listen on * @param staticAddresses * Static peer address * @param blacklist * Peer blacklist * @throws IOException * I/O error */ public NetworkHandler(int maxConnections, int maxOutbound, String hostName, int listenPort, PeerAddress[] staticAddresses, List<BlacklistEntry> blacklist) throws IOException { this.maxConnections = maxConnections; this.maxOutbound = maxOutbound; this.hostName = hostName; BTCLoader.listenPort = listenPort; peerBlacklist.addAll(blacklist); // // Create the selector for listening for network events // networkSelector = Selector.open(); // // Build the static peer address list // if (staticAddresses != null) { staticConnections = true; this.maxOutbound = Math.min(this.maxOutbound, staticAddresses.length); for (PeerAddress address : staticAddresses) { address.setStatic(true); BTCLoader.peerAddresses.add(0, address); BTCLoader.peerMap.put(address, address); } } } /** * Processes network events */ @Override public void run() { BTCLoader .info(String .format("Network listener started: Port %d, Max connections %d, Max outbound %d", BTCLoader.listenPort, maxConnections, maxOutbound)); lastPeerUpdateTime = System.currentTimeMillis() / 1000; lastOutboundConnectTime = lastPeerUpdateTime; lastStatsTime = lastPeerUpdateTime; lastConnectionCheckTime = lastPeerUpdateTime; listenerThread = Thread.currentThread(); BTCLoader.networkChainHeight = BTCLoader.blockStore.getChainHeight(); try { // // Get our external IP address from checkip.dyndns.org // // The returned string is '<html><body>Current IP Address: // n.n.n.n</body></html>' // getExternalIP(); // // Get the peer nodes DNS discovery if we are not using static // connections. // The address list will be sorted in descending timestamp order so // that the // most recent peers appear first in the list. // if (!staticConnections) { dnsDiscovery(); Collections.sort(BTCLoader.peerAddresses, new PeerAddressComparator()); } // // Get the current alerts // BTCLoader.alerts.addAll(BTCLoader.blockStore.getAlerts()); // // Create the listen channel // listenChannel = ServerSocketChannel.open(); listenChannel.configureBlocking(false); listenChannel.bind(new InetSocketAddress(BTCLoader.listenPort), 10); listenKey = listenChannel.register(networkSelector, SelectionKey.OP_ACCEPT); // // Create the initial outbound connections to get us started // while (!networkShutdown && outboundCount < Math.min(maxOutbound, 4) && connections.size() < maxConnections && connections.size() < BTCLoader.peerAddresses.size()) if (!connectOutbound()) break; } catch (BlockStoreException | IOException exc) { BTCLoader.error("Unable to initialize network listener", exc); networkShutdown = true; } // // Create a timer to wake us up every 2 minutes // timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { wakeup(); } }, 2 * 60 * 1000, 2 * 60 * 1000); // // Process network events until shutdown() is called // try { while (!networkShutdown) { processEvents(); } } catch (Throwable exc) { BTCLoader.error( "Runtime exception while processing network events", exc); } // // Stopping // timer.cancel(); BTCLoader.info("Network listener stopped"); } /** * Process network events */ private void processEvents() { int count; try { // // Process selectable events // // Note that you need to remove the key from the selected key // set. Otherwise, the selector will return immediately since // it thinks there are still unprocessed events. Also, accessing // a key after the channel is closed will cause an exception to be // thrown, so it is best to test for just one event at a time. // count = networkSelector.select(); if (count > 0 && !networkShutdown) { Set<SelectionKey> selectedKeys = networkSelector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext() && !networkShutdown) { SelectionKey key = keyIterator.next(); SelectableChannel channel = key.channel(); if (channel.isOpen()) { if (key.isAcceptable()) { processAccept(key); } else if (key.isConnectable()) { processConnect(key); } else if (key.isReadable()) { processRead(key); } else if (key.isWritable()) { processWrite(key); } } keyIterator.remove(); } } if (!networkShutdown) { // // Process completed messages // if (!BTCLoader.completedMessages.isEmpty()) { processCompletedMessages(); } // // Process peer requests // if (!BTCLoader.pendingRequests.isEmpty() || !BTCLoader.processedRequests.isEmpty()) processRequests(); // // Remove peer addresses that are too old and broadcast any new // addresses. // A maximum of 999 addresses will be broadcast in a single // 'addr' message // since we always broadcast our own address. // long currentTime = System.currentTimeMillis() / 1000; if (currentTime > lastPeerUpdateTime + BTCLoader.MAX_PEER_ADDRESS_AGE) { List<PeerAddress> newAddresses = new ArrayList<>( BTCLoader.peerAddresses.size()); synchronized (BTCLoader.peerAddresses) { Iterator<PeerAddress> iterator = BTCLoader.peerAddresses .iterator(); while (iterator.hasNext()) { PeerAddress address = iterator.next(); if (address.isStatic()) continue; if (address.getTimeStamp() < lastPeerUpdateTime) { BTCLoader.peerMap.remove(address); iterator.remove(); } else if (!address.wasBroadcast() && newAddresses.size() < 999) { address.setBroadcast(true); newAddresses.add(address); } } } if (!newAddresses.isEmpty()) { Message addrMsg = AddressMessage.buildAddressMessage( null, newAddresses, BTCLoader.listenAddress); broadcastMessage(addrMsg); BTCLoader.info(String.format( "%d addresses broadcast to peers", newAddresses.size())); } lastPeerUpdateTime = currentTime; } // // Check for inactive peer connections every 2 minutes // // Close the connection if the peer hasn't completed the version // handshake within 2 minutes. // Otherwise, send a 'ping' message. Close the connection if the // peer is still inactive // after 4 minutes. // if (currentTime > lastConnectionCheckTime + 2 * 60) { lastConnectionCheckTime = currentTime; List<Peer> inactiveList = new ArrayList<Peer>(); for (Peer chkPeer : connections) { PeerAddress chkAddress = chkPeer.getAddress(); if (chkAddress.getTimeStamp() < currentTime - 5 * 60) { inactiveList.add(chkPeer); } else if (chkAddress.getTimeStamp() < currentTime - 2 * 60) { if (chkPeer.getVersionCount() < 2) { inactiveList.add(chkPeer); } else if (!chkPeer.wasPingSent()) { chkPeer.setPing(true); Message chkMsg = PingMessage .buildPingMessage(chkPeer); synchronized (chkPeer) { chkPeer.getOutputList().add(chkMsg); SelectionKey chkKey = chkPeer.getKey(); chkKey.interestOps(chkKey.interestOps() | SelectionKey.OP_WRITE); } BTCLoader.info(String .format("'ping' message sent to %s", chkAddress)); } } } for (Peer chkPeer : inactiveList) { BTCLoader.info(String.format( "Closing connection due to inactivity: %s", chkPeer.getAddress())); closeConnection(chkPeer); } } // // Create a new outbound connection if we have less than the // maximum number and we haven't tried for 30 seconds // if (currentTime > lastOutboundConnectTime + 30) { lastOutboundConnectTime = currentTime; while (outboundCount < maxOutbound && connections.size() < maxConnections && connections.size() < BTCLoader.peerAddresses .size()) { if (!connectOutbound() || outboundCount >= Math.min(maxOutbound, 4)) break; } } // // Print statistics every 5 minutes // if (currentTime > lastStatsTime + (5 * 60)) { lastStatsTime = currentTime; BTCLoader .info(String .format("\n" + "=======================================================\n" + "** Chain height: Network %,d, Local %,d\n" + "** Connections: %,d outbound, %,d inbound\n" + "** Addresses: %,d peers, %,d banned\n" + "** Blocks: %,d received, %,d sent, %,d filtered sent\n" + "** Transactions: %,d received, %,d sent, %,d pool, %,d rejected, %,d orphaned\n" + "=======================================================", BTCLoader.networkChainHeight, BTCLoader.blockStore .getChainHeight(), outboundCount, connections.size() - outboundCount, BTCLoader.peerAddresses.size(), peerBlacklist.size(), BTCLoader.blocksReceived.get(), BTCLoader.blocksSent.get(), BTCLoader.filteredBlocksSent.get(), BTCLoader.txReceived.get(), BTCLoader.txSent.get(), BTCLoader.txMap.size(), BTCLoader.txRejected.get(), BTCLoader.orphanTxMap.size())); System.gc(); } } } catch (ClosedChannelException exc) { BTCLoader.error("Network channel closed unexpectedly", exc); } catch (ClosedSelectorException exc) { BTCLoader.error("Network selector closed unexpectedly", exc); networkShutdown = true; } catch (IOException exc) { BTCLoader.error("I/O error while processing selection event", exc); } } /** * Register a connection listener * * @param listener * Connection listener */ public void addListener(ConnectionListener listener) { connectionListeners.add(listener); } /** * Returns the current connections * * @return Peer connections */ public List<Peer> getConnections() { // // Get the current connection list // List<Peer> connectionList; synchronized (connections) { connectionList = new ArrayList<>(connections); } // // Remove pending connections from the list // Iterator<Peer> it = connectionList.iterator(); while (it.hasNext()) { Peer peer = it.next(); if (peer.getVersionCount() < 3) it.remove(); } return connectionList; } /** * Wakes up the network listener */ public void wakeup() { if (Thread.currentThread() != listenerThread) networkSelector.wakeup(); } /** * Shutdowns the network listener */ public void shutdown() { networkShutdown = true; wakeup(); } /** * Sends a message to a connected peer * * @param msg * The message to be sent */ public void sendMessage(Message msg) { Peer peer = msg.getPeer(); if (peer != null) { SelectionKey key = peer.getKey(); PeerAddress address = peer.getAddress(); synchronized (peer) { if (address.isConnected()) { peer.getOutputList().add(msg); key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); } } wakeup(); } } /** * Broadcasts a message to all connected peers * * Block notifications will be sent to peers that are providing network * services. Transaction notifications will be sent to peers that have * requested transaction relays. All other notifications will be sent to all * connected peers. * * @param msg * Message to broadcast */ public void broadcastMessage(Message msg) { // // Send the message to each connected peer // synchronized (connections) { for (Peer relayPeer : connections) { if (relayPeer.getVersionCount() > 2) { boolean sendMsg = true; MessageHeader.MessageCommand cmd = msg.getCommand(); if (cmd == MessageHeader.MessageCommand.INV) { if (msg.getInventoryType() == InventoryItem.INV_BLOCK) sendMsg = relayPeer.shouldRelayBlocks(); else if (msg.getInventoryType() == InventoryItem.INV_TX) sendMsg = relayPeer.shouldRelayTx(); } if (sendMsg) { synchronized (relayPeer) { relayPeer.getOutputList().add(msg.clone(relayPeer)); SelectionKey relayKey = relayPeer.getKey(); relayKey.interestOps(relayKey.interestOps() | SelectionKey.OP_WRITE); } } } } } // // Wakeup the network listener to send the broadcast messages // wakeup(); } /** * Processes an OP_ACCEPT selection event * * We will accept the connection if we haven't reached the maximum number of * connections. The new socket channel will be placed in non-blocking mode * and the selection key enabled for read events. We will not add the peer * address to the peer address list since we only want nodes that have * advertised their availability on the list. */ private void processAccept(SelectionKey acceptKey) { try { SocketChannel channel = listenChannel.accept(); if (channel != null) { InetSocketAddress remoteAddress = (InetSocketAddress) channel .getRemoteAddress(); PeerAddress address = new PeerAddress(remoteAddress); if (connections.size() >= maxConnections) { channel.close(); BTCLoader .info(String .format("Max connections reached: Connection rejected from %s", address)); } else if (isBlacklisted(address.getAddress())) { channel.close(); BTCLoader.info(String.format( "Connection rejected from banned address %s", address)); } else if (connectionMap.get(address.getAddress()) != null) { channel.close(); BTCLoader.info(String.format( "Duplicate connection rejected from %s", address)); } else { address.setTimeConnected(System.currentTimeMillis() / 1000); channel.configureBlocking(false); channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true); SelectionKey key = channel.register(networkSelector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); Peer peer = new Peer(address, channel, key); key.attach(peer); peer.setConnected(true); address.setConnected(true); BTCLoader.info(String.format("Connection accepted from %s", address)); Message msg = VersionMessage.buildVersionMessage(peer, BTCLoader.listenAddress, BTCLoader.blockStore.getChainHeight()); synchronized (connections) { connections.add(peer); connectionMap.put(address.getAddress(), peer); peer.getOutputList().add(msg); } BTCLoader.info(String.format( "Sent 'version' message to %s", address)); } } } catch (IOException exc) { BTCLoader.error("Unable to accept connection", exc); networkShutdown = true; } } /** * Creates a new outbound connection * * This routine selects the most recent peer from the peer address list. The * channel is placed in non-blocking mode and the connection is initiated. * An OP_CONNECT selection event will be generated when the connection has * been established or has failed. * * @return TRUE if a connection was established */ private boolean connectOutbound() { // // Get the most recent peer that does not have a connection // PeerAddress address = null; synchronized (BTCLoader.peerAddresses) { for (PeerAddress chkAddress : BTCLoader.peerAddresses) { if (!chkAddress.isConnected() && connectionMap.get(chkAddress.getAddress()) == null && !isBlacklisted(chkAddress.getAddress()) && (!staticConnections || chkAddress.isStatic())) { address = chkAddress; break; } } } if (address == null) return false; // // Create a socket channel for the connection and open the connection // Peer peer = null; try { SocketChannel channel = SocketChannel.open(); channel.configureBlocking(false); channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true); channel.bind(null); SelectionKey key = channel.register(networkSelector, SelectionKey.OP_CONNECT); peer = new Peer(address, channel, key); key.attach(peer); peer.setConnected(true); address.setConnected(true); address.setOutbound(true); channel.connect(address.toSocketAddress()); outboundCount++; synchronized (connections) { connections.add(peer); connectionMap.put(address.getAddress(), peer); } } catch (BindException exc) { BTCLoader.error( String.format("Unable to open connection to %s", address), exc); if (peer != null) closeConnection(peer); } catch (IOException exc) { BTCLoader.error( String.format("Unable to open connection to %s", address), exc); networkShutdown = true; } return true; } /** * Processes an OP_CONNECT selection event * * We will finish the connection and send a Version message to the remote * peer * * @param key * The channel selection key */ private void processConnect(SelectionKey key) { Peer peer = (Peer) key.attachment(); PeerAddress address = peer.getAddress(); SocketChannel channel = peer.getChannel(); try { channel.finishConnect(); BTCLoader.info(String.format("Connection established to %s", address)); address.setTimeConnected(System.currentTimeMillis() / 1000); Message msg = VersionMessage.buildVersionMessage(peer, BTCLoader.listenAddress, BTCLoader.blockStore.getChainHeight()); synchronized (peer) { peer.getOutputList().add(msg); key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); } BTCLoader.info(String.format("Sent 'version' message to %s", address)); } catch (SocketException exc) { BTCLoader.info(String.format("%s: Peer %s", exc.getLocalizedMessage(), address)); closeConnection(peer); if (!address.isStatic()) { synchronized (BTCLoader.peerAddresses) { if (BTCLoader.peerMap.get(address) != null) { BTCLoader.peerAddresses.remove(address); BTCLoader.peerMap.remove(address); } } } } catch (IOException exc) { BTCLoader.error(String.format("Connection failed to %s", address), exc); closeConnection(peer); } } /** * Processes an OP_READ selection event * * @param key * The channel selection key */ private void processRead(SelectionKey key) { Peer peer = (Peer) key.attachment(); PeerAddress address = peer.getAddress(); SocketChannel channel = peer.getChannel(); ByteBuffer buffer = peer.getInputBuffer(); address.setTimeStamp(System.currentTimeMillis() / 1000); try { int count; // // Read data until we have a complete message or no more data is // available // while (true) { // // Allocate a header buffer if no read is in progress // if (buffer == null) { buffer = ByteBuffer .wrap(new byte[MessageHeader.HEADER_LENGTH]); peer.setInputBuffer(buffer); } // // Fill the input buffer // if (buffer.position() < buffer.limit()) { count = channel.read(buffer); if (count <= 0) { if (count < 0) closeConnection(peer); break; } } // // Process the message header // if (buffer.position() == buffer.limit() && buffer.limit() == MessageHeader.HEADER_LENGTH) { byte[] hdrBytes = buffer.array(); long magic = Helper.readUint32LE(hdrBytes, 0); long length = Helper.readUint32LE(hdrBytes, 16); if (magic != NetParams.MAGIC_NUMBER) { BTCLoader.error(String.format( "Message magic number %X is incorrect", magic)); BTCLoader.dumpData("Failing Message Header", hdrBytes); closeConnection(peer); break; } if (length > NetParams.MAX_MESSAGE_SIZE) { BTCLoader.error(String.format( "Message length %,d is too large", length)); closeConnection(peer); break; } if (length > 0) { byte[] msgBytes = new byte[MessageHeader.HEADER_LENGTH + (int) length]; System.arraycopy(hdrBytes, 0, msgBytes, 0, MessageHeader.HEADER_LENGTH); buffer = ByteBuffer.wrap(msgBytes); buffer.position(MessageHeader.HEADER_LENGTH); peer.setInputBuffer(buffer); } } // // Queue the message for a message handler // // We will disable read operations for this peer if it has too // many // pending messages. Read operations will be re-enabled once // all of the messages have been processed. We do this to keep // one node from flooding us with requests. // if (buffer.position() == buffer.limit()) { peer.setInputBuffer(null); buffer.position(0); Message msg = new Message(buffer, peer, null); BTCLoader.messageQueue.put(msg); synchronized (peer) { count = peer.getInputCount() + 1; peer.setInputCount(count); if (count >= MAX_INPUT_MESSAGES || peer.getOutputList().size() >= MAX_OUTPUT_MESSAGES) key.interestOps(key.interestOps() & (~SelectionKey.OP_READ)); } break; } } } catch (IOException exc) { closeConnection(peer); } catch (InterruptedException exc) { BTCLoader.warn("Interrupted while processing read request"); networkShutdown = true; } } /** * Processes an OP_WRITE selection event * * @param key * The channel selection key */ private void processWrite(SelectionKey key) { Peer peer = (Peer) key.attachment(); SocketChannel channel = peer.getChannel(); ByteBuffer buffer = peer.getOutputBuffer(); try { // // Write data until all pending messages have been sent or the // socket buffer is full // while (true) { // // Get the next message if no write is in progress. Disable // write events // if there are no more messages to write. // if (buffer == null) { synchronized (peer) { List<Message> outputList = peer.getOutputList(); if (outputList.isEmpty()) { key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE)); } else { Message msg = outputList.remove(0); buffer = msg.getBuffer(); peer.setOutputBuffer(buffer); } } } // // Stop if all messages have been sent // if (buffer == null) break; // // Write the current buffer to the channel // channel.write(buffer); if (buffer.position() < buffer.limit()) break; buffer = null; peer.setOutputBuffer(null); } // // Restart a deferred request if we have sent all of the pending // data // if (peer.getOutputBuffer() == null) { synchronized (peer) { if (peer.getInputCount() == 0) key.interestOps(key.interestOps() | SelectionKey.OP_READ); Message deferredMsg = peer.getDeferredMessage(); if (deferredMsg != null) { peer.setDeferredMessage(null); deferredMsg.setPeer(peer); deferredMsg.setBuffer(deferredMsg.getRestartBuffer()); deferredMsg.setRestartBuffer(null); BTCLoader.messageQueue.put(deferredMsg); int count = peer.getInputCount() + 1; peer.setInputCount(count); if (count >= MAX_INPUT_MESSAGES) key.interestOps(key.interestOps() & (~SelectionKey.OP_READ)); } } } } catch (IOException exc) { closeConnection(peer); } catch (InterruptedException msg) { BTCLoader.warn("Interrupted while queueing deferred request"); networkShutdown = true; } } /** * Closes a peer connection and discards any pending messages * * @param peer * The peer being closed */ private void closeConnection(Peer peer) { PeerAddress address = peer.getAddress(); SocketChannel channel = peer.getChannel(); try { // // Disconnect the peer // peer.setInputBuffer(null); peer.setOutputBuffer(null); peer.setDeferredMessage(null); peer.getOutputList().clear(); if (address.isOutbound()) outboundCount--; address.setConnected(false); address.setOutbound(false); peer.setConnected(false); synchronized (connections) { connections.remove(peer); connectionMap.remove(address.getAddress()); } if (!address.isStatic()) { synchronized (BTCLoader.peerAddresses) { BTCLoader.peerAddresses.remove(address); BTCLoader.peerMap.remove(address); } } // // Ban the peer if necessary // synchronized (peer) { if (peer.getBanScore() >= BTCLoader.MAX_BAN_SCORE && !isBlacklisted(address.getAddress())) { peerBlacklist.add(new BlacklistEntry(address.getAddress(), -1)); BTCLoader.info(String.format("Peer address %s banned", address.getAddress().getHostAddress())); } } // // Notify listeners that a connection has ended // if (peer.getVersionCount() > 2) { for (ConnectionListener listener : connectionListeners) { listener.connectionEnded(peer, connections.size()); } } // // Close the channel // if (channel.isOpen()) channel.close(); BTCLoader.info(String.format("Connection closed with peer %s", address)); } catch (IOException exc) { BTCLoader .error(String.format( "Error while closing socket channel with %s", address), exc); } } /** * Processes completed messages */ private void processCompletedMessages() { Message msg; while ((msg = BTCLoader.completedMessages.poll()) != null) { Peer peer = msg.getPeer(); if (peer == null) continue; PeerAddress address = peer.getAddress(); SelectionKey key = peer.getKey(); // // Nothing to do if the connection has been closed // if (!address.isConnected()) continue; // // Close the connection if requested // if (peer.shouldDisconnect()) { closeConnection(peer); continue; } // // Send the response (if any) // if (msg.getBuffer() != null) { synchronized (peer) { peer.getOutputList().add(msg); key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); } } // // Decrement the pending input count for the peer and re-enable read // when the count reaches zero. Read is disabled when the peer has // sent too many requests at one time. // synchronized (peer) { int count = peer.getInputCount() - 1; peer.setInputCount(count); if (count == 0 && peer.getOutputList().isEmpty()) key.interestOps(key.interestOps() | SelectionKey.OP_READ); } // // Sent initial setup messages if we have successfully exchanged // 'version' messages // if (peer.getVersionCount() == 2) { peer.incVersionCount(); BTCLoader.info(String.format( "Connection handshake completed with %s", address)); // // Disconnect if this is an outbound connection and the peer // doesn't provide network services // if ((peer.getServices() & NetParams.NODE_NETWORK) == 0 && address.isOutbound()) { BTCLoader.info(String.format( "Network services not provided by %s", address)); closeConnection(peer); continue; } // // Send a 'getaddr' message to exchange peer address lists. // Do not do this if we are using static connections since we // don't need // to know peer addresses. // if (!staticConnections) { if ((peer.getServices() & NetParams.NODE_NETWORK) != 0) { Message addrMsg = GetAddressMessage .buildGetAddressMessage(peer); synchronized (peer) { peer.getOutputList().add(addrMsg); key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); } } } // // Send current alert messages // long currentTime = System.currentTimeMillis() / 1000; synchronized (BTCLoader.alerts) { for (Alert alert : BTCLoader.alerts) { if (!alert.isCanceled() && alert.getExpireTime() > currentTime) { Message alertMsg = AlertMessage.buildAlertMessage( peer, alert); synchronized (peer) { peer.getOutputList().add(alertMsg); key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); } BTCLoader.info(String.format("Sent alert %d to %s", alert.getID(), address)); } } } // // Send a 'getblocks' message if we are down-level and we // haven't sent // one yet // if (!getblocksSent && (peer.getServices() & NetParams.NODE_NETWORK) != 0 && peer.getHeight() > BTCLoader.blockStore .getChainHeight()) { BTCLoader.networkChainHeight = Math.max( BTCLoader.networkChainHeight, peer.getHeight()); List<Sha256Hash> blockList = getBlockList(); Message getMsg = GetBlocksMessage.buildGetBlocksMessage( peer, blockList, Sha256Hash.ZERO_HASH); synchronized (peer) { peer.getOutputList().add(getMsg); key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); } getblocksSent = true; BTCLoader.info(String.format( "Sent 'getblocks' message to %s", address)); } // // Notify listeners of the new connection // for (ConnectionListener listener : connectionListeners) { listener.connectionStarted(peer, connections.size()); } } } } /** * Process peer requests */ private void processRequests() { long currentTime = System.currentTimeMillis() / 1000; PeerRequest request; Peer peer; // // Check for request timeouts (we will wait 10 seconds for a response) // synchronized (BTCLoader.pendingRequests) { while (!BTCLoader.processedRequests.isEmpty()) { request = BTCLoader.processedRequests.get(0); if (request.getTimeStamp() >= currentTime - 10 || request.isProcessing()) break; // // Move the request back to the pending queue // BTCLoader.processedRequests.remove(0); if (request.getType() == InventoryItem.INV_BLOCK) BTCLoader.pendingRequests.add(request); else BTCLoader.pendingRequests.add(0, request); } } // // Send pending requests. We will suspend request processing if we come // to // a block request and the database handler has 10 blocks waiting for // processing. // All pending transaction requests will have been processed at this // point since // transaction requests are placed at the front of the queue while block // requests // are placed at the end of the queue. // while (!BTCLoader.pendingRequests.isEmpty()) { synchronized (BTCLoader.pendingRequests) { request = BTCLoader.pendingRequests.get(0); if (request.getType() == InventoryItem.INV_BLOCK && (BTCLoader.databaseQueue.size() >= 10 || BTCLoader.processedRequests .size() > 50)) { request = null; } else { BTCLoader.pendingRequests.remove(0); BTCLoader.processedRequests.add(request); } } if (request == null) break; // // Send the request to the origin peer unless we already tried or // the peer is // no longer connected // peer = request.getOrigin(); if (peer != null && (request.wasContacted(peer) || !peer.isConnected())) peer = null; // // Select a peer to process the request. The peer must provide // network // services and must not have been contacted for this request. // if (peer == null) { int index = (int) (((double) connections.size()) * Math .random()); for (int i = index; i < connections.size(); i++) { Peer chkPeer = connections.get(i); if ((chkPeer.getServices() & NetParams.NODE_NETWORK) != 0 && !request.wasContacted(chkPeer) && chkPeer.isConnected()) { peer = chkPeer; break; } } if (peer == null) { for (int i = 0; i < index; i++) { Peer chkPeer = connections.get(i); if ((chkPeer.getServices() & NetParams.NODE_NETWORK) != 0 && !request.wasContacted(chkPeer) && chkPeer.isConnected()) { peer = chkPeer; break; } } } } // // Discard the request if all of the available peers have been // contacted. We will // increment the banscore for the origin peer since he is // broadcasting inventory // that he doesn't have (except transactions which are removed from // the memory pool // when they are included in a block) // if (peer == null) { Peer originPeer = request.getOrigin(); synchronized (BTCLoader.pendingRequests) { BTCLoader.processedRequests.remove(request); } if (originPeer != null && request.getType() != InventoryItem.INV_TX) { synchronized (originPeer) { int banScore = originPeer.getBanScore() + 5; originPeer.setBanScore(banScore); if (banScore >= BTCLoader.MAX_BAN_SCORE) originPeer.setDisconnect(true); } } String originAddress = (originPeer != null ? originPeer .getAddress().toString() : "local"); BTCLoader .warn(String .format("Purging unavailable %s request initiated by %s\n %s", (request.getType() == InventoryItem.INV_TX ? "transaction" : "block"), originAddress, request.getHash())); continue; } // // Send the request to the peer // request.addPeer(peer); request.setTimeStamp(currentTime); List<InventoryItem> invList = new ArrayList<>(1); invList.add(new InventoryItem(request.getType(), request.getHash())); Message msg = GetDataMessage.buildGetDataMessage(peer, invList); synchronized (peer) { peer.getOutputList().add(msg); SelectionKey key = peer.getKey(); key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); } } } /** * Get more blocks * * This method is called by the database handler when it is loading the * initial block chain and it needs more blocks. We rely on inventory * messages to obtain new blocks once the block chain has been loaded. */ public void getBlocks() { // // Select a random peer // Peer peer = null; int chainHeight = BTCLoader.blockStore.getChainHeight(); synchronized (connections) { int index = (int) (((double) connections.size()) * Math.random()); for (int i = index; i < connections.size(); i++) { Peer chkPeer = connections.get(i); if (chkPeer.isConnected() && chkPeer.getVersionCount() > 2 && chkPeer.getHeight() > chainHeight && (chkPeer.getServices() & NetParams.NODE_NETWORK) != 0) { peer = chkPeer; break; } } if (peer == null) { for (int i = 0; i < index; i++) { Peer chkPeer = connections.get(i); if (chkPeer.isConnected() && chkPeer.getVersionCount() > 2 && chkPeer.getHeight() > chainHeight && (chkPeer.getServices() & NetParams.NODE_NETWORK) != 0) { peer = chkPeer; break; } } } } if (peer == null) return; // // Send the 'getblocks' message // List<Sha256Hash> blockList = getBlockList(); Message msg = GetBlocksMessage.buildGetBlocksMessage(peer, blockList, Sha256Hash.ZERO_HASH); sendMessage(msg); BTCLoader.info(String.format("Sent 'getblocks' message to %s", peer.getAddress())); } /** * Get block list for 'getblocks' message */ private List<Sha256Hash> getBlockList() { List<Sha256Hash> invList = new ArrayList<>(500); try { // // Get the chain list // int chainHeight = BTCLoader.blockStore.getChainHeight(); int blockHeight = Math.max(0, chainHeight - 500); List<InventoryItem> chainList = BTCLoader.blockStore.getChainList( blockHeight, Sha256Hash.ZERO_HASH); // // Build the locator list starting with the chain head and working // backwards towards // the genesis block // int step = 1; int loop = 0; int pos = chainList.size() - 1; while (pos >= 0) { invList.add(chainList.get(pos).getHash()); if (loop == 10) { step = step * 2; pos = pos - step; } else { loop++; pos--; } } if (invList.isEmpty()) invList.add(BTCLoader.blockStore.getChainHead()); } catch (BlockStoreException exc) { // // We can't query the database, so just locate the chain head and // hope we // are on the main chain // invList.add(BTCLoader.blockStore.getChainHead()); } return invList; } private void getExternalIP() { int inChar; try { if (hostName != null) { BTCLoader.listenAddress = new PeerAddress( InetAddress.getByName(hostName), BTCLoader.listenPort); BTCLoader.info(String.format("External IP address is %s", BTCLoader.listenAddress)); } else { try { HttpRequest request = HttpRequest .get("http://checkip.dyndns.org:80/"); if (request.ok()) { BTCLoader .info("Getting external IP address from checkip.dyndns.org"); try (InputStream inStream = request.stream()) { StringBuilder outString = new StringBuilder(128); while ((inChar = inStream.read()) >= 0) outString.appendCodePoint(inChar); String ipString = outString.toString(); int start = ipString.indexOf(':'); if (start < 0) { BTCLoader .error(String .format("Unrecognized response from checkip.dyndns.org\n Response: %s", ipString)); } else { int stop = ipString.indexOf('<', start); String ipAddress = ipString.substring( start + 1, stop).trim(); BTCLoader.listenAddress = new PeerAddress( InetAddress.getByName(ipAddress), BTCLoader.listenPort); BTCLoader.info(String.format( "External IP address is %s", BTCLoader.listenAddress)); } } } } catch (Throwable ex) { try { String ipAddress = IP46Utils.getLocalIP(); BTCLoader.listenAddress = new PeerAddress( InetAddress.getByName(ipAddress), BTCLoader.listenPort); BTCLoader.info(String.format( "External IP address is %s", BTCLoader.listenAddress)); } catch (Exception exc) { } } } } catch (Exception exc) { BTCLoader.error("Unable to get external IP address", exc); } } /** * Performs DNS lookups to get the initial peer list */ private void dnsDiscovery() { String[] dns = BTCLoader.testNetwork ? dnsTestSeeds : dnsSeeds; long currentTime = System.currentTimeMillis() / 1000; for (String host : dns) { PeerAddress peerAddress; try { InetAddress[] addresses = InetAddress.getAllByName(host); for (InetAddress address : addresses) { if (BTCLoader.listenAddress != null && address.equals(BTCLoader.listenAddress .getAddress())) continue; long timeSeen = currentTime - (long) ((double) (7 * 24 * 3600) * Math.random()); peerAddress = new PeerAddress(address, BTCLoader.DEFAULT_PORT, timeSeen); peerAddress.setBroadcast(true); if (BTCLoader.peerMap.get(peerAddress) == null) { BTCLoader.peerAddresses.add(peerAddress); BTCLoader.peerMap.put(peerAddress, peerAddress); } } } catch (UnknownHostException exc) { BTCLoader.warn(String.format("DNS host %s not found", host)); } } } /** * Check if an address is blacklisted * * @param address * Address to check * @return TRUE if the address is blacklisted */ private boolean isBlacklisted(InetAddress addr) { boolean blacklisted = false; for (BlacklistEntry entry : peerBlacklist) { if (entry.isBlacklisted(addr)) { blacklisted = true; break; } } return blacklisted; } /** * Blacklist entry */ public static class BlacklistEntry { /** Base address */ private final byte[] baseAddr; /** Subnet mask */ private final byte[] subnetMask; /** * Create the blacklist entry * * @param addr * IP address * @param maskBits * Number of bits in the address mask or -1 to use entire * address */ public BlacklistEntry(InetAddress addr, int maskBits) { baseAddr = addr.getAddress(); subnetMask = new byte[baseAddr.length]; int bitCount = baseAddr.length * 8; if (maskBits >= 0) bitCount = Math.min(bitCount, maskBits); for (int i = 0; i < subnetMask.length; i++) { if (bitCount >= 8) { subnetMask[i] = (byte) 0xff; bitCount -= 8; } else if (bitCount > 0) { subnetMask[i] = (byte) (0xff << (8 - bitCount)); bitCount = 0; } baseAddr[i] &= subnetMask[i]; } } /** * Check if an address matches this blacklist entry * * @param addr * IP address * @return TRUE if the address matches */ public boolean isBlacklisted(InetAddress addr) { byte[] addrBytes = addr.getAddress(); if (addrBytes.length != baseAddr.length) return false; boolean matches = true; for (int i = 0; i < baseAddr.length; i++) { if ((addrBytes[i] & subnetMask[i]) != baseAddr[i]) { matches = false; break; } } return matches; } } public SelectionKey getListenKey() { return listenKey; } public void setListenKey(SelectionKey listenKey) { this.listenKey = listenKey; } }