public void checkConsistency() throws com.google.bitcoin.store.BlockStoreException { StoredBlock head = block_store.getChainHead(); StoredBlock curr_block = head; Sha256Hash genisis_hash = params.getGenesisBlock().getHash(); int checked = 0; while (true) { Sha256Hash curr_hash = curr_block.getHeader().getHash(); if (curr_block.getHeight() % 10000 == 0) { System.out.println("Block: " + curr_block.getHeight()); } if (!file_db.getBlockMap().containsKey(curr_hash)) { throw new RuntimeException("Missing block: " + curr_hash); } checked++; // if (checked > 20) return; if (curr_hash.equals(genisis_hash)) return; curr_block = curr_block.getPrev(block_store); } }
@Override protected StoredBlock addToBlockStore( StoredBlock storedPrev, Block header, TransactionOutputChanges txOutChanges) throws BlockStoreException, VerificationException { StoredBlock newBlock = storedPrev.build(header); blockStore.put(newBlock, new StoredUndoableBlock(newBlock.getHeader().getHash(), txOutChanges)); return newBlock; }
@Override protected StoredBlock addToBlockStore(StoredBlock storedPrev, Block block) throws BlockStoreException, VerificationException { StoredBlock newBlock = storedPrev.build(block); blockStore.put( newBlock, new StoredUndoableBlock(newBlock.getHeader().getHash(), block.transactions)); return newBlock; }
@Override public View getView(final int position, final View convertView, final ViewGroup parent) { final ViewGroup row; if (convertView == null) row = (ViewGroup) getLayoutInflater(null).inflate(R.layout.block_row, null); else row = (ViewGroup) convertView; final StoredBlock storedBlock = getItem(position); final Block header = storedBlock.getHeader(); final TextView rowHeight = (TextView) row.findViewById(R.id.block_list_row_height); final int height = storedBlock.getHeight(); rowHeight.setText(Integer.toString(height)); final TextView rowTime = (TextView) row.findViewById(R.id.block_list_row_time); final long timeMs = header.getTimeSeconds() * DateUtils.SECOND_IN_MILLIS; rowTime.setText( DateUtils.getRelativeDateTimeString( activity, timeMs, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0)); final TextView rowHash = (TextView) row.findViewById(R.id.block_list_row_hash); rowHash.setText(WalletUtils.formatHash(null, header.getHashAsString(), 8, 0, ' ')); final int transactionChildCount = row.getChildCount() - ROW_BASE_CHILD_COUNT; int iTransactionView = 0; if (transactions != null) { final String precision = prefs.getString( Constants.PREFS_KEY_BTC_PRECISION, Constants.PREFS_DEFAULT_BTC_PRECISION); final int btcPrecision = precision.charAt(0) - '0'; final int btcShift = precision.length() == 3 ? precision.charAt(2) - '0' : 0; transactionsAdapter.setPrecision(btcPrecision, btcShift); for (final Transaction tx : transactions) { if (tx.getAppearsInHashes().containsKey(header.getHash())) { final View view; if (iTransactionView < transactionChildCount) { view = row.getChildAt(ROW_INSERT_INDEX + iTransactionView); } else { view = getLayoutInflater(null).inflate(R.layout.transaction_row_oneline, null); row.addView(view, ROW_INSERT_INDEX + iTransactionView); } transactionsAdapter.bindView(view, tx); iTransactionView++; } } } final int leftoverTransactionViews = transactionChildCount - iTransactionView; if (leftoverTransactionViews > 0) row.removeViews(ROW_INSERT_INDEX + iTransactionView, leftoverTransactionViews); return row; }
private synchronized boolean add(Block block, boolean tryConnecting) throws BlockStoreException, VerificationException, ScriptException { if (System.currentTimeMillis() - statsLastTime > 1000) { // More than a second passed since last stats logging. log.info("{} blocks per second", statsBlocksAdded); statsLastTime = System.currentTimeMillis(); statsBlocksAdded = 0; } // We check only the chain head for double adds here to avoid potentially expensive block chain // misses. if (block.equals(chainHead.getHeader())) { // Duplicate add of the block at the top of the chain, can be a natural artifact of the // download process. 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.error("Failed to verify block:", e); log.error(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.warn("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. StoredBlock newStoredBlock = storedPrev.build(block); checkDifficultyTransitions(storedPrev, newStoredBlock); 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(); statsBlocksAdded++; return true; }
/** * Returns the set of contiguous blocks between 'higher' and 'lower'. Higher is included, lower is * not. */ private List<StoredBlock> getPartialChain(StoredBlock higher, StoredBlock lower) throws BlockStoreException { assert higher.getHeight() > lower.getHeight(); LinkedList<StoredBlock> results = new LinkedList<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; }
public synchronized void put(StoredBlock block) throws BlockStoreException { try { Sha256Hash hash = block.getHeader().getHash(); assert blockMap.get(hash) == null : "Attempt to insert duplicate"; // Append to the end of the file. The other fields in StoredBlock will be recalculated when // it's reloaded. byte[] bytes = block.getHeader().bitcoinSerialize(); stream.write(bytes); stream.flush(); blockMap.put(hash, block); } catch (IOException e) { throw new BlockStoreException(e); } }
public synchronized void setChainHead(StoredBlock chainHead) throws BlockStoreException { try { this.chainHead = chainHead.getHeader().getHash(); // Write out new hash to the first 32 bytes of the file past one (first byte is version // number). stream.getChannel().write(ByteBuffer.wrap(this.chainHead.getBytes()), 1); } catch (IOException e) { throw new BlockStoreException(e); } }
/** * This is broken for blocks that do not pass BIP30, so all BIP30-failing blocks which are allowed * to fail BIP30 must be checkpointed. */ @Override protected void disconnectTransactions(StoredBlock oldBlock) throws PrunedException, BlockStoreException { checkState(lock.isHeldByCurrentThread()); blockStore.beginDatabaseBatchWrite(); try { StoredUndoableBlock undoBlock = blockStore.getUndoBlock(oldBlock.getHeader().getHash()); if (undoBlock == null) throw new PrunedException(oldBlock.getHeader().getHash()); TransactionOutputChanges txOutChanges = undoBlock.getTxOutChanges(); for (StoredTransactionOutput out : txOutChanges.txOutsSpent) blockStore.addUnspentTransactionOutput(out); for (StoredTransactionOutput out : txOutChanges.txOutsCreated) blockStore.removeUnspentTransactionOutput(out); } catch (PrunedException e) { blockStore.abortDatabaseBatchWrite(); throw e; } catch (BlockStoreException e) { blockStore.abortDatabaseBatchWrite(); throw e; } }
/** * 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 BoundedOverheadBlockStore} 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.info("chain head is:\n{}", chainHead.getHeader()); } catch (BlockStoreException e) { throw new RuntimeException(e); } this.params = params; this.wallet = wallet; }
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.trace("Chain is now {} blocks high", chainHead.getHeight()); 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 haveNewBestChain = newStoredBlock.moreWorkThan(chainHead); if (haveNewBestChain) { log.info("Block is causing a re-organize"); } else { StoredBlock splitPoint = findSplit(newStoredBlock, chainHead); String splitPointHash = splitPoint != null ? splitPoint.getHeader().getHashAsString() : "?"; log.info( "Block forks the chain at {}, but it did not cause a reorganize:\n{}", splitPointHash, newStoredBlock); } // 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 (haveNewBestChain) handleNewBestChain(newStoredBlock); } }
/** * Called as part of connecting a block when the new block results in a different chain having * higher total work. */ private void handleNewBestChain(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.info("Re-organize after split at height {}", splitPoint.getHeight()); log.info("Old chain head: {}", chainHead.getHeader().getHashAsString()); log.info("New chain head: {}", newChainHead.getHeader().getHashAsString()); log.info("Split at block: {}", splitPoint.getHeader().getHashAsString()); // Then build a list of all blocks in the old part of the chain and the new part. List<StoredBlock> oldBlocks = getPartialChain(chainHead, splitPoint); List<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); }
private void createNewStore(NetworkParameters params, File file) throws BlockStoreException { // Create a new block store if the file wasn't found or anything went wrong whilst reading. blockMap.clear(); try { stream = new FileOutputStream(file, false); // Do not append, create fresh. stream.write(1); // Version. } catch (IOException e1) { // We could not load a block store nor could we create a new one! throw new BlockStoreException(e1); } try { // Set up the genesis block. When we start out fresh, it is by definition the top of the // chain. Block genesis = params.genesisBlock.cloneAsHeader(); StoredBlock storedGenesis = new StoredBlock(genesis, genesis.getWork(), 0); this.chainHead = storedGenesis.getHeader().getHash(); stream.write(this.chainHead.getBytes()); put(storedGenesis); } catch (VerificationException e1) { throw new RuntimeException(e1); // Cannot happen. } catch (IOException e) { throw new BlockStoreException(e); } }
/** * 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)) { if (currentChainCursor.getHeight() > newChainCursor.getHeight()) { currentChainCursor = currentChainCursor.getPrev(blockStore); assert currentChainCursor != null : "Attempt to follow an orphan chain"; } else { newChainCursor = newChainCursor.getPrev(blockStore); assert newChainCursor != null : "Attempt to follow an orphan chain"; } } return currentChainCursor; }
@Test public void testStorage() throws Exception { File temp = File.createTempFile("bitcoinj-test", null, null); System.out.println(temp.getAbsolutePath()); temp.deleteOnExit(); NetworkParameters params = NetworkParameters.unitTests(); Address to = new ECKey().toAddress(params); BoundedOverheadBlockStore store = new BoundedOverheadBlockStore(params, temp); // Check the first block in a new store is the genesis block. StoredBlock genesis = store.getChainHead(); assertEquals(params.genesisBlock, genesis.getHeader()); // Build a new block. StoredBlock b1 = genesis.build(genesis.getHeader().createNextBlock(to).cloneAsHeader()); store.put(b1); store.setChainHead(b1); // Check we can get it back out again if we rebuild the store object. store = new BoundedOverheadBlockStore(params, temp); StoredBlock b2 = store.get(b1.getHeader().getHash()); assertEquals(b1, b2); // Check the chain head was stored correctly also. assertEquals(b1, store.getChainHead()); }
@Override /** Used during reorgs to connect a block previously on a fork */ protected synchronized TransactionOutputChanges connectTransactions(StoredBlock newBlock) throws VerificationException, BlockStoreException, PrunedException { checkState(lock.isHeldByCurrentThread()); if (!params.passesCheckpoint(newBlock.getHeight(), newBlock.getHeader().getHash())) throw new VerificationException("Block failed checkpoint lockin at " + newBlock.getHeight()); blockStore.beginDatabaseBatchWrite(); StoredUndoableBlock block = blockStore.getUndoBlock(newBlock.getHeader().getHash()); if (block == null) { // We're trying to re-org too deep and the data needed has been deleted. blockStore.abortDatabaseBatchWrite(); throw new PrunedException(newBlock.getHeader().getHash()); } TransactionOutputChanges txOutChanges; try { List<Transaction> transactions = block.getTransactions(); if (transactions != null) { LinkedList<StoredTransactionOutput> txOutsSpent = new LinkedList<StoredTransactionOutput>(); LinkedList<StoredTransactionOutput> txOutsCreated = new LinkedList<StoredTransactionOutput>(); long sigOps = 0; final boolean enforcePayToScriptHash = newBlock.getHeader().getTimeSeconds() >= NetworkParameters.BIP16_ENFORCE_TIME; if (!params.isCheckpoint(newBlock.getHeight())) { for (Transaction tx : transactions) { Sha256Hash hash = tx.getHash(); if (blockStore.hasUnspentOutputs(hash, tx.getOutputs().size())) throw new VerificationException("Block failed BIP30 test!"); } } Coin totalFees = Coin.ZERO; Coin coinbaseValue = null; if (scriptVerificationExecutor.isShutdown()) scriptVerificationExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); List<Future<VerificationException>> listScriptVerificationResults = new ArrayList<Future<VerificationException>>(transactions.size()); for (final Transaction tx : transactions) { boolean isCoinBase = tx.isCoinBase(); Coin valueIn = Coin.ZERO; Coin valueOut = Coin.ZERO; final List<Script> prevOutScripts = new LinkedList<Script>(); if (!isCoinBase) { for (int index = 0; index < tx.getInputs().size(); index++) { final TransactionInput in = tx.getInputs().get(index); final StoredTransactionOutput prevOut = blockStore.getTransactionOutput( in.getOutpoint().getHash(), in.getOutpoint().getIndex()); if (prevOut == null) throw new VerificationException( "Attempted spend of a non-existent or already spent output!"); if (newBlock.getHeight() - prevOut.getHeight() < params.getSpendableCoinbaseDepth()) throw new VerificationException( "Tried to spend coinbase at depth " + (newBlock.getHeight() - prevOut.getHeight())); valueIn = valueIn.add(prevOut.getValue()); if (enforcePayToScriptHash) { Script script = new Script(prevOut.getScriptBytes()); if (script.isPayToScriptHash()) sigOps += Script.getP2SHSigOpCount(in.getScriptBytes()); if (sigOps > Block.MAX_BLOCK_SIGOPS) throw new VerificationException("Too many P2SH SigOps in block"); } prevOutScripts.add(new Script(prevOut.getScriptBytes())); blockStore.removeUnspentTransactionOutput(prevOut); txOutsSpent.add(prevOut); } } Sha256Hash hash = tx.getHash(); for (TransactionOutput out : tx.getOutputs()) { valueOut = valueOut.add(out.getValue()); StoredTransactionOutput newOut = new StoredTransactionOutput( hash, out.getIndex(), out.getValue(), newBlock.getHeight(), isCoinBase, out.getScriptBytes()); blockStore.addUnspentTransactionOutput(newOut); txOutsCreated.add(newOut); } // All values were already checked for being non-negative (as it is verified in // Transaction.verify()) // but we check again here just for defence in depth. Transactions with zero output value // are OK. if (valueOut.signum() < 0 || valueOut.compareTo(NetworkParameters.MAX_MONEY) > 0) throw new VerificationException("Transaction output value out of range"); if (isCoinBase) { coinbaseValue = valueOut; } else { if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(NetworkParameters.MAX_MONEY) > 0) throw new VerificationException("Transaction input value out of range"); totalFees = totalFees.add(valueIn.subtract(valueOut)); } if (!isCoinBase) { // Because correctlySpends modifies transactions, this must come after we are done with // tx FutureTask<VerificationException> future = new FutureTask<VerificationException>( new Verifier(tx, prevOutScripts, enforcePayToScriptHash)); scriptVerificationExecutor.execute(future); listScriptVerificationResults.add(future); } } if (totalFees.compareTo(NetworkParameters.MAX_MONEY) > 0 || newBlock .getHeader() .getBlockInflation(newBlock.getHeight()) .add(totalFees) .compareTo(coinbaseValue) < 0) throw new VerificationException("Transaction fees out of range"); txOutChanges = new TransactionOutputChanges(txOutsCreated, txOutsSpent); for (Future<VerificationException> future : listScriptVerificationResults) { VerificationException e; try { e = future.get(); } catch (InterruptedException thrownE) { throw new RuntimeException(thrownE); // Shouldn't happen } catch (ExecutionException thrownE) { log.error("Script.correctlySpends threw a non-normal exception: " + thrownE.getCause()); throw new VerificationException( "Bug in Script.correctlySpends, likely script malformed in some new and interesting way.", thrownE); } if (e != null) throw e; } } else { txOutChanges = block.getTxOutChanges(); if (!params.isCheckpoint(newBlock.getHeight())) for (StoredTransactionOutput out : txOutChanges.txOutsCreated) { Sha256Hash hash = out.getHash(); if (blockStore.getTransactionOutput(hash, out.getIndex()) != null) throw new VerificationException("Block failed BIP30 test!"); } for (StoredTransactionOutput out : txOutChanges.txOutsCreated) blockStore.addUnspentTransactionOutput(out); for (StoredTransactionOutput out : txOutChanges.txOutsSpent) blockStore.removeUnspentTransactionOutput(out); } } catch (VerificationException e) { scriptVerificationExecutor.shutdownNow(); blockStore.abortDatabaseBatchWrite(); throw e; } catch (BlockStoreException e) { scriptVerificationExecutor.shutdownNow(); blockStore.abortDatabaseBatchWrite(); throw e; } return txOutChanges; }
public static void main(String[] args) throws Exception { BriefLogFormatter.init(); // Sorted map of UNIX time of block to StoredBlock object. final TreeMap<Integer, StoredBlock> checkpoints = new TreeMap<Integer, StoredBlock>(); // Configure bitcoinj to fetch only headers, not save them to disk, connect to a local fully // synced/validated // node and to save block headers that are on interval boundaries, as long as they are <1 month // old. final BlockStore store = new MemoryBlockStore(PARAMS); final BlockChain chain = new BlockChain(PARAMS, store); final PeerGroup peerGroup = new PeerGroup(PARAMS, chain); peerGroup.addAddress(InetAddress.getLocalHost()); // peerGroup.addAddress(InetAddress.getByName("69.164.198.161")); long now = new Date().getTime() / 1000; peerGroup.setFastCatchupTimeSecs(now); final long oneMonthAgo = now - (86400 * 2); chain.addListener( new AbstractBlockChainListener() { @Override public void notifyNewBestBlock(StoredBlock block) throws VerificationException { int height = block.getHeight(); if (height % CoinDefinition.getIntervalCheckpoints() == 0 && block.getHeader().getTimeSeconds() <= oneMonthAgo) { // if (height % PARAMS.getInterval() == 0 && // block.getHeader().getTimeSeconds() <= oneMonthAgo) { System.out.println( String.format( "Checkpointing block %s at height %d", block.getHeader().getHash(), block.getHeight())); checkpoints.put(height, block); } } }, Threading.SAME_THREAD); peerGroup.startAndWait(); peerGroup.downloadBlockChain(); checkState(checkpoints.size() > 0); // Write checkpoint data out. final FileOutputStream fileOutputStream = new FileOutputStream(CHECKPOINTS_FILE, false); MessageDigest digest = MessageDigest.getInstance("SHA-256"); final DigestOutputStream digestOutputStream = new DigestOutputStream(fileOutputStream, digest); digestOutputStream.on(false); final DataOutputStream dataOutputStream = new DataOutputStream(digestOutputStream); dataOutputStream.writeBytes("CHECKPOINTS 1"); dataOutputStream.writeInt(0); // Number of signatures to read. Do this later. digestOutputStream.on(true); dataOutputStream.writeInt(checkpoints.size()); ByteBuffer buffer = ByteBuffer.allocate(StoredBlock.COMPACT_SERIALIZED_SIZE); for (StoredBlock block : checkpoints.values()) { block.serializeCompact(buffer); dataOutputStream.write(buffer.array()); buffer.position(0); } dataOutputStream.close(); Sha256Hash checkpointsHash = new Sha256Hash(digest.digest()); System.out.println("Hash of checkpoints data is " + checkpointsHash); digestOutputStream.close(); fileOutputStream.close(); peerGroup.stopAndWait(); store.close(); // Sanity check the created file. CheckpointManager manager = new CheckpointManager(PARAMS, new FileInputStream(CHECKPOINTS_FILE)); checkState(manager.numCheckpoints() == checkpoints.size()); /*if (PARAMS.getId() == NetworkParameters.ID_MAINNET) { StoredBlock test = manager.getCheckpointBefore(1390500000); // Thu Jan 23 19:00:00 CET 2014 checkState(test.getHeight() == 280224); checkState(test.getHeader().getHashAsString() .equals("00000000000000000b5d59a15f831e1c45cb688a4db6b0a60054d49a9997fa34")); } else if (PARAMS.getId() == NetworkParameters.ID_TESTNET) { StoredBlock test = manager.getCheckpointBefore(1390500000); // Thu Jan 23 19:00:00 CET 2014 checkState(test.getHeight() == 167328); checkState(test.getHeader().getHashAsString() .equals("0000000000035ae7d5025c2538067fe7adb1cf5d5d9c31b024137d9090ed13a9")); }*/ System.out.println("Checkpoints written to '" + CHECKPOINTS_FILE.getCanonicalPath() + "'."); }
private void load(File file) throws IOException, BlockStoreException { log.info("Reading block store from {}", file); InputStream input = null; try { input = new BufferedInputStream(new FileInputStream(file)); // Read a version byte. int version = input.read(); if (version == -1) { // No such file or the file was empty. throw new FileNotFoundException(file.getName() + " does not exist or is empty"); } if (version != 1) { throw new BlockStoreException("Bad version number: " + version); } // Chain head pointer is the first thing in the file. byte[] chainHeadHash = new byte[32]; if (input.read(chainHeadHash) < chainHeadHash.length) throw new BlockStoreException("Truncated block store: cannot read chain head hash"); this.chainHead = new Sha256Hash(chainHeadHash); log.info("Read chain head from disk: {}", this.chainHead); long now = System.currentTimeMillis(); // Rest of file is raw block headers. byte[] headerBytes = new byte[Block.HEADER_SIZE]; try { while (true) { // Read a block from disk. if (input.read(headerBytes) < 80) { // End of file. break; } // Parse it. Block b = new Block(params, headerBytes); // Look up the previous block it connects to. StoredBlock prev = get(b.getPrevBlockHash()); StoredBlock s; if (prev == null) { // First block in the stored chain has to be treated specially. if (b.equals(params.genesisBlock)) { s = new StoredBlock( params.genesisBlock.cloneAsHeader(), params.genesisBlock.getWork(), 0); } else { throw new BlockStoreException( "Could not connect " + b.getHash().toString() + " to " + b.getPrevBlockHash().toString()); } } else { // Don't try to verify the genesis block to avoid upsetting the unit tests. b.verifyHeader(); // Calculate its height and total chain work. s = prev.build(b); } // Save in memory. blockMap.put(b.getHash(), s); } } catch (ProtocolException e) { // Corrupted file. throw new BlockStoreException(e); } catch (VerificationException e) { // Should not be able to happen unless the file contains bad blocks. throw new BlockStoreException(e); } long elapsed = System.currentTimeMillis() - now; log.info("Block chain read complete in {}ms", elapsed); } finally { if (input != null) input.close(); } }
/** Throws an exception if the blocks difficulty is not correct. */ private void checkDifficultyTransitions(StoredBlock storedPrev, StoredBlock storedNext) throws BlockStoreException, VerificationException { Block prev = storedPrev.getHeader(); Block next = storedNext.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. long now = System.currentTimeMillis(); 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()); } log.info("Difficulty transition traversal took {}msec", System.currentTimeMillis() - now); 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.warn("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)); }