/** * 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 java.math.BigInteger; import java.util.*; import static com.google.bitcoin.core.Utils.LOG; /** * A BlockChain holds a series of {@link Block} objects, links them together, and knows how to verify that the * chain follows the rules of the {@link NetworkParameters} for this chain.<p> * * A BlockChain requires a {@link Wallet} to receive transactions that it finds during the initial download. However, * if you don't care about this, you can just pass in an empty wallet and nothing bad will happen.<p> * * A newly constructed BlockChain is empty. To fill it up, use a {@link Peer} object to download the chain from the * network.<p> * * <b>Notes</b><p> * * The 'chain' can actually be a tree although in normal operation it can be thought of as a simple list. In such a * situation there are multiple stories of the economy competing to become the one true consensus. This can happen * naturally when two miners solve a block within a few seconds of each other, or it can happen when the chain is * under attack.<p> * * A reference to the head block of every chain is stored. If you can reach the genesis block by repeatedly walking * through the prevBlock pointers, then we say this is a full chain. If you cannot reach the genesis block we say it is * an orphan chain.<p> * * Orphan chains can occur when blocks are solved and received during the initial block chain download, * or if we connect to a peer that doesn't send us blocks in order. */ public class BlockChain { /** Keeps a map of block hashes to StoredBlocks. */ protected BlockStore blockStore; /** * Tracks the top of the best known chain.<p> * * Following this one down to the genesis block produces the story of the economy from the creation of BitCoin * until the present day. The chain head can change if a new set of blocks is received that results in a chain of * greater work than the one obtained by following this one down. In that case a reorganize is triggered, * potentially invalidating transactions in our wallet. */ protected StoredBlock chainHead; protected final NetworkParameters params; protected final Wallet wallet; // Holds blocks that we have received but can't plug into the chain yet, eg because they were created whilst we // were downloading the block chain. private final ArrayList<Block> unconnectedBlocks = new ArrayList<Block>(); /** * Constructs a BlockChain connected to the given wallet and store. To obtain a {@link Wallet} you can construct * one from scratch, or you can deserialize a saved wallet from disk using {@link Wallet#loadFromFile(java.io.File)} * <p> * * For the store you can use a {@link MemoryBlockStore} if you don't care about saving the downloaded data, or a * {@link DiskBlockStore} if you'd like to ensure fast startup the next time you run the program. */ public BlockChain(NetworkParameters params, Wallet wallet, BlockStore blockStore) { try { this.blockStore = blockStore; chainHead = blockStore.getChainHead(); LOG("chain head is: " + chainHead.getHeader().toString()); } catch (BlockStoreException e) { throw new RuntimeException(e); } this.params = params; this.wallet = wallet; } /** * Processes a received block and tries to add it to the chain. If there's something wrong with the block an * exception is thrown. If the block is OK but cannot be connected to the chain at this time, returns false. * If the block can be connected to the chain, returns true. */ public synchronized boolean add(Block block) throws VerificationException, ScriptException { try { return add(block, true); } catch (BlockStoreException e) { // TODO: Figure out a better way to propagate this exception to the user. throw new RuntimeException(e); } } private synchronized boolean add(Block block, boolean tryConnecting) throws BlockStoreException, VerificationException, ScriptException { LOG("Adding block " + block.getHashAsString() + " to the chain"); if (blockStore.get(block.getHash()) != null) { LOG("Already have block"); return true; } // Prove the block is internally valid: hash is lower than target, merkle root is correct and so on. try { block.verify(); } catch (VerificationException e) { LOG("Failed to verify block: " + e.toString()); LOG(block.toString()); throw e; } // Try linking it to a place in the currently known blocks. StoredBlock storedPrev = blockStore.get(block.getPrevBlockHash()); if (storedPrev == null) { // We can't find the previous block. Probably we are still in the process of downloading the chain and a // block was solved whilst we were doing it. We put it to one side and try to connect it later when we // have more blocks. LOG("Block does not connect: " + block.getHashAsString()); unconnectedBlocks.add(block); return false; } else { // It connects to somewhere on the chain. Not necessarily the top of the best known chain. // // Create a new StoredBlock from this block. It will throw away the transaction data so when block goes // out of scope we will reclaim the used memory. checkDifficultyTransitions(storedPrev, block); StoredBlock newStoredBlock = storedPrev.build(block); blockStore.put(newStoredBlock); // block.transactions may be null here if we received only a header and not a full block. This does not // happen currently but might in future if getheaders is implemented. connectBlock(newStoredBlock, storedPrev, block.transactions); } if (tryConnecting) tryConnectingUnconnected(); return true; } private void connectBlock(StoredBlock newStoredBlock, StoredBlock storedPrev, List<Transaction> newTransactions) throws BlockStoreException, VerificationException { if (storedPrev.equals(chainHead)) { // This block connects to the best known block, it is a normal continuation of the system. setChainHead(newStoredBlock); LOG("Chain is now " + chainHead.getHeight() + " blocks high"); if (newTransactions != null) sendTransactionsToWallet(newStoredBlock, NewBlockType.BEST_CHAIN, newTransactions); } else { // This block connects to somewhere other than the top of the best known chain. We treat these differently. // // Note that we send the transactions to the wallet FIRST, even if we're about to re-organize this block // to become the new best chain head. This simplifies handling of the re-org in the Wallet class. boolean causedSplit = newStoredBlock.moreWorkThan(chainHead); if (causedSplit) { LOG("Block is causing a re-organize"); } else { LOG("Block forks the chain, but it did not cause a reorganize."); } // We may not have any transactions if we received only a header. That never happens today but will in // future when getheaders is used as an optimization. if (newTransactions != null) { sendTransactionsToWallet(newStoredBlock, NewBlockType.SIDE_CHAIN, newTransactions); } if (causedSplit) handleChainSplit(newStoredBlock); } } /** * Called as part of connecting a block when the new block results in a different chain having higher total work. */ private void handleChainSplit(StoredBlock newChainHead) throws BlockStoreException, VerificationException { // This chain has overtaken the one we currently believe is best. Reorganize is required. // // Firstly, calculate the block at which the chain diverged. We only need to examine the // chain from beyond this block to find differences. StoredBlock splitPoint = findSplit(newChainHead, chainHead); LOG("Re-organize after split at height " + splitPoint.getHeight()); LOG("Old chain head: " + chainHead.getHeader().getHashAsString()); LOG("New chain head: " + newChainHead.getHeader().getHashAsString()); LOG("Split at block: " + splitPoint.getHeader().getHashAsString()); // Then build a list of all blocks in the old part of the chain and the new part. Set<StoredBlock> oldBlocks = getPartialChain(chainHead, splitPoint); Set<StoredBlock> newBlocks = getPartialChain(newChainHead, splitPoint); // Now inform the wallet. This is necessary so the set of currently active transactions (that we can spend) // can be updated to take into account the re-organize. We might also have received new coins we didn't have // before and our previous spends might have been undone. wallet.reorganize(oldBlocks, newBlocks); // Update the pointer to the best known block. setChainHead(newChainHead); } /** * Returns the set of contiguous blocks between 'higher' and 'lower'. Higher is included, lower is not. */ private Set<StoredBlock> getPartialChain(StoredBlock higher, StoredBlock lower) throws BlockStoreException { assert higher.getHeight() > lower.getHeight(); Set<StoredBlock> results = new HashSet<StoredBlock>(); StoredBlock cursor = higher; while (true) { results.add(cursor); cursor = cursor.getPrev(blockStore); assert cursor != null : "Ran off the end of the chain"; if (cursor.equals(lower)) break; } return results; } /** * Locates the point in the chain at which newStoredBlock and chainHead diverge. Returns null if no split point was * found (ie they are part of the same chain). */ private StoredBlock findSplit(StoredBlock newChainHead, StoredBlock chainHead) throws BlockStoreException { StoredBlock currentChainCursor = chainHead; StoredBlock newChainCursor = newChainHead; // Loop until we find the block both chains have in common. Example: // // A -> B -> C -> D // \--> E -> F -> G // // findSplit will return block B. chainHead = D and newChainHead = G. while (!currentChainCursor.equals(newChainCursor)) { // Move the new chain cursor backwards until it is at the same height as the current chain head. while (newChainCursor.getHeight() > currentChainCursor.getHeight()) { newChainCursor = blockStore.get(newChainCursor.getHeader().getPrevBlockHash()); // Stores contain the genesis block which has a height of zero. Thus we should always be able to find // a block of equal height in this loop. If we fall off the end of the chain it means there is a bug // in the library. assert newChainCursor != null : "Attempt to follow an orphan chain"; } // We found a place where the chains have equal height. In the first iteration with the above example // newChainCursor will be F. Did we find the fork yet? if (newChainCursor.equals(currentChainCursor)) break; // No, we did not. First iteration D != F so move currentChainCursor backwards one and try again. currentChainCursor = blockStore.get(currentChainCursor.getHeader().getPrevBlockHash()); assert currentChainCursor != null : "Attempt to follow an orphan chain"; // Eventually currentChainCursor will move from C to B, the inner while loop will move newChainCursor // from E to B and we will exit. } return currentChainCursor; } enum NewBlockType { BEST_CHAIN, SIDE_CHAIN } private void sendTransactionsToWallet(StoredBlock block, NewBlockType blockType, List<Transaction> newTransactions) throws VerificationException { // Scan the transactions to find out if any mention addresses we own. for (Transaction tx : newTransactions) { try { scanTransaction(block, tx, blockType); } catch (ScriptException e) { // We don't want scripts we don't understand to break the block chain, // so just note that this tx was not scanned here and continue. LOG("Failed to parse a script: " + e.toString()); } } } private void setChainHead(StoredBlock chainHead) { this.chainHead = chainHead; try { blockStore.setChainHead(chainHead); } catch (BlockStoreException e) { throw new RuntimeException(e); } } /** * For each block in unconnectedBlocks, see if we can now fit it on top of the chain and if so, do so. */ private void tryConnectingUnconnected() throws VerificationException, ScriptException, BlockStoreException { // For each block in our unconnected list, try and fit it onto the head of the chain. If we succeed remove it // from the list and keep going. If we changed the head of the list at the end of the round try again until // we can't fit anything else on the top. int blocksConnectedThisRound; do { blocksConnectedThisRound = 0; Iterator<Block> iter = unconnectedBlocks.iterator(); while (iter.hasNext()) { Block block = iter.next(); // Look up the blocks previous. StoredBlock prev = blockStore.get(block.getPrevBlockHash()); if (prev == null) { // This is still an unconnected/orphan block. continue; } // Otherwise we can connect it now. // False here ensures we don't recurse infinitely downwards when connecting huge chains. add(block, false); iter.remove(); blocksConnectedThisRound++; } if (blocksConnectedThisRound > 0) { LOG("Connected " + blocksConnectedThisRound + " floating blocks."); } } while (blocksConnectedThisRound > 0); } /** * Throws an exception if the blocks difficulty is not correct. */ private void checkDifficultyTransitions(StoredBlock storedPrev, Block next) throws BlockStoreException, VerificationException { Block prev = storedPrev.getHeader(); // Is this supposed to be a difficulty transition point? if ((storedPrev.getHeight() + 1) % params.interval != 0) { // No ... so check the difficulty didn't actually change. if (next.getDifficultyTarget() != prev.getDifficultyTarget()) throw new VerificationException("Unexpected change in difficulty at height " + storedPrev.getHeight() + ": " + Long.toHexString(next.getDifficultyTarget()) + " vs " + Long.toHexString(prev.getDifficultyTarget())); return; } // We need to find a block far back in the chain. It's OK that this is expensive because it only occurs every // two weeks after the initial block chain download. StoredBlock cursor = blockStore.get(prev.getHash()); for (int i = 0; i < params.interval - 1; i++) { if (cursor == null) { // This should never happen. If it does, it means we are following an incorrect or busted chain. throw new VerificationException( "Difficulty transition point but we did not find a way back to the genesis block."); } cursor = blockStore.get(cursor.getHeader().getPrevBlockHash()); } Block blockIntervalAgo = cursor.getHeader(); int timespan = (int) (prev.getTime() - blockIntervalAgo.getTime()); // Limit the adjustment step. if (timespan < params.targetTimespan / 4) timespan = params.targetTimespan / 4; if (timespan > params.targetTimespan * 4) timespan = params.targetTimespan * 4; BigInteger newDifficulty = Utils.decodeCompactBits(blockIntervalAgo.getDifficultyTarget()); newDifficulty = newDifficulty.multiply(BigInteger.valueOf(timespan)); newDifficulty = newDifficulty.divide(BigInteger.valueOf(params.targetTimespan)); if (newDifficulty.compareTo(params.proofOfWorkLimit) > 0) { LOG("Difficulty hit proof of work limit: " + newDifficulty.toString(16)); newDifficulty = params.proofOfWorkLimit; } int accuracyBytes = (int) (next.getDifficultyTarget() >>> 24) - 3; BigInteger receivedDifficulty = next.getDifficultyTargetAsInteger(); // The calculated difficulty is to a higher precision than received, so reduce here. BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8); newDifficulty = newDifficulty.and(mask); if (newDifficulty.compareTo(receivedDifficulty) != 0) throw new VerificationException("Network provided difficulty bits do not match what was calculated: " + receivedDifficulty.toString(16) + " vs " + newDifficulty.toString(16)); } private void scanTransaction(StoredBlock block, Transaction tx, NewBlockType blockType) throws ScriptException, VerificationException { boolean shouldReceive = false; for (TransactionOutput output : tx.outputs) { // TODO: Handle more types of outputs, not just regular to address outputs. if (output.getScriptPubKey().isSentToIP()) return; // This is not thread safe as a key could be removed between the call to isMine and receive. if (output.isMine(wallet)) { shouldReceive = true; } } // Coinbase transactions don't have anything useful in their inputs (as they create coins out of thin air). if (!tx.isCoinBase()) { for (TransactionInput i : tx.inputs) { byte[] pubkey = i.getScriptSig().getPubKey(); // This is not thread safe as a key could be removed between the call to isPubKeyMine and receive. if (wallet.isPubKeyMine(pubkey)) { shouldReceive = true; } } } if (shouldReceive) wallet.receive(tx, block, blockType); } /** * Returns the block at the head of the current best chain. This is the block which represents the greatest * amount of cumulative work done. */ public synchronized StoredBlock getChainHead() { return chainHead; } /** * Returns the most recent unconnected block or null if there are none. This will all have to change. */ public synchronized Block getUnconnectedBlock() { if (unconnectedBlocks.size() == 0) return null; return unconnectedBlocks.get(unconnectedBlocks.size() - 1); } }