3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-07 23:03:04 +00:00

Convert block chain to use a cycle detecting lock.

This commit is contained in:
Mike Hearn 2013-03-07 15:57:50 +01:00
parent 9d87997736
commit 0221b71418
2 changed files with 146 additions and 125 deletions

View File

@ -18,12 +18,15 @@ package com.google.bitcoin.core;
import com.google.bitcoin.store.BlockStore; import com.google.bitcoin.store.BlockStore;
import com.google.bitcoin.store.BlockStoreException; import com.google.bitcoin.store.BlockStoreException;
import com.google.bitcoin.utils.Locks;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.*; import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
import static com.google.common.base.Preconditions.*; import static com.google.common.base.Preconditions.*;
@ -72,6 +75,7 @@ import static com.google.common.base.Preconditions.*;
*/ */
public abstract class AbstractBlockChain { public abstract class AbstractBlockChain {
private static final Logger log = LoggerFactory.getLogger(AbstractBlockChain.class); private static final Logger log = LoggerFactory.getLogger(AbstractBlockChain.class);
protected ReentrantLock lock = Locks.lock("blockchain");
/** Keeps a map of block hashes to StoredBlocks. */ /** Keeps a map of block hashes to StoredBlocks. */
private final BlockStore blockStore; private final BlockStore blockStore;
@ -86,6 +90,7 @@ public abstract class AbstractBlockChain {
*/ */
protected StoredBlock chainHead; protected StoredBlock chainHead;
// TODO: Scrap this and use a proper read/write for all of the block chain objects.
// The chainHead field is read/written synchronized with this object rather than BlockChain. However writing is // The chainHead field is read/written synchronized with this object rather than BlockChain. However writing is
// also guaranteed to happen whilst BlockChain is synchronized (see setChainHead). The goal of this is to let // also guaranteed to happen whilst BlockChain is synchronized (see setChainHead). The goal of this is to let
// clients quickly access the chain head even whilst the block chain is downloading and thus the BlockChain is // clients quickly access the chain head even whilst the block chain is downloading and thus the BlockChain is
@ -93,7 +98,7 @@ public abstract class AbstractBlockChain {
private final Object chainHeadLock = new Object(); private final Object chainHeadLock = new Object();
protected final NetworkParameters params; protected final NetworkParameters params;
private final List<BlockChainListener> listeners; private final CopyOnWriteArrayList<BlockChainListener> listeners;
// Holds a block header and, optionally, a list of tx hashes or block's transactions // Holds a block header and, optionally, a list of tx hashes or block's transactions
protected static class OrphanBlock { protected static class OrphanBlock {
@ -116,12 +121,12 @@ public abstract class AbstractBlockChain {
* Constructs a BlockChain connected to the given list of listeners (eg, wallets) and a store. * Constructs a BlockChain connected to the given list of listeners (eg, wallets) and a store.
*/ */
public AbstractBlockChain(NetworkParameters params, List<BlockChainListener> listeners, public AbstractBlockChain(NetworkParameters params, List<BlockChainListener> listeners,
BlockStore blockStore) throws BlockStoreException { BlockStore blockStore) throws BlockStoreException {
this.blockStore = blockStore; this.blockStore = blockStore;
chainHead = blockStore.getChainHead(); chainHead = blockStore.getChainHead();
log.info("chain head is at height {}:\n{}", chainHead.getHeight(), chainHead.getHeader()); log.info("chain head is at height {}:\n{}", chainHead.getHeight(), chainHead.getHeader());
this.params = params; this.params = params;
this.listeners = new ArrayList<BlockChainListener>(listeners); this.listeners = new CopyOnWriteArrayList<BlockChainListener>(listeners);
} }
/** /**
@ -130,21 +135,21 @@ public abstract class AbstractBlockChain {
* have never been in use, or if the wallet has been loaded along with the BlockChain. Note that adding multiple * have never been in use, or if the wallet has been loaded along with the BlockChain. Note that adding multiple
* wallets is not well tested! * wallets is not well tested!
*/ */
public synchronized void addWallet(Wallet wallet) { public void addWallet(Wallet wallet) {
listeners.add(wallet); listeners.add(wallet);
} }
/** /**
* Adds a generic {@link BlockChainListener} listener to the chain. * Adds a generic {@link BlockChainListener} listener to the chain.
*/ */
public synchronized void addListener(BlockChainListener listener) { public void addListener(BlockChainListener listener) {
listeners.add(listener); listeners.add(listener);
} }
/** /**
* Removes the given {@link BlockChainListener} from the chain. * Removes the given {@link BlockChainListener} from the chain.
*/ */
public synchronized void removeListener(BlockChainListener listener) { public void removeListener(BlockChainListener listener) {
listeners.remove(listener); listeners.remove(listener);
} }
@ -203,7 +208,7 @@ public abstract class AbstractBlockChain {
* exception is thrown. If the block is OK but cannot be connected to the chain at this time, returns false. * exception is thrown. If the block is OK but cannot be connected to the chain at this time, returns false.
* If the block can be connected to the chain, returns true. * If the block can be connected to the chain, returns true.
*/ */
public synchronized boolean add(Block block) throws VerificationException, PrunedException { public boolean add(Block block) throws VerificationException, PrunedException {
try { try {
return add(block, null, null, true); return add(block, null, null, true);
} catch (BlockStoreException e) { } catch (BlockStoreException e) {
@ -225,12 +230,12 @@ public abstract class AbstractBlockChain {
* exception is thrown. If the block is OK but cannot be connected to the chain at this time, returns false. * exception is thrown. If the block is OK but cannot be connected to the chain at this time, returns false.
* If the block can be connected to the chain, returns true. * If the block can be connected to the chain, returns true.
*/ */
public synchronized boolean add(FilteredBlock block) throws VerificationException, PrunedException { public boolean add(FilteredBlock block) throws VerificationException, PrunedException {
try { try {
Set<Sha256Hash> filteredTxnHashSet = new HashSet<Sha256Hash>(block.getTransactionHashes()); Set<Sha256Hash> filteredTxnHashSet = new HashSet<Sha256Hash>(block.getTransactionHashes());
List<Transaction> filteredTxn = block.getAssociatedTransactions(); List<Transaction> filteredTxn = block.getAssociatedTransactions();
for (Transaction tx : filteredTxn) { for (Transaction tx : filteredTxn) {
Preconditions.checkState(filteredTxnHashSet.remove(tx.getHash())); checkState(filteredTxnHashSet.remove(tx.getHash()));
} }
return add(block.getBlockHeader(), filteredTxnHashSet, filteredTxn, true); return add(block.getBlockHeader(), filteredTxnHashSet, filteredTxn, true);
} catch (BlockStoreException e) { } catch (BlockStoreException e) {
@ -280,88 +285,90 @@ public abstract class AbstractBlockChain {
private long statsBlocksAdded; private long statsBlocksAdded;
// filteredTxHashList and filteredTxn[i].GetHash() should be mutually exclusive // filteredTxHashList and filteredTxn[i].GetHash() should be mutually exclusive
private synchronized boolean add(Block block, Set<Sha256Hash> filteredTxHashList, List<Transaction> filteredTxn, boolean tryConnecting) private boolean add(Block block, Set<Sha256Hash> filteredTxHashList, List<Transaction> filteredTxn, boolean tryConnecting)
throws BlockStoreException, VerificationException, PrunedException { throws BlockStoreException, VerificationException, PrunedException {
// Note on locking: this method runs with the block chain locked. All mutations to the chain are serialized. lock.lock();
// 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.
if (statsBlocksAdded > 1)
log.info("{} blocks per second", statsBlocksAdded);
statsLastTime = System.currentTimeMillis();
statsBlocksAdded = 0;
}
// Quick check for duplicates to avoid an expensive check further down (in findSplit). This can happen a lot
// when connecting orphan transactions due to the dumb brute force algorithm we use.
if (block.equals(getChainHead().getHeader())) {
return true;
}
if (tryConnecting && orphanBlocks.containsKey(block.getHash())) {
return false;
}
// If we want to verify transactions (ie we are running with full blocks), verify that block has transactions
if (shouldVerifyTransactions() && block.transactions == null)
throw new VerificationException("Got a block header while running in full-block mode");
// Does this block contain any transactions we might care about? Check this up front before verifying the
// blocks validity so we can skip the merkle root verification if the contents aren't interesting. This saves
// a lot of time for big blocks.
boolean contentsImportant = shouldVerifyTransactions();
if (block.transactions != null) {
contentsImportant = contentsImportant || containsRelevantTransactions(block);
}
// Prove the block is internally valid: hash is lower than target, etc. This only checks the block contents
// if there is a tx sending or receiving coins using an address in one of our wallets. And those transactions
// are only lightly verified: presence in a valid connecting block is taken as proof of validity. See the
// article here for more details: http://code.google.com/p/bitcoinj/wiki/SecurityModel
try { try {
block.verifyHeader(); // TODO: Use read/write locks to ensure that during chain download properties are still low latency.
if (contentsImportant) if (System.currentTimeMillis() - statsLastTime > 1000) {
block.verifyTransactions(); // More than a second passed since last stats logging.
} catch (VerificationException e) { if (statsBlocksAdded > 1)
log.error("Failed to verify block: ", e); log.info("{} blocks per second", statsBlocksAdded);
log.error(block.getHashAsString()); statsLastTime = System.currentTimeMillis();
throw e; statsBlocksAdded = 0;
}
// Quick check for duplicates to avoid an expensive check further down (in findSplit). This can happen a lot
// when connecting orphan transactions due to the dumb brute force algorithm we use.
if (block.equals(getChainHead().getHeader())) {
return true;
}
if (tryConnecting && orphanBlocks.containsKey(block.getHash())) {
return false;
}
// If we want to verify transactions (ie we are running with full blocks), verify that block has transactions
if (shouldVerifyTransactions() && block.transactions == null)
throw new VerificationException("Got a block header while running in full-block mode");
// Does this block contain any transactions we might care about? Check this up front before verifying the
// blocks validity so we can skip the merkle root verification if the contents aren't interesting. This saves
// a lot of time for big blocks.
boolean contentsImportant = shouldVerifyTransactions();
if (block.transactions != null) {
contentsImportant = contentsImportant || containsRelevantTransactions(block);
}
// Prove the block is internally valid: hash is lower than target, etc. This only checks the block contents
// if there is a tx sending or receiving coins using an address in one of our wallets. And those transactions
// are only lightly verified: presence in a valid connecting block is taken as proof of validity. See the
// article here for more details: http://code.google.com/p/bitcoinj/wiki/SecurityModel
try {
block.verifyHeader();
if (contentsImportant)
block.verifyTransactions();
} catch (VerificationException e) {
log.error("Failed to verify block: ", e);
log.error(block.getHashAsString());
throw e;
}
// Try linking it to a place in the currently known blocks.
StoredBlock storedPrev = getStoredBlockInCurrentScope(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.
checkState(tryConnecting, "bug in tryConnectingOrphans");
log.warn("Block does not connect: {} prev {}", block.getHashAsString(), block.getPrevBlockHash());
orphanBlocks.put(block.getHash(), new OrphanBlock(block, filteredTxHashList, filteredTxn));
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.
checkDifficultyTransitions(storedPrev, block);
connectBlock(block, storedPrev, shouldVerifyTransactions(), filteredTxHashList, filteredTxn);
}
if (tryConnecting)
tryConnectingOrphans();
statsBlocksAdded++;
return true;
} finally {
lock.unlock();
} }
// Try linking it to a place in the currently known blocks.
StoredBlock storedPrev = getStoredBlockInCurrentScope(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.
checkState(tryConnecting, "bug in tryConnectingOrphans");
log.warn("Block does not connect: {} prev {}", block.getHashAsString(), block.getPrevBlockHash());
orphanBlocks.put(block.getHash(), new OrphanBlock(block, filteredTxHashList, filteredTxn));
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.
checkDifficultyTransitions(storedPrev, block);
connectBlock(block, storedPrev, shouldVerifyTransactions(), filteredTxHashList, filteredTxn);
}
if (tryConnecting)
tryConnectingOrphans();
statsBlocksAdded++;
return true;
} }
// expensiveChecks enables checks that require looking at blocks further back in the chain // expensiveChecks enables checks that require looking at blocks further back in the chain
// than the previous one when connecting (eg median timestamp check) // than the previous one when connecting (eg median timestamp check)
// It could be exposed, but for now we just set it to shouldVerifyTransactions() // It could be exposed, but for now we just set it to shouldVerifyTransactions()
private void connectBlock(Block block, StoredBlock storedPrev, boolean expensiveChecks, private void connectBlock(Block block, StoredBlock storedPrev, boolean expensiveChecks,
Set<Sha256Hash> filteredTxHashList, List<Transaction> filteredTxn) Set<Sha256Hash> filteredTxHashList, List<Transaction> filteredTxn)
throws BlockStoreException, VerificationException, PrunedException { throws BlockStoreException, VerificationException, PrunedException {
checkState(lock.isLocked());
// Check that we aren't connecting a block that fails a checkpoint check // Check that we aren't connecting a block that fails a checkpoint check
if (!params.passesCheckpoint(storedPrev.getHeight() + 1, block.getHash())) if (!params.passesCheckpoint(storedPrev.getHeight() + 1, block.getHash()))
throw new VerificationException("Block failed checkpoint lockin at " + (storedPrev.getHeight() + 1)); throw new VerificationException("Block failed checkpoint lockin at " + (storedPrev.getHeight() + 1));
@ -372,7 +379,7 @@ public abstract class AbstractBlockChain {
StoredBlock head = getChainHead(); StoredBlock head = getChainHead();
if (storedPrev.equals(head)) { if (storedPrev.equals(head)) {
if (expensiveChecks && block.getTimeSeconds() <= getMedianTimestampOfRecentBlocks(head)) if (expensiveChecks && block.getTimeSeconds() <= getMedianTimestampOfRecentBlocks(head, blockStore))
throw new VerificationException("Block's timestamp is too early"); throw new VerificationException("Block's timestamp is too early");
// This block connects to the best known block, it is a normal continuation of the system. // This block connects to the best known block, it is a normal continuation of the system.
@ -386,8 +393,8 @@ public abstract class AbstractBlockChain {
// Notify the listeners of the new block, so the depth and workDone of stored transactions can be updated // Notify the listeners of the new block, so the depth and workDone of stored transactions can be updated
// (in the case of the listener being a wallet). Wallets need to know how deep each transaction is so // (in the case of the listener being a wallet). Wallets need to know how deep each transaction is so
// coinbases aren't used before maturity. // coinbases aren't used before maturity.
for (int i = 0; i < listeners.size(); i++) { boolean first = true;
BlockChainListener listener = listeners.get(i); for (BlockChainListener listener : listeners) {
if (block.transactions != null || filteredTxn != null) { if (block.transactions != null || filteredTxn != null) {
// If this is not the first wallet, ask for the transactions to be duplicated before being given // If this is not the first wallet, ask for the transactions to be duplicated before being given
// to the wallet when relevant. This ensures that if we have two connected wallets and a tx that // to the wallet when relevant. This ensures that if we have two connected wallets and a tx that
@ -395,27 +402,15 @@ public abstract class AbstractBlockChain {
// result in temporary in-memory corruption during re-orgs). See bug 257. We only duplicate in // result in temporary in-memory corruption during re-orgs). See bug 257. We only duplicate in
// the case of multiple wallets to avoid an unnecessary efficiency hit in the common case. // the case of multiple wallets to avoid an unnecessary efficiency hit in the common case.
sendTransactionsToListener(newStoredBlock, NewBlockType.BEST_CHAIN, listener, sendTransactionsToListener(newStoredBlock, NewBlockType.BEST_CHAIN, listener,
block.transactions != null ? block.transactions : filteredTxn, i > 0); block.transactions != null ? block.transactions : filteredTxn, !first);
} }
if (filteredTxHashList != null) { if (filteredTxHashList != null) {
for (Sha256Hash hash : filteredTxHashList) { for (Sha256Hash hash : filteredTxHashList) {
listener.notifyTransactionIsInBlock(hash, newStoredBlock, NewBlockType.BEST_CHAIN); listener.notifyTransactionIsInBlock(hash, newStoredBlock, NewBlockType.BEST_CHAIN);
} }
} }
// Allow the listener to have removed itself.
if (i == listeners.size()) {
break; // Listener removed itself and it was the last one.
} else if (listeners.get(i) != listener) {
i--; // Listener removed itself and it was not the last one.
break;
}
listener.notifyNewBestBlock(newStoredBlock); listener.notifyNewBestBlock(newStoredBlock);
if (i == listeners.size()) { first = false;
break; // Listener removed itself and it was the last one.
} else if (listeners.get(i) != listener) {
i--; // Listener removed itself and it was not the last one.
break;
}
} }
} else { } else {
// This block connects to somewhere other than the top of the best known chain. We treat these differently. // This block connects to somewhere other than the top of the best known chain. We treat these differently.
@ -427,7 +422,7 @@ public abstract class AbstractBlockChain {
if (haveNewBestChain) { if (haveNewBestChain) {
log.info("Block is causing a re-organize"); log.info("Block is causing a re-organize");
} else { } else {
StoredBlock splitPoint = findSplit(newBlock, head); StoredBlock splitPoint = findSplit(newBlock, head, blockStore);
if (splitPoint != null && splitPoint.equals(newBlock)) { if (splitPoint != null && splitPoint.equals(newBlock)) {
// newStoredBlock is a part of the same chain, there's no fork. This happens when we receive a block // newStoredBlock is a part of the same chain, there's no fork. This happens when we receive a block
// that we already saw and linked into the chain previously, which isn't the chain head. // that we already saw and linked into the chain previously, which isn't the chain head.
@ -489,11 +484,12 @@ public abstract class AbstractBlockChain {
/** /**
* Gets the median timestamp of the last 11 blocks * Gets the median timestamp of the last 11 blocks
*/ */
private long getMedianTimestampOfRecentBlocks(StoredBlock storedBlock) throws BlockStoreException { private static long getMedianTimestampOfRecentBlocks(StoredBlock storedBlock,
BlockStore store) throws BlockStoreException {
long[] timestamps = new long[11]; long[] timestamps = new long[11];
int unused = 9; int unused = 9;
timestamps[10] = storedBlock.getHeader().getTimeSeconds(); timestamps[10] = storedBlock.getHeader().getTimeSeconds();
while (unused >= 0 && (storedBlock = storedBlock.getPrev(blockStore)) != null) while (unused >= 0 && (storedBlock = storedBlock.getPrev(store)) != null)
timestamps[unused--] = storedBlock.getHeader().getTimeSeconds(); timestamps[unused--] = storedBlock.getHeader().getTimeSeconds();
Arrays.sort(timestamps, unused+1, 10); Arrays.sort(timestamps, unused+1, 10);
@ -516,19 +512,20 @@ public abstract class AbstractBlockChain {
*/ */
private void handleNewBestChain(StoredBlock storedPrev, StoredBlock newChainHead, Block block, boolean expensiveChecks) private void handleNewBestChain(StoredBlock storedPrev, StoredBlock newChainHead, Block block, boolean expensiveChecks)
throws BlockStoreException, VerificationException, PrunedException { throws BlockStoreException, VerificationException, PrunedException {
checkState(lock.isLocked());
// This chain has overtaken the one we currently believe is best. Reorganize is required. // 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 // Firstly, calculate the block at which the chain diverged. We only need to examine the
// chain from beyond this block to find differences. // chain from beyond this block to find differences.
StoredBlock head = getChainHead(); StoredBlock head = getChainHead();
StoredBlock splitPoint = findSplit(newChainHead, head); StoredBlock splitPoint = findSplit(newChainHead, head, blockStore);
log.info("Re-organize after split at height {}", splitPoint.getHeight()); log.info("Re-organize after split at height {}", splitPoint.getHeight());
log.info("Old chain head: {}", head.getHeader().getHashAsString()); log.info("Old chain head: {}", head.getHeader().getHashAsString());
log.info("New chain head: {}", newChainHead.getHeader().getHashAsString()); log.info("New chain head: {}", newChainHead.getHeader().getHashAsString());
log.info("Split at block: {}", splitPoint.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. // Then build a list of all blocks in the old part of the chain and the new part.
LinkedList<StoredBlock> oldBlocks = getPartialChain(head, splitPoint); LinkedList<StoredBlock> oldBlocks = getPartialChain(head, splitPoint, blockStore);
LinkedList<StoredBlock> newBlocks = getPartialChain(newChainHead, splitPoint); LinkedList<StoredBlock> newBlocks = getPartialChain(newChainHead, splitPoint, blockStore);
// Disconnect each transaction in the previous main chain that is no longer in the new main chain // Disconnect each transaction in the previous main chain that is no longer in the new main chain
StoredBlock storedNewHead = splitPoint; StoredBlock storedNewHead = splitPoint;
if (shouldVerifyTransactions()) { if (shouldVerifyTransactions()) {
@ -547,7 +544,7 @@ public abstract class AbstractBlockChain {
// Walk in ascending chronological order. // Walk in ascending chronological order.
for (Iterator<StoredBlock> it = newBlocks.descendingIterator(); it.hasNext();) { for (Iterator<StoredBlock> it = newBlocks.descendingIterator(); it.hasNext();) {
cursor = it.next(); cursor = it.next();
if (expensiveChecks && cursor.getHeader().getTimeSeconds() <= getMedianTimestampOfRecentBlocks(cursor.getPrev(blockStore))) if (expensiveChecks && cursor.getHeader().getTimeSeconds() <= getMedianTimestampOfRecentBlocks(cursor.getPrev(blockStore), blockStore))
throw new VerificationException("Block's timestamp is too early during reorg"); throw new VerificationException("Block's timestamp is too early during reorg");
TransactionOutputChanges txOutChanges; TransactionOutputChanges txOutChanges;
if (cursor != newChainHead || block == null) if (cursor != newChainHead || block == null)
@ -579,13 +576,13 @@ public abstract class AbstractBlockChain {
/** /**
* Returns the set of contiguous blocks between 'higher' and 'lower'. Higher is included, lower is not. * Returns the set of contiguous blocks between 'higher' and 'lower'. Higher is included, lower is not.
*/ */
private LinkedList<StoredBlock> getPartialChain(StoredBlock higher, StoredBlock lower) throws BlockStoreException { private static LinkedList<StoredBlock> getPartialChain(StoredBlock higher, StoredBlock lower, BlockStore store) throws BlockStoreException {
checkArgument(higher.getHeight() > lower.getHeight(), "higher and lower are reversed"); checkArgument(higher.getHeight() > lower.getHeight(), "higher and lower are reversed");
LinkedList<StoredBlock> results = new LinkedList<StoredBlock>(); LinkedList<StoredBlock> results = new LinkedList<StoredBlock>();
StoredBlock cursor = higher; StoredBlock cursor = higher;
while (true) { while (true) {
results.add(cursor); results.add(cursor);
cursor = checkNotNull(cursor.getPrev(blockStore), "Ran off the end of the chain"); cursor = checkNotNull(cursor.getPrev(store), "Ran off the end of the chain");
if (cursor.equals(lower)) break; if (cursor.equals(lower)) break;
} }
return results; return results;
@ -596,7 +593,8 @@ public abstract class AbstractBlockChain {
* found (ie they are not part of the same chain). Returns newChainHead or chainHead if they don't actually diverge * found (ie they are not part of the same chain). Returns newChainHead or chainHead if they don't actually diverge
* but are part of the same chain. * but are part of the same chain.
*/ */
private StoredBlock findSplit(StoredBlock newChainHead, StoredBlock oldChainHead) throws BlockStoreException { private static StoredBlock findSplit(StoredBlock newChainHead, StoredBlock oldChainHead,
BlockStore store) throws BlockStoreException {
StoredBlock currentChainCursor = oldChainHead; StoredBlock currentChainCursor = oldChainHead;
StoredBlock newChainCursor = newChainHead; StoredBlock newChainCursor = newChainHead;
// Loop until we find the block both chains have in common. Example: // Loop until we find the block both chains have in common. Example:
@ -607,10 +605,10 @@ public abstract class AbstractBlockChain {
// findSplit will return block B. oldChainHead = D and newChainHead = G. // findSplit will return block B. oldChainHead = D and newChainHead = G.
while (!currentChainCursor.equals(newChainCursor)) { while (!currentChainCursor.equals(newChainCursor)) {
if (currentChainCursor.getHeight() > newChainCursor.getHeight()) { if (currentChainCursor.getHeight() > newChainCursor.getHeight()) {
currentChainCursor = currentChainCursor.getPrev(blockStore); currentChainCursor = currentChainCursor.getPrev(store);
checkNotNull(currentChainCursor, "Attempt to follow an orphan chain"); checkNotNull(currentChainCursor, "Attempt to follow an orphan chain");
} else { } else {
newChainCursor = newChainCursor.getPrev(blockStore); newChainCursor = newChainCursor.getPrev(store);
checkNotNull(newChainCursor, "Attempt to follow an orphan chain"); checkNotNull(newChainCursor, "Attempt to follow an orphan chain");
} }
} }
@ -629,8 +627,10 @@ public abstract class AbstractBlockChain {
SIDE_CHAIN SIDE_CHAIN
} }
private void sendTransactionsToListener(StoredBlock block, NewBlockType blockType, BlockChainListener listener, private static void sendTransactionsToListener(StoredBlock block, NewBlockType blockType,
List<Transaction> transactions, boolean clone) throws VerificationException { BlockChainListener listener,
List<Transaction> transactions,
boolean clone) throws VerificationException {
for (Transaction tx : transactions) { for (Transaction tx : transactions) {
try { try {
if (listener.isTransactionRelevant(tx)) { if (listener.isTransactionRelevant(tx)) {
@ -660,6 +660,7 @@ public abstract class AbstractBlockChain {
* For each block in orphanBlocks, see if we can now fit it on top of the chain and if so, do so. * For each block in orphanBlocks, see if we can now fit it on top of the chain and if so, do so.
*/ */
private void tryConnectingOrphans() throws VerificationException, BlockStoreException, PrunedException { private void tryConnectingOrphans() throws VerificationException, BlockStoreException, PrunedException {
checkState(lock.isLocked());
// For each block in our orphan list, try and fit it onto the head of the chain. If we succeed remove it // For each block in our orphan 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 // 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. // we can't fit anything else on the top.
@ -698,8 +699,8 @@ public abstract class AbstractBlockChain {
/** /**
* Throws an exception if the blocks difficulty is not correct. * Throws an exception if the blocks difficulty is not correct.
*/ */
private void checkDifficultyTransitions(StoredBlock storedPrev, Block nextBlock) private void checkDifficultyTransitions(StoredBlock storedPrev, Block nextBlock) throws BlockStoreException, VerificationException {
throws BlockStoreException, VerificationException { checkState(lock.isLocked());
Block prev = storedPrev.getHeader(); Block prev = storedPrev.getHeader();
// Is this supposed to be a difficulty transition point? // Is this supposed to be a difficulty transition point?
@ -767,6 +768,7 @@ public abstract class AbstractBlockChain {
} }
private void checkTestnetDifficulty(StoredBlock storedPrev, Block prev, Block next) throws VerificationException, BlockStoreException { private void checkTestnetDifficulty(StoredBlock storedPrev, Block prev, Block next) throws VerificationException, BlockStoreException {
checkState(lock.isLocked());
// After 15th February 2012 the rules on the testnet change to avoid people running up the difficulty // After 15th February 2012 the rules on the testnet change to avoid people running up the difficulty
// and then leaving, making it too hard to mine a block. On non-difficulty transition points, easy // and then leaving, making it too hard to mine a block. On non-difficulty transition points, easy
// blocks are allowed if there has been a span of 20 minutes without one. // blocks are allowed if there has been a span of 20 minutes without one.
@ -794,6 +796,7 @@ public abstract class AbstractBlockChain {
* Returns true if any connected wallet considers any transaction in the block to be relevant. * Returns true if any connected wallet considers any transaction in the block to be relevant.
*/ */
private boolean containsRelevantTransactions(Block block) { private boolean containsRelevantTransactions(Block block) {
// Does not need to be locked.
for (Transaction tx : block.transactions) { for (Transaction tx : block.transactions) {
try { try {
for (BlockChainListener listener : listeners) { for (BlockChainListener listener : listeners) {
@ -826,20 +829,30 @@ public abstract class AbstractBlockChain {
* *
* @return from or one of froms parents, or null if "from" does not identify an orphan block * @return from or one of froms parents, or null if "from" does not identify an orphan block
*/ */
public synchronized Block getOrphanRoot(Sha256Hash from) { public Block getOrphanRoot(Sha256Hash from) {
OrphanBlock cursor = orphanBlocks.get(from); lock.lock();
if (cursor == null) try {
return null; OrphanBlock cursor = orphanBlocks.get(from);
OrphanBlock tmp; if (cursor == null)
while ((tmp = orphanBlocks.get(cursor.block.getPrevBlockHash())) != null) { return null;
cursor = tmp; OrphanBlock tmp;
while ((tmp = orphanBlocks.get(cursor.block.getPrevBlockHash())) != null) {
cursor = tmp;
}
return cursor.block;
} finally {
lock.unlock();
} }
return cursor.block;
} }
/** Returns true if the given block is currently in the orphan blocks list. */ /** Returns true if the given block is currently in the orphan blocks list. */
public synchronized boolean isOrphan(Sha256Hash block) { public boolean isOrphan(Sha256Hash block) {
return orphanBlocks.containsKey(block); lock.lock();
try {
return orphanBlocks.containsKey(block);
} finally {
lock.unlock();
}
} }
/** /**

View File

@ -26,6 +26,9 @@ import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
import static com.google.common.base.Preconditions.checkState;
/** /**
* <p>A FullPrunedBlockChain works in conjunction with a {@link FullPrunedBlockStore} to verify all the rules of the * <p>A FullPrunedBlockChain works in conjunction with a {@link FullPrunedBlockStore} to verify all the rules of the
* Bitcoin system, with the downside being a larg cost in system resources. Fully verifying means all unspent transaction * Bitcoin system, with the downside being a larg cost in system resources. Fully verifying means all unspent transaction
@ -95,8 +98,9 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
ExecutorService scriptVerificationExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); ExecutorService scriptVerificationExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
@Override @Override
protected synchronized TransactionOutputChanges connectTransactions(int height, Block block) protected TransactionOutputChanges connectTransactions(int height, Block block)
throws VerificationException, BlockStoreException { throws VerificationException, BlockStoreException {
checkState(lock.isLocked());
if (block.transactions == null) if (block.transactions == null)
throw new RuntimeException("connectTransactions called with Block that didn't have transactions!"); throw new RuntimeException("connectTransactions called with Block that didn't have transactions!");
if (!params.passesCheckpoint(height, block.getHash())) if (!params.passesCheckpoint(height, block.getHash()))
@ -244,6 +248,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
*/ */
protected synchronized TransactionOutputChanges connectTransactions(StoredBlock newBlock) protected synchronized TransactionOutputChanges connectTransactions(StoredBlock newBlock)
throws VerificationException, BlockStoreException, PrunedException { throws VerificationException, BlockStoreException, PrunedException {
checkState(lock.isLocked());
if (!params.passesCheckpoint(newBlock.getHeight(), newBlock.getHeader().getHash())) if (!params.passesCheckpoint(newBlock.getHeight(), newBlock.getHeader().getHash()))
throw new VerificationException("Block failed checkpoint lockin at " + newBlock.getHeight()); throw new VerificationException("Block failed checkpoint lockin at " + newBlock.getHeight());
@ -393,6 +398,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
*/ */
@Override @Override
protected void disconnectTransactions(StoredBlock oldBlock) throws PrunedException, BlockStoreException { protected void disconnectTransactions(StoredBlock oldBlock) throws PrunedException, BlockStoreException {
checkState(lock.isLocked());
blockStore.beginDatabaseBatchWrite(); blockStore.beginDatabaseBatchWrite();
try { try {
StoredUndoableBlock undoBlock = blockStore.getUndoBlock(oldBlock.getHeader().getHash()); StoredUndoableBlock undoBlock = blockStore.getUndoBlock(oldBlock.getHeader().getHash());
@ -413,6 +419,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
@Override @Override
protected void doSetChainHead(StoredBlock chainHead) throws BlockStoreException { protected void doSetChainHead(StoredBlock chainHead) throws BlockStoreException {
checkState(lock.isLocked());
blockStore.setVerifiedChainHead(chainHead); blockStore.setVerifiedChainHead(chainHead);
blockStore.commitDatabaseBatchWrite(); blockStore.commitDatabaseBatchWrite();
} }
@ -424,6 +431,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
@Override @Override
protected StoredBlock getStoredBlockInCurrentScope(Sha256Hash hash) throws BlockStoreException { protected StoredBlock getStoredBlockInCurrentScope(Sha256Hash hash) throws BlockStoreException {
checkState(lock.isLocked());
return blockStore.getOnceUndoableStoredBlock(hash); return blockStore.getOnceUndoableStoredBlock(hash);
} }
} }