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);
}
});