diff --git a/src/com/google/bitcoin/core/BlockChain.java b/src/com/google/bitcoin/core/BlockChain.java index 83d9e3c8..b17a6e0d 100644 --- a/src/com/google/bitcoin/core/BlockChain.java +++ b/src/com/google/bitcoin/core/BlockChain.java @@ -52,7 +52,7 @@ public class BlockChain { private static final Logger log = LoggerFactory.getLogger(BlockChain.class); /** Keeps a map of block hashes to StoredBlocks. */ - protected BlockStore blockStore; + protected final BlockStore blockStore; /** * Tracks the top of the best known chain.

@@ -63,6 +63,10 @@ public class BlockChain { * potentially invalidating transactions in our wallet. */ protected StoredBlock chainHead; + // chainHead is accessed under this lock rather than the BlockChain lock. This is to try and keep accessors + // responsive whilst the chain is downloading, without free-threading the entire chain add process (which could + // get very confusing in the case of multiple blocks being added simultaneously). + protected final Object chainHeadLock = new Object(); protected final NetworkParameters params; protected final List wallets; @@ -134,9 +138,15 @@ public class BlockChain { private synchronized boolean add(Block block, boolean tryConnecting) throws BlockStoreException, VerificationException, ScriptException { + // Note on locking: this method runs with the block chain locked. All mutations to the chain are serialized. + // This has the undesirable consequence that during block chain download, it's slow to read the current chain + // head and other chain info because the accessors are constantly waiting for the chain to become free. To + // solve this things viewable via accessors must use fine-grained locking as well as being mutated under the + // chain lock. if (System.currentTimeMillis() - statsLastTime > 1000) { // More than a second passed since last stats logging. - log.info("{} blocks per second", statsBlocksAdded); + if (statsBlocksAdded > 1) + log.info("{} blocks per second", statsBlocksAdded); statsLastTime = System.currentTimeMillis(); statsBlocksAdded = 0; } @@ -331,7 +341,9 @@ public class BlockChain { private void setChainHead(StoredBlock chainHead) throws BlockStoreException { blockStore.setChainHead(chainHead); - this.chainHead = chainHead; + synchronized (chainHeadLock) { + this.chainHead = chainHead; + } } /** @@ -457,14 +469,17 @@ public class BlockChain { * 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; + public StoredBlock getChainHead() { + synchronized (chainHeadLock) { + return chainHead; + } } /** - * Returns the most recent unconnected block or null if there are none. This will all have to change. + * Returns the most recent unconnected block or null if there are none. This will all have to change. It's used + * only in processing of inv messages. */ - public synchronized Block getUnconnectedBlock() { + synchronized Block getUnconnectedBlock() { if (unconnectedBlocks.size() == 0) return null; return unconnectedBlocks.get(unconnectedBlocks.size() - 1); diff --git a/src/com/google/bitcoin/core/Peer.java b/src/com/google/bitcoin/core/Peer.java index 839a34be..2b60f208 100644 --- a/src/com/google/bitcoin/core/Peer.java +++ b/src/com/google/bitcoin/core/Peer.java @@ -296,7 +296,7 @@ public class Peer { } private void processBlock(Block m) throws IOException { - log.info("Received broadcast block {}", m.getHashAsString()); + log.trace("Received broadcast block {}", m.getHashAsString()); try { // Was this block requested by getBlock()? synchronized (pendingGetBlockFutures) { @@ -352,7 +352,7 @@ public class Peer { if (!downloadData) return; - // The peer told us about some blocks or transactions they have. For now we only care about blocks. + // The peer told us about some blocks or transactions they have. Block topBlock = blockChain.getUnconnectedBlock(); Sha256Hash topHash = (topBlock != null ? topBlock.getHash() : null); if (isNewBlockTickle(topHash, items)) { diff --git a/src/com/google/bitcoin/examples/toywallet/ToyWallet.java b/src/com/google/bitcoin/examples/toywallet/ToyWallet.java index 36f4e3fc..3e024ac9 100644 --- a/src/com/google/bitcoin/examples/toywallet/ToyWallet.java +++ b/src/com/google/bitcoin/examples/toywallet/ToyWallet.java @@ -120,22 +120,19 @@ public class ToyWallet { @Override public void onPeerConnected(Peer peer, int peerCount) { super.onPeerConnected(peer, peerCount); - triggerNetworkStatsUpdate(null, -1); + triggerNetworkStatsUpdate(); } @Override public void onPeerDisconnected(Peer peer, int peerCount) { super.onPeerDisconnected(peer, peerCount); - triggerNetworkStatsUpdate(null, -1); + triggerNetworkStatsUpdate(); } @Override public void onBlocksDownloaded(Peer peer, Block block, int blocksLeft) { super.onBlocksDownloaded(peer, block, blocksLeft); - // Calculate chain height on this thread to avoid the UI and peer threads contending on the chain. - int chainHeight = chain.getBestChainHeight(); - String blockDate = block.getTime().toString(); - triggerNetworkStatsUpdate(blockDate, chainHeight); + triggerNetworkStatsUpdate(); handleNewBlock(); } }); @@ -205,19 +202,15 @@ public class ToyWallet { }); } - private String blockDate; - private int chainHeight; - private void triggerNetworkStatsUpdate(String blockDate, int chainHeight) { - // Running on a peer thread, switch to Swing thread before updating the peer count label. - if (blockDate != null) this.blockDate = blockDate; - if (chainHeight != -1) this.chainHeight = chainHeight; - + private void triggerNetworkStatsUpdate() { SwingUtilities.invokeLater(new Runnable() { public void run() { int numPeers = peerGroup.numConnectedPeers(); + StoredBlock chainHead = chain.getChainHead(); + String date = chainHead.getHeader().getTime().toString(); String plural = numPeers > 1 ? "peers" : "peer"; String status = String.format("%d %s connected. %d blocks: %s", - numPeers, plural, ToyWallet.this.chainHeight, ToyWallet.this.blockDate); + numPeers, plural, chainHead.getHeight(), date); networkStats.setText(status); } });