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; }
public void flush() { Iterator<BlockPayload> iterator = dirty.values().iterator(); while (iterator.hasNext()) { BlockPayload block = iterator.next(); iterator.remove(); store.write(block); } store.flush(); }
/** * 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.info("Connected {} floating blocks.", blocksConnectedThisRound); } } while (blocksConnectedThisRound > 0); }
private void setChainHead(StoredBlock chainHead) { this.chainHead = chainHead; try { blockStore.setChainHead(chainHead); } catch (BlockStoreException e) { throw new RuntimeException(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; }
public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) { T block = payloadType.cast(dirty.get(pos)); if (block != null) { return block; } block = payloadType.cast(indexBlockCache.get(pos)); if (block != null) { return block; } block = store.read(pos, payloadType); maybeCache(block); return block; }
public void write(BlockPayload block) { store.attach(block); maybeCache(block); dirty.put(block.getPos(), block); }
public <T extends BlockPayload> T readFirst(Class<T> payloadType) { T block = store.readFirst(payloadType); maybeCache(block); return block; }
public void remove(BlockPayload block) { dirty.remove(block.getPos()); indexBlockCache.remove(block.getPos()); store.remove(block); }
public void attach(BlockPayload block) { store.attach(block); }
public void clear() { dirty.clear(); indexBlockCache.clear(); store.clear(); }
public void close() { flush(); indexBlockCache.clear(); store.close(); }
public void open(Runnable initAction, Factory factory) { store.open(initAction, factory); }
/** 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)); }