/** * 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.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.*; import java.util.Date; /** * A Peer handles the high level communication with a BitCoin node. It requires a NetworkConnection to be set up for * it. After that it takes ownership of the connection, creates and manages its own thread used for communication * with the network. All these threads synchronize on the block chain. */ public class Peer { private static final Logger log = LoggerFactory.getLogger(Peer.class); private final NetworkConnection conn; private final NetworkParameters params; private Thread thread; // Whether the peer thread is supposed to be running or not. Set to false during shutdown so the peer thread // knows to quit when the socket goes away. private boolean running; private final BlockChain blockChain; private final Wallet wallet; // Used to notify clients when the initial block chain download is finished. private CountDownLatch chainCompletionLatch; // When we want to download a block or transaction from a peer, the InventoryItem is put here whilst waiting for // the response. Synchronized on itself. private final List<GetDataFuture<Block>> pendingGetBlockFutures; /** * Construct a peer that handles the given network connection and reads/writes from the given block chain. Note that * communication won't occur until you call start(). */ public Peer(NetworkParameters params, NetworkConnection conn, BlockChain blockChain, Wallet wallet) { this.conn = conn; this.params = params; this.blockChain = blockChain; this.wallet = wallet; this.pendingGetBlockFutures = new ArrayList<GetDataFuture<Block>>(); } /** Starts the background thread that processes messages. */ public void start() { this.thread = new Thread(new Runnable() { public void run() { Peer.this.run(); } }); synchronized (this) { running = true; } this.thread.setName("Bitcoin peer thread: " + conn.toString()); this.thread.start(); } public boolean isRunning(){ return running; } /** * Runs in the peers network thread and manages communication with the peer. */ private void run() { assert Thread.currentThread() == thread; try { while (true) { Message m = conn.readMessage(); if (m instanceof InventoryMessage) { processInv((InventoryMessage) m); } else if (m instanceof Block) { processBlock((Block) m); } else if (m instanceof Transaction) { processPendingTransaction((Transaction) m); } else if (m instanceof AddressMessage) { // We don't care about addresses of the network right now. But in future, // we should save them in the wallet so we don't put too much load on the seed nodes and can // properly explore the network. } else { // TODO: Handle the other messages we can receive. log.warn("Received unhandled message: {}", m); } } } catch (Exception e) { if (e instanceof IOException && !running) { // This exception was expected because we are tearing down the socket as part of quitting. log.info("Shutting down peer thread"); } else { // We caught an unexpected exception. log.info("Peer Exception"); e.printStackTrace(); } } synchronized (this) { running = false; } } // process an unverified pending transaction, add it to pending in our wallet and call onPendingCoinsReceived private void processPendingTransaction(Transaction tx) { assert Thread.currentThread() == thread; if (tx.isMine(wallet)) { wallet.receivePendingTransaction(tx); } } private void processBlock(Block m) throws IOException { assert Thread.currentThread() == thread; try { // Was this block requested by getBlock()? synchronized (pendingGetBlockFutures) { for (int i = 0; i < pendingGetBlockFutures.size(); i++) { GetDataFuture<Block> f = pendingGetBlockFutures.get(i); if (Arrays.equals(f.getItem().hash, m.getHash())) { // Yes, it was. So pass it through the future. f.setResult(m); // Blocks explicitly requested don't get sent to the block chain. pendingGetBlockFutures.remove(i); return; } } } // Otherwise it's a block sent to us because the peer thought we needed it, so add it to the block chain. // This call will synchronize on blockChain. if (blockChain.add(m)) { // The block was successfully linked into the chain. Notify the user of our progress. if (chainCompletionLatch != null) { chainCompletionLatch.countDown(); if (chainCompletionLatch.getCount() == 0) { // All blocks fetched, so we don't need this anymore. chainCompletionLatch = null; } } } else { // This block is unconnected - we don't know how to get from it back to the genesis block yet. That // must mean that there are blocks we are missing, so do another getblocks with a new block locator // to ask the peer to send them to us. This can happen during the initial block chain download where // the peer will only send us 500 at a time and then sends us the head block expecting us to request // the others. // TODO: Should actually request root of orphan chain here. blockChainDownload(m.getHash()); } } catch (VerificationException e) { // We don't want verification failures to kill the thread. log.warn("block verification failed", e); } catch (ScriptException e) { // We don't want script failures to kill the thread. log.warn("script exception", e); } } private void processInv(InventoryMessage inv) throws IOException { assert Thread.currentThread() == thread; // The peer told us about some blocks or transactions they have. For now we only care about blocks. // Note that as we don't actually want to store the entire block chain or even the headers of the block // chain, we may end up requesting blocks we already requested before. This shouldn't (in theory) happen // enough to be a problem. Block topBlock = blockChain.getUnconnectedBlock(); byte[] topHash = (topBlock != null ? topBlock.getHash() : null); List<InventoryItem> items = inv.getItems(); if (items.size() == 1 && items.get(0).type == InventoryItem.Type.Block && topHash != null && Arrays.equals(items.get(0).hash, topHash)) { // An inv with a single hash containing our most recent unconnected block is a special inv, // it's kind of like a tickle from the peer telling us that it's time to download more blocks to catch up to // the block chain. We could just ignore this and treat it as a regular inv but then we'd download the head // block over and over again after each batch of 500 blocks, which is wasteful. blockChainDownload(topHash); return; } GetDataMessage getdata = new GetDataMessage(params); boolean dirty = false; for (InventoryItem item : items) { if (item.type != InventoryItem.Type.Block && item.type != InventoryItem.Type.Transaction) continue; getdata.addItem(item); dirty = true; } // No blocks to download. This probably contained transactions instead, but right now we can't prove they are // valid so we don't bother downloading transactions that aren't in blocks yet. if (!dirty) return; // This will cause us to receive a bunch of block messages. conn.writeMessage(getdata); } /** * Asks the connected peer for the block of the given hash, and returns a Future representing the answer. * If you want the block right away and don't mind waiting for it, just call .get() on the result. Your thread * will block until the peer answers. You can also use the Future object to wait with a timeout, or just check * whether it's done later. * * @param blockHash Hash of the block you wareare requesting. * @throws IOException */ public Future<Block> getBlock(byte[] blockHash) throws IOException { InventoryMessage getdata = new InventoryMessage(params); InventoryItem inventoryItem = new InventoryItem(InventoryItem.Type.Block, blockHash); getdata.addItem(inventoryItem); GetDataFuture<Block> future = new GetDataFuture<Block>(inventoryItem); // Add to the list of things we're waiting for. It's important this come before the network send to avoid // race conditions. synchronized (pendingGetBlockFutures) { pendingGetBlockFutures.add(future); } conn.writeMessage(getdata); return future; } // A GetDataFuture wraps the result of a getBlock or (in future) getTransaction so the owner of the object can // decide whether to wait forever, wait for a short while or check later after doing other work. private class GetDataFuture<T extends Message> implements Future<T> { private boolean cancelled; private final InventoryItem item; private final CountDownLatch latch; private T result; GetDataFuture(InventoryItem item) { this.item = item; this.latch = new CountDownLatch(1); } public boolean cancel(boolean b) { // Cannot cancel a getdata - once sent, it's sent. cancelled = true; return false; } public boolean isCancelled() { return cancelled; } public boolean isDone() { return result != null || cancelled; } public T get() throws InterruptedException, ExecutionException { latch.await(); assert result != null; return result; } public T get(long l, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException { if (!latch.await(l, timeUnit)) throw new TimeoutException(); assert result != null; return result; } InventoryItem getItem() { return item; } /** Called by the Peer when the result has arrived. Completes the task. */ void setResult(T result) { assert Thread.currentThread() == thread; // Called from peer thread. this.result = result; // Now release the thread that is waiting. We don't need to synchronize here as the latch establishes // a memory barrier. latch.countDown(); } } /** * Send the given Transaction, ie, make a payment with BitCoins. To create a transaction you can broadcast, use * a {@link Wallet}. After the broadcast completes, confirm the send using the wallet confirmSend() method. * @throws IOException */ public void broadcastTransaction(Transaction tx) throws IOException { conn.writeMessage(tx); } private void blockChainDownload(byte[] toHash) throws IOException { // This may run in ANY thread. // The block chain download process is a bit complicated. Basically, we start with zero or more blocks in a // chain that we have from a previous session. We want to catch up to the head of the chain BUT we don't know // where that chain is up to or even if the top block we have is even still in the chain - we // might have got ourselves onto a fork that was later resolved by the network. // // To solve this, we send the peer a block locator which is just a list of block hashes. It contains the // blocks we know about, but not all of them, just enough of them so the peer can figure out if we did end up // on a fork and if so, what the earliest still valid block we know about is likely to be. // // Once it has decided which blocks we need, it will send us an inv with up to 500 block messages. We may // have some of them already if we already have a block chain and just need to catch up. Once we request the // last block, if there are still more to come it sends us an "inv" containing only the hash of the head // block. // // That causes us to download the head block but then we find (in processBlock) that we can't connect // it to the chain yet because we don't have the intermediate blocks. So we rerun this function building a // new block locator describing where we're up to. // // The getblocks with the new locator gets us another inv with another bunch of blocks. We download them once // again. This time when the peer sends us an inv with the head block, we already have it so we won't download // it again - but we recognize this case as special and call back into blockChainDownload to continue the // process. // // So this is a complicated process but it has the advantage that we can download a chain of enormous length // in a relatively stateless manner and with constant/bounded memory usage. log.info("blockChainDownload({})", Utils.bytesToHexString(toHash)); // TODO: Block locators should be abstracted out rather than special cased here. List<byte[]> blockLocator = new LinkedList<byte[]>(); // We don't do the exponential thinning here, so if we get onto a fork of the chain we will end up // redownloading the whole thing again. blockLocator.add(params.genesisBlock.getHash()); Block topBlock = blockChain.getChainHead().getHeader(); if (!topBlock.equals(params.genesisBlock)) blockLocator.add(0, topBlock.getHash()); GetBlocksMessage message = new GetBlocksMessage(params, blockLocator, toHash); conn.writeMessage(message); } /** * Starts an asynchronous download of the block chain. The chain download is deemed to be complete once we've * downloaded the same number of blocks that the peer advertised having in its version handshake message. * * @return a {@link CountDownLatch} that can be used to track progress and wait for completion. */ public CountDownLatch startBlockChainDownload() throws IOException { // Chain will overflow signed int blocks in ~41,000 years. int chainHeight = (int) conn.getVersionMessage().bestHeight; if (chainHeight <= 0) { // This should not happen because we shouldn't have given the user a Peer that is to another client-mode // node. If that happens it means the user overrode us somewhere. throw new RuntimeException("Peer does not have block chain"); } int blocksToGet = chainHeight - blockChain.getChainHead().getHeight(); chainCompletionLatch = new CountDownLatch(blocksToGet); if (blocksToGet > 0) { // When we just want as many blocks as possible, we can set the target hash to zero. blockChainDownload(new byte[32]); } return chainCompletionLatch; } /** * Terminates the network connection and stops the background thread. */ public void disconnect() { synchronized (this) { running = false; } try { // This will cause the background thread to die, but it's really ugly. We must do a better job of this. conn.shutdown(); } catch (IOException e) { // Don't care about this. } } }