it.nibbles.javacoin.block.BlockChainImpl.java Source code

Java tutorial

Introduction

Here is the source code for it.nibbles.javacoin.block.BlockChainImpl.java

Source

/**
 * Copyright (C) 2011 NetMind Consulting Bt.
 * Copyright (C) 2012 nibbles.it.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package it.nibbles.javacoin.block;

import it.nibbles.javacoin.Block;
import it.nibbles.javacoin.BlockChain;
import it.nibbles.javacoin.Transaction;
import it.nibbles.javacoin.TransactionOutput;
import it.nibbles.javacoin.VerificationException;
import java.math.BigInteger;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Observable;
import java.util.ResourceBundle;
import java.util.StringTokenizer;
import java.util.TreeSet;
import org.apache.commons.collections.map.LRUMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The BlockChain is responsible for maintaining the list of valid Blocks
 * and also calculating the longest chain starting from the Genesis Block.
 * @author Robert Brautigam, Alessandro Polverini
 */
public class BlockChainImpl extends Observable implements BlockChain {
    private static final Logger logger = LoggerFactory.getLogger(BlockChainImpl.class);
    public static final long TARGET_TIMESPAN = 14l * 24l * 60l * 60l * 1000l; // A target lasts 14 days
    public static final long TARGET_SPACING = 10l * 60l * 1000l; // Spacing between two blocks 10 minutes
    public static final long TARGET_RECALC = TARGET_TIMESPAN / TARGET_SPACING;
    public static final int MEDIAN_BLOCKS = 11;
    public static final long COINBASE_MATURITY = 100;
    public static final long INITIAL_COINBASE_VALUE = 5000000000l;
    public static final long COINBASE_VALUE_HALFTIME = 210000l;

    private LRUMap blockHeadersCache = new LRUMap(100);

    private static final Map<BigInteger, Map<Integer, BigInteger>> knownHashes = new HashMap<>();

    private OrphanBlockSet orphanBlocks = new OrphanBlockSet();
    //private Block genesisBlock = null;
    private BlockChainLinkStorage linkStorage = null;
    private BlockChainListener listener = null;
    //private ScriptFactory scriptFactory = null;
    private BitcoinFactory bitcoinFactory = null;
    private boolean simplifiedVerification = false;
    //private boolean isTestnet = false;
    //private ParallelTransactionsVerifier parallelVerifier;
    private BlockTransactionsVerifier transactionsVerifier;

    public BlockChainImpl(BitcoinFactory bitcoinFactory, BlockChainLinkStorage linkStorage,
            boolean simplifiedVerification) throws VerificationException {
        this(bitcoinFactory, linkStorage, simplifiedVerification, 0);
    }

    /**
     * Construct a new block chain.
     * @param genesisBlock The valid genesis block for this chain.
     * @param linkStorage The store to get/store the chain links.
     * @param simplifiedVerification Set to "true" to disable transaction checking. If this
     * is disabled the bitcoin network (whoever supplies blocks) is trusted instead. You have to
     * disable this check if you don't want to run a full node.
     * @param maxThreads Hints to maximum number of threads to use in the computing.
     * If zero autodetects the best value, if greater than zero uses at most that level of parallelism
     */
    public BlockChainImpl(BitcoinFactory bitcoinFactory, BlockChainLinkStorage linkStorage,
            boolean simplifiedVerification, int maxThreads) throws VerificationException {
        this.linkStorage = linkStorage;
        this.bitcoinFactory = bitcoinFactory;
        this.simplifiedVerification = simplifiedVerification;
        // Check if the genesis blocks equal, or add genesis block if storage is empty.
        // Here we assume that the storage is not tampered!
        BlockChainLink storedGenesisLink = linkStorage.getGenesisLink();
        if (storedGenesisLink == null) {
            BlockChainLink genesisLink = new BlockChainLink(bitcoinFactory.getGenesisBlock(),
                    bitcoinFactory.getGenesisDifficulty(), BlockChainLink.ROOT_HEIGHT);
            linkStorage.addLink(genesisLink);
        } else {
            if (!storedGenesisLink.getBlock().equals(bitcoinFactory.getGenesisBlock()))
                throw new VerificationException("genesis block in storage is not the same as the block chain's");
        }
        if (maxThreads == 1)
            transactionsVerifier = new SerialTransactionsVerifier(linkStorage, bitcoinFactory.getScriptFactory(),
                    simplifiedVerification);
        else
            transactionsVerifier = new ParallelTransactionsVerifier(linkStorage, bitcoinFactory.getScriptFactory(),
                    simplifiedVerification, maxThreads);
    }

    public void setListener(BlockChainListener listener) {
        this.listener = listener;
    }

    public BlockChainListener getListener() {
        return listener;
    }

    @Override
    public Block getBlock(byte[] hash) {
        BlockChainLink link = linkStorage.getLink(hash);
        if (link == null)
            return null;
        return link.getBlock();
    }

    /**
     * Returns a block header. Sometimes it can have transactions but there is no guarantee
     * It uses a little LRU cache to speedup things for getMedianTimestamp()
     * @param hash
     * @return The
     */
    public Block getBlockHeader(byte[] hash) {
        Block block = (Block) blockHeadersCache.get(new HashWrapper(hash));
        if (block != null)
            return block;
        BlockChainLink link = linkStorage.getLinkBlockHeader(hash);
        if (link == null)
            return null;
        blockHeadersCache.put(new HashWrapper(hash), link.getBlock());
        return link.getBlock();
    }

    /**
     * Get the previous block.
     */
    @Override
    public Block getPreviousBlock(Block current) {
        return getBlock(current.getPreviousBlockHash());
    }

    //   /**
    //    * Get the next block.
    //    */
    //   @Override
    //   public Block getNextBlock(Block current, Block target)
    //   {
    //      BlockChainLink link = linkStorage.getNextLink(current.getHash(),target.getHash());
    //      if ( link == null )
    //         return null;
    //      return link.getBlock();
    //   }
    //
    /**
     * Add a block to the chain. The block is only added if it is verified, and
     * passes all known checks. If the block already exists in the chain, nothing
     * is done (there are no changes). Note: orphan blocks are not fully checked
     * when letting into the store, but they will not be cleaned when it turns out
     * they are not valid (so they can't be repeated). Potential DOS attack vector.
     */
    @Override
    public void addBlock(Block block) throws VerificationException {
        addBlock(block, true);
    }

    /**
     * Compute the latest common block for the two blocks given.
     * @return The latest common block if there is one, or null if
     * there is no common block, in which case one or both blocks
     * must be not of this chain.
     */
    @Override
    public Block getCommonBlock(Block first, Block second) {
        BlockChainLink link = linkStorage.getCommonLink(first.getHash(), second.getHash());
        if (link == null)
            return null;
        return link.getBlock();
    }

    /**
     * Determine whether a block given is reachable from an intermediate
     * block by only going forward. In other words, they are both on this
     * chain, and both are on the same branch also.
     * @param target The block to reach.
     * @param source The source from which the target is attempted to be reached.
     * @return True if the target can be reached from the source, false otherwise.
     * A block can always reach itself. All blocks in the chain are reachable from
     * the genesis block.
     */
    @Override
    public boolean isReachable(Block target, Block source) {
        return linkStorage.isReachable(target.getHash(), source.getHash());
    }

    /**
     * Add a block to the chain. If the block is not connectable it will be added to the orphan pool.
     * No orphan is ever passed to the store.
     * @param rawBlock The block to add.
     * @param checkOrphans Whether we should check if some orphans are now connectable.
     */
    private int addBlock(Block block, boolean checkOrphans) throws VerificationException {
        logger.debug("Trying to add block: {}", block);

        // Internal validation
        block.validate();

        logger.debug("Checking whether block is already in the chain...");
        if (linkStorage.blockExists(block.getHash()))
            return 0;

        // Check 11: Check whether block is orphan block, in which case notify
        // listener to try to get that block and stop
        logger.debug("Checking whether block is orphan...");
        BlockChainLink previousLink = linkStorage.getLinkBlockHeader(block.getPreviousBlockHash());
        if (previousLink == null) {
            orphanBlocks.addBlock(block);

            // Notify listeners that we have a missing block
            if (listener != null)
                listener.notifyMissingBlock(block.getPreviousBlockHash());
            // Finish here for now, this block will be re-checked as soon as
            // its parent will be non-orphan
            return 0;
        }

        // Check 12: Check that nBits value matches the difficulty rules
        logger.debug("checking whether block has the appropriate target...");
        DifficultyTarget blockTarget = new DifficultyTarget(block.getCompressedTarget());
        BlockChainLink link = new BlockChainLink(block, // Create link for block
                previousLink.getTotalDifficulty().add(bitcoinFactory.newDifficulty(blockTarget)),
                previousLink.getHeight() + 1);
        DifficultyTarget calculatedTarget = getNextDifficultyTarget(previousLink, link.getBlock());
        if (blockTarget.compareTo(calculatedTarget) != 0)
            // Target has to exactly match the one calculated, otherwise it is
            // considered invalid!
            throw new VerificationException(
                    "block has wrong target " + blockTarget + ", when calculated is: " + calculatedTarget);

        // Check 13: Reject if timestamp is before the median time of the last 11 blocks
        long medianTimestamp = getMedianTimestamp(previousLink);
        logger.debug("checking timestamp {} against median {}", block.getCreationTime(), medianTimestamp);
        if (block.getCreationTime() <= medianTimestamp)
            throw new VerificationException("block's creation time (" + block.getCreationTime()
                    + ") is not after median of previous blocks: " + medianTimestamp);

        // Check 14: Check for known hashes
        BigInteger genesisHash = new BigInteger(1, bitcoinFactory.getGenesisBlock().getHash());
        BigInteger blockHash = new BigInteger(1, block.getHash());
        if (knownHashes.containsKey(genesisHash)) {
            BigInteger knownHash = knownHashes.get(genesisHash).get(link.getHeight());
            if ((knownHash != null) && (!knownHash.equals(blockHash)))
                throw new VerificationException(
                        "block should have a hash we already know, but it doesn't, might indicate a tampering or attack at depth: "
                                + link.getHeight());
        } else
            logger.warn(
                    "known hashes don't exist for this chain, security checks for known blocks can not be made");
        // Checks 15,16,17,18: Check the transactions in the block
        // We diverge from the official list here since we don't maintain main and side branches
        // separately, and we have to make sure block is 100% compliant if we want to add it to the
        // tree (as non-orphan). Because of Block checks we know the first is a coinbase tx and
        // the rest are not. So execute checks from point 16. (Checks 16.3-5 are not
        // handles since they don't apply to this model)
        logger.debug("checking transactions...");
        // long time = System.currentTimeMillis();
        // long blockFees = serialTransactionVerifier(previousLink, block);
        // long time1 = System.currentTimeMillis() - time;
        long blockFees;
        try {
            //time = System.currentTimeMillis();
            //blockFees = parallelVerifier.verifyBlockTransactions(linkStorage, bitcoinFactory.getScriptFactory(), previousLink, block, simplifedVerification);
            blockFees = transactionsVerifier.verifyBlockTransactions(previousLink, block);
            //long time3 = System.currentTimeMillis() - time;
            //if (blockFees != parallelFees)
            //   throw new VerificationException("Calcolo fee non corrispondente: " + blockFees + " vs " + parallelFees);
            //else
            //   logger.info("time1: " + time1 + " time3: " + time3 + " fees: " + blockFees + " speedup: " + ((1.0 * time2) / time3));
        } catch (Exception e) {
            logger.error("Ex in nuovo codice verifica transazioni: " + e.getMessage(), e);
            throw new VerificationException(e.getMessage(), e);
        }

        // Verify coinbase if we have full verification and there is a coinbase
        logger.debug("verifying coinbase...");
        Transaction coinbaseTx = null;
        if (!block.getTransactions().isEmpty())
            coinbaseTx = block.getTransactions().get(0);
        if ((!simplifiedVerification) && (coinbaseTx.isCoinbase())) {
            long coinbaseValue = 0;
            for (TransactionOutput out : coinbaseTx.getOutputs()) {
                coinbaseValue += out.getValue();
            }
            // Check 16.2: Verify that the money produced is in the legal range
            // Valid if coinbase value is not greater than mined value plus fees in tx
            long coinbaseValid = getBlockCoinbaseValue(link);
            if (coinbaseValue > coinbaseValid + blockFees)
                throw new VerificationException(
                        "coinbase transaction in block " + block + " claimed more coins than appropriate: "
                                + coinbaseValue + " vs. " + (coinbaseValid + blockFees) + " (coinbase: "
                                + coinbaseValid + ", block fees: " + blockFees + ")");
        }

        // Check 16.6: Relay block to our peers
        // (Also: add or update the link in storage, and only relay if it's really new)
        logger.debug("adding block to storage...");
        linkStorage.addLink(link);
        if (listener != null)
            listener.notifyAddedBlock(block);

        // Check 19: For each orphan block for which this block is its prev,
        // run all these steps (including this one) recursively on that orphan
        int blocksAdded = 1;
        if (checkOrphans) {
            blocksAdded += connectOrphanBlocks(block);
        }
        return blocksAdded;
    }

    /**
     * This is an iterative function that adds to the chain all
     * the orphan blocks that can be added after a new block is inserted
     * to avoid the otherwise more natural recursion.
     * @param lastAddedBlock
     * @return Number of blocks added to the chain
     */
    private int connectOrphanBlocks(Block lastAddedBlock) {
        logger.debug("Checking for connectable orphans...");
        int blocksAdded = 0;
        List<Block> blocks = new LinkedList<>();
        blocks.add(lastAddedBlock);
        Block orphan;
        while (!blocks.isEmpty()) {
            Block block = blocks.remove(0);
            while ((orphan = orphanBlocks.removeBlockByPreviousHash(block.getHash())) != null) {
                blocks.add(orphan);
                try {
                    int n = addBlock(orphan, false);
                    if (n != 1)
                        logger.error("Internal error: inserting connectable orphan block returned " + n
                                + " instead of 1");
                    blocksAdded++;
                } catch (VerificationException e) {
                    logger.warn("orphan block was rechecked (because parent appeared), but is not valid", e);
                }
            }
        }
        return blocksAdded;
    }

    /**
     * Get a Block's maximum coinbase value.
     */
    private long getBlockCoinbaseValue(BlockChainLink link) {
        return (INITIAL_COINBASE_VALUE) >> (link.getHeight() / COINBASE_VALUE_HALFTIME);
    }

    /**
     * Calculate the median of the (some number of) blocks starting at the given block.
     */
    private long getMedianTimestamp(BlockChainLink link) {
        if (link == null)
            return 0;
        Block block = link.getBlock();
        List<Long> times = new LinkedList<>();
        for (int i = 0; (block != null) && (i < MEDIAN_BLOCKS); i++) {
            times.add(block.getCreationTime());
            block = getBlockHeader(block.getPreviousBlockHash());
        }
        Collections.sort(times);
        return times.get(times.size() / 2);
    }

    /**
     * Calculate the difficulty for the next block after the one supplied.
     */
    public DifficultyTarget getNextDifficultyTarget(BlockChainLink link, Block newBlock) {
        // If we're calculating for the genesis block return
        // fixed difficulty
        if (link == null)
            return bitcoinFactory.maxDifficultyTarget();
        // Look whether it's time to change the difficulty setting
        // (only change every TARGET_RECALC blocks). If not, return the
        // setting of this block, because the next one has to have the same
        // target.
        if ((link.getHeight() + 1) % TARGET_RECALC != 0) {
            // Special rules for testnet (for testnet2 only after 15 Feb 2012)
            Block currBlock = link.getBlock();
            if (bitcoinFactory.isTestnet3()
                    || (bitcoinFactory.isTestnet2() && currBlock.getCreationTime() > 1329180000000L)) {
                // If the new block's timestamp is more than 2* 10 minutes
                // then allow mining of a min-difficulty block.
                if (newBlock.getCreationTime() > link.getBlock().getCreationTime() + 2 * TARGET_SPACING)
                    return bitcoinFactory.maxDifficultyTarget();
                else {
                    // Return the last non-special-min-difficulty-rules-block
                    // We could use a custom method here to load only block headers instead of full blocks
                    // but this lack of performance is only for the testnet so we don't care
                    while (link != null && (link.getHeight() % TARGET_RECALC) != 0 && link.getBlock()
                            .getCompressedTarget() == bitcoinFactory.maxDifficultyTarget().getCompressedTarget())
                        link = linkStorage.getLinkBlockHeader(link.getBlock().getPreviousBlockHash());
                    if (link != null)
                        return new DifficultyTarget(link.getBlock().getCompressedTarget());
                    else
                        return bitcoinFactory.maxDifficultyTarget();
                }
            }
            logger.debug("previous height {}, not change in target", link.getHeight());
            return new DifficultyTarget(link.getBlock().getCompressedTarget());
        }
        // We have to change the target. First collect the last TARGET_RECALC 
        // blocks (including the given block) 
        Block startBlock = link.getBlock();
        for (int i = 0; (i < TARGET_RECALC - 1) && (startBlock != null); i++)
            startBlock = getBlockHeader(startBlock.getPreviousBlockHash());
        // This shouldn't happen, we reached genesis
        if (startBlock == null)
            return bitcoinFactory.maxDifficultyTarget();
        // Calculate the time the TARGET_RECALC blocks took
        long calculatedTimespan = link.getBlock().getCreationTime() - startBlock.getCreationTime();
        if (calculatedTimespan < TARGET_TIMESPAN / 4)
            calculatedTimespan = TARGET_TIMESPAN / 4;
        if (calculatedTimespan > TARGET_TIMESPAN * 4)
            calculatedTimespan = TARGET_TIMESPAN * 4;
        // Calculate new target, but allow no more than maximum target
        DifficultyTarget difficultyTarget = new DifficultyTarget(link.getBlock().getCompressedTarget());
        BigInteger target = difficultyTarget.getTarget();
        target = target.multiply(BigInteger.valueOf(calculatedTimespan));
        target = target.divide(BigInteger.valueOf(TARGET_TIMESPAN));
        // Return the new difficulty setting
        DifficultyTarget resultTarget = new DifficultyTarget(target);
        DifficultyTarget maxTarget = bitcoinFactory.maxDifficultyTarget();
        if (resultTarget.compareTo(maxTarget) > 0)
            return maxTarget;
        else
            resultTarget = new DifficultyTarget(resultTarget.getCompressedTarget()); // Normalize
        logger.debug("previous height {}, recalculated target is: {}", link.getHeight(), resultTarget);
        return resultTarget;
    }

    /**
     * Returns a block 
     * @return 
     */
    //   public Block prepareNewBlock(int version)
    //   {
    //      if (version <1 || version > 2) {
    //         logger.error("Unsupported block version: "+version);
    //         return null;
    //      }
    //      ScriptFragment coinbaseScript = bitcoinFactory.getScriptFactory().
    //         createFragment(new byte[] { 0x00 });
    //      TransactionInput txIn = new TransactionInputImpl(TransactionInput.ZERO_HASH, -1, coinbaseScript, 0);
    //      TransactionOutput txOut = new TransactionOutputImpl(, coinbaseScript)
    //      Block block = new BlockImpl(null, TARGET_RECALC, TARGET_RECALC, TARGET_TIMESPAN, previousBlockHash, merkleRoot, null, 2);
    //   }

    /**
     * Return a block locator to be used by getBlocks using the specs defined by the wiki:
     * https://en.bitcoin.it/wiki/Protocol_specification#getblocks
     * @return a list of block hashes
     */
    @Override
    public List<byte[]> buildBlockLocator() {
        List<byte[]> blocks = new LinkedList<>();
        int topHeight = linkStorage.getLastLink().getHeight();
        int start = 0;
        int step = 1;
        for (long i = topHeight; i > 0; i -= step, ++start) {
            if (start >= 10)
                step *= 2;
            blocks.add(linkStorage.getHashOfMainChainAtHeight(i));
        }
        blocks.add(getGenesisBlock().getHash());
        return blocks;
    }

    @Override
    public Block getGenesisBlock() {
        return bitcoinFactory.getGenesisBlock();
    }

    @Override
    public Block getLastBlock() {
        return linkStorage.getLastLink().getBlock();
    }

    @Override
    public int getHeight() {
        return linkStorage.getHeight();
    }

    static {
        logger.debug("reading known hashes...");
        try {
            Map<Integer, BigInteger> idGenesis = new HashMap<>();
            ResourceBundle bundle = ResourceBundle.getBundle("chain-knownhashes");
            // We need to sort the keys of the bundle
            for (String key : new TreeSet<>(bundle.keySet())) {
                StringTokenizer tokens = new StringTokenizer(key, ".");
                tokens.nextToken();
                int id = Integer.valueOf(tokens.nextToken());
                int height = Integer.valueOf(tokens.nextToken());
                BigInteger hash = new BigInteger(bundle.getString(key).substring(2), 16);
                // Put into maps
                if (height == 0) {
                    // This is a genesis block, so rememeber and create maps
                    idGenesis.put(id, hash);
                    knownHashes.put(hash, new HashMap<Integer, BigInteger>());
                    logger.debug("New chain for known hashes: " + id + "," + hash.toString(16));
                } else {
                    // This is a random height, so there should be a map for that
                    Map<Integer, BigInteger> values = knownHashes.get(idGenesis.get(id));
                    if (values == null)
                        logger.warn("can not accept known value for id " + id + ", height " + height
                                + ", because genesis hash not yet defined");
                    else {
                        values.put(height, hash);
                        logger.debug("Known Hash: " + hash.toString(16) + " at height: " + height);
                    }
                }
            }
            logger.info("KnownHashes initialized with " + knownHashes.size() + " items");
        } catch (MissingResourceException e) {
            logger.warn("can not read known hashes for the block chain, security might be impacted", e);
        }
    }

}