diff --git a/core/src/main/java/com/google/bitcoin/core/AbstractBlockChain.java b/core/src/main/java/com/google/bitcoin/core/AbstractBlockChain.java
index 65013b9a..b79d77a5 100644
--- a/core/src/main/java/com/google/bitcoin/core/AbstractBlockChain.java
+++ b/core/src/main/java/com/google/bitcoin/core/AbstractBlockChain.java
@@ -76,7 +76,7 @@ public abstract class AbstractBlockChain {
* greater work than the one obtained by following this one down. In that case a reorganize is triggered,
* potentially invalidating transactions in our wallet.
*/
- private StoredBlock chainHead;
+ protected StoredBlock chainHead;
// 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
@@ -157,11 +157,11 @@ public abstract class AbstractBlockChain {
throws BlockStoreException, VerificationException;
/**
- * Called before setting chain head in memory, but after writing the blockstore to disk.
- * Can be used to commit database transactions that were started by
- * disconnectTransactions/connectTransactions.
+ * Called before setting chain head in memory.
+ * Should write the new head to block store and then commit any database transactions
+ * that were started by disconnectTransactions/connectTransactions.
*/
- protected abstract void preSetChainHead() throws BlockStoreException;
+ protected abstract void doSetChainHead(StoredBlock chainHead) throws BlockStoreException;
/**
* Called if we (possibly) previously called disconnectTransaction/connectTransactions,
@@ -170,6 +170,12 @@ public abstract class AbstractBlockChain {
* disconnectTransactions/connectTransactions.
*/
protected abstract void notSettingChainHead() throws BlockStoreException;
+
+ /**
+ * For a standard BlockChain, this should return blockStore.get(hash),
+ * for a FullPrunedBlockChain blockStore.getOnceUndoableStoredBlock(hash)
+ */
+ protected abstract StoredBlock getStoredBlockInCurrentScope(Sha256Hash hash) throws BlockStoreException;
/**
* Processes a received block and tries to add it to the chain. If there's something wrong with the block an
@@ -275,7 +281,7 @@ public abstract class AbstractBlockChain {
}
// Try linking it to a place in the currently known blocks.
- StoredBlock storedPrev = blockStore.get(block.getPrevBlockHash());
+ 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
@@ -579,8 +585,7 @@ public abstract class AbstractBlockChain {
}
private void setChainHead(StoredBlock chainHead) throws BlockStoreException {
- blockStore.setChainHead(chainHead);
- preSetChainHead();
+ doSetChainHead(chainHead);
synchronized (chainHeadLock) {
this.chainHead = chainHead;
}
@@ -604,8 +609,8 @@ public abstract class AbstractBlockChain {
Block block = iter.next();
log.debug("Trying to connect {}", block.getHash());
// Look up the blocks previous.
- StoredBlock prev = blockStore.get(block.getPrevBlockHash());
- if (prev == null) {
+ StoredBlock prev = getStoredBlockInCurrentScope(block.getPrevBlockHash());
+ if (prev == null || prev.getHeight() > getChainHead().getHeight()) {
// This is still an unconnected/orphan block.
log.debug(" but it is not connectable right now");
continue;
diff --git a/core/src/main/java/com/google/bitcoin/core/BlockChain.java b/core/src/main/java/com/google/bitcoin/core/BlockChain.java
index 3efbaaf7..8106b642 100644
--- a/core/src/main/java/com/google/bitcoin/core/BlockChain.java
+++ b/core/src/main/java/com/google/bitcoin/core/BlockChain.java
@@ -99,12 +99,17 @@ public class BlockChain extends AbstractBlockChain {
}
@Override
- protected void preSetChainHead() {
- // We don't use DB transactions here, so we don't need to do anything
+ protected void doSetChainHead(StoredBlock chainHead) throws BlockStoreException {
+ blockStore.setChainHead(chainHead);
}
@Override
protected void notSettingChainHead() throws BlockStoreException {
// We don't use DB transactions here, so we don't need to do anything
}
+
+ @Override
+ protected StoredBlock getStoredBlockInCurrentScope(Sha256Hash hash) throws BlockStoreException {
+ return blockStore.get(hash);
+ }
}
diff --git a/core/src/main/java/com/google/bitcoin/core/FullPrunedBlockChain.java b/core/src/main/java/com/google/bitcoin/core/FullPrunedBlockChain.java
index d5779a2b..933d7242 100644
--- a/core/src/main/java/com/google/bitcoin/core/FullPrunedBlockChain.java
+++ b/core/src/main/java/com/google/bitcoin/core/FullPrunedBlockChain.java
@@ -64,6 +64,8 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
FullPrunedBlockStore blockStore) throws BlockStoreException {
super(params, listeners, blockStore);
this.blockStore = blockStore;
+ // Ignore upgrading for now
+ this.chainHead = blockStore.getVerifiedChainHead();
}
@Override
@@ -409,7 +411,8 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
}
@Override
- protected void preSetChainHead() throws BlockStoreException {
+ protected void doSetChainHead(StoredBlock chainHead) throws BlockStoreException {
+ blockStore.setVerifiedChainHead(chainHead);
blockStore.commitDatabaseBatchWrite();
}
@@ -417,4 +420,9 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
protected void notSettingChainHead() throws BlockStoreException {
blockStore.abortDatabaseBatchWrite();
}
+
+ @Override
+ protected StoredBlock getStoredBlockInCurrentScope(Sha256Hash hash) throws BlockStoreException {
+ return blockStore.getOnceUndoableStoredBlock(hash);
+ }
}
diff --git a/core/src/main/java/com/google/bitcoin/store/FullPrunedBlockStore.java b/core/src/main/java/com/google/bitcoin/store/FullPrunedBlockStore.java
index c613b6af..5211abc5 100644
--- a/core/src/main/java/com/google/bitcoin/store/FullPrunedBlockStore.java
+++ b/core/src/main/java/com/google/bitcoin/store/FullPrunedBlockStore.java
@@ -24,8 +24,17 @@ import com.google.bitcoin.core.StoredUndoableBlock;
/**
*
An implementor of FullPrunedBlockStore saves StoredBlock objects to some storage mechanism.
*
- * It should store the {@link StoredUndoableBlock}s of a number of recent blocks.
- * It is advisable to store any {@link StoredUndoableBlock} which has a height > head.height - N.
+ *
In addition to keeping tack of a chain using {@link StoredBlock}s, it should also keep track of a second
+ * copy of the chain which holds {@link StoredUndoableBlock}s. In this way, an application can perform a
+ * headers-only initial sync and then use that information to more efficiently download a locally verified
+ * full copy of the block chain.
+ *
+ * A FullPrunedBlockStore should function well as a standard {@link BlockStore} and then be able to
+ * trivially switch to being used as a FullPrunedBlockStore.
+ *
+ * It should store the {@link StoredUndoableBlock}s of a number of recent blocks before verifiedHead.height and
+ * all those after verifiedHead.height.
+ * It is advisable to store any {@link StoredUndoableBlock} which has a height > verifiedHead.height - N.
* Because N determines the memory usage, it is recommended that N be customizable. N should be chosen such that
* re-orgs beyond that point are vanishingly unlikely, for example, a few thousand blocks is a reasonable choice.
*
@@ -41,11 +50,21 @@ import com.google.bitcoin.core.StoredUndoableBlock;
*/
public interface FullPrunedBlockStore extends BlockStore {
/**
- * Saves the given {@link StoredUndoableBlock} and {@link StoredBlock}. Calculates keys from the {@link StoredBlock}
- * Note that a call to put(StoredBlock) will throw a BlockStoreException if its height is > head.height - N
+ * Saves the given {@link StoredUndoableBlock} and {@link StoredBlock}. Calculates keys from the {@link StoredBlock}
+ *
+ * Though not required for proper function of a FullPrunedBlockStore, any user of a FullPrunedBlockStore should ensure
+ * that a StoredUndoableBlock for each block up to the fully verified chain head has been added to this block store using
+ * this function (not put(StoredBlock)), so that the ability to perform reorgs is maintained.
+ *
* @throws BlockStoreException if there is a problem with the underlying storage layer, such as running out of disk space.
*/
void put(StoredBlock storedBlock, StoredUndoableBlock undoableBlock) throws BlockStoreException;
+
+ /**
+ * Returns the StoredBlock that was added as a StoredUndoableBlock given a hash. The returned values block.getHash() method will be equal to the
+ * parameter. If no such block is found, returns null.
+ */
+ StoredBlock getOnceUndoableStoredBlock(Sha256Hash hash) throws BlockStoreException;
/**
* Returns a {@link StoredUndoableBlock} who's block.getHash() method will be equal to the
@@ -78,6 +97,25 @@ public interface FullPrunedBlockStore extends BlockStore {
*/
boolean hasUnspentOutputs(Sha256Hash hash, int numOutputs) throws BlockStoreException;
+ /**
+ * Returns the {@link StoredBlock} that represents the top of the chain of greatest total work that has
+ * been fully verified and the point in the chain at which the unspent transaction output set in this
+ * store represents.
+ */
+ StoredBlock getVerifiedChainHead() throws BlockStoreException;
+
+ /**
+ * Sets the {@link StoredBlock} that represents the top of the chain of greatest total work that has been
+ * fully verified. It should generally be set after a batch of updates to the transaction unspent output set,
+ * before a call to commitDatabaseBatchWrite.
+ *
+ * If chainHead has a greater height than the non-verified chain head (ie that set with
+ * {@link BlockStore.setChainHead}) the non-verified chain head should be set to the one set here.
+ * In this way a class using a FullPrunedBlockStore only in full-verification mode can ignore the regular
+ * {@link BlockStore} functions implemented as a part of a FullPrunedBlockStore.
+ */
+ void setVerifiedChainHead(StoredBlock chainHead) throws BlockStoreException;
+
/**
* Begins/Commits/Aborts a database transaction.
*
diff --git a/core/src/main/java/com/google/bitcoin/store/H2FullPrunedBlockStore.java b/core/src/main/java/com/google/bitcoin/store/H2FullPrunedBlockStore.java
index f1aba97c..d9474e26 100644
--- a/core/src/main/java/com/google/bitcoin/store/H2FullPrunedBlockStore.java
+++ b/core/src/main/java/com/google/bitcoin/store/H2FullPrunedBlockStore.java
@@ -42,8 +42,10 @@ import java.util.List;
public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
private static final Logger log = LoggerFactory.getLogger(H2FullPrunedBlockStore.class);
- private StoredBlock chainHeadBlock;
private Sha256Hash chainHeadHash;
+ private StoredBlock chainHeadBlock;
+ private Sha256Hash verifiedChainHeadHash;
+ private StoredBlock verifiedChainHeadBlock;
private NetworkParameters params;
private ThreadLocal conn;
private List allConnections;
@@ -56,12 +58,14 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
+ "value BLOB"
+ ")";
static final String CHAIN_HEAD_SETTING = "chainhead";
+ static final String VERIFIED_CHAIN_HEAD_SETTING = "verifiedchainhead";
static final String CREATE_HEADERS_TABLE = "CREATE TABLE headers ( "
+ "hash BINARY(28) NOT NULL CONSTRAINT headers_pk PRIMARY KEY,"
+ "chainWork BLOB NOT NULL,"
+ "height INT NOT NULL,"
- + "header BLOB NOT NULL"
+ + "header BLOB NOT NULL,"
+ + "wasUndoable BOOL NOT NULL"
+ ")";
static final String CREATE_UNDOABLE_TABLE = "CREATE TABLE undoableBlocks ( "
@@ -203,25 +207,40 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
log.debug("H2FullPrunedBlockStore : CREATE open output table");
s.executeUpdate(CREATE_OPEN_OUTPUT_TABLE);
- s.executeUpdate("INSERT INTO settings(name, value) VALUES('chainhead', NULL)");
+ s.executeUpdate("INSERT INTO settings(name, value) VALUES('" + CHAIN_HEAD_SETTING + "', NULL)");
+ s.executeUpdate("INSERT INTO settings(name, value) VALUES('" + VERIFIED_CHAIN_HEAD_SETTING + "', NULL)");
s.close();
createNewStore(params);
}
private void initFromDatabase() throws SQLException, BlockStoreException {
Statement s = conn.get().createStatement();
- ResultSet rs = s.executeQuery("SELECT value FROM settings WHERE name = 'chainhead'");
+ ResultSet rs = s.executeQuery("SELECT value FROM settings WHERE name = '" + CHAIN_HEAD_SETTING + "'");
if (!rs.next()) {
throw new BlockStoreException("corrupt H2 block store - no chain head pointer");
}
Sha256Hash hash = new Sha256Hash(rs.getBytes(1));
- s.close();
+ rs.close();
this.chainHeadBlock = get(hash);
+ this.chainHeadHash = hash;
if (this.chainHeadBlock == null)
{
throw new BlockStoreException("corrupt H2 block store - head block not found");
}
- this.chainHeadHash = hash;
+
+ rs = s.executeQuery("SELECT value FROM settings WHERE name = '" + VERIFIED_CHAIN_HEAD_SETTING + "'");
+ if (!rs.next()) {
+ throw new BlockStoreException("corrupt H2 block store - no verified chain head pointer");
+ }
+ hash = new Sha256Hash(rs.getBytes(1));
+ rs.close();
+ s.close();
+ this.verifiedChainHeadBlock = get(hash);
+ this.verifiedChainHeadHash = hash;
+ if (this.verifiedChainHeadBlock == null)
+ {
+ throw new BlockStoreException("corrupt H2 block store - verified head block not found");
+ }
}
private void createNewStore(NetworkParameters params) throws BlockStoreException {
@@ -235,6 +254,7 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
StoredUndoableBlock storedGenesis = new StoredUndoableBlock(params.genesisBlock.getHash(), genesisTransactions);
put(storedGenesisHeader, storedGenesis);
setChainHead(storedGenesisHeader);
+ setVerifiedChainHead(storedGenesisHeader);
} catch (VerificationException e) {
throw new RuntimeException(e); // Cannot happen.
}
@@ -335,11 +355,11 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
}
- private void putStoredBlock(StoredBlock storedBlock) throws BlockStoreException {
+ private void putUpdateStoredBlock(StoredBlock storedBlock, boolean wasUndoable) throws SQLException {
try {
PreparedStatement s =
- conn.get().prepareStatement("INSERT INTO headers(hash, chainWork, height, header)"
- + " VALUES(?, ?, ?, ?)");
+ conn.get().prepareStatement("INSERT INTO headers(hash, chainWork, height, header, wasUndoable)"
+ + " VALUES(?, ?, ?, ?, ?)");
// We skip the first 4 bytes because (on prodnet) the minimum target has 4 0-bytes
byte[] hashBytes = new byte[28];
System.arraycopy(storedBlock.getHeader().getHash().getBytes(), 3, hashBytes, 0, 28);
@@ -347,18 +367,33 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
s.setBytes(2, storedBlock.getChainWork().toByteArray());
s.setInt(3, storedBlock.getHeight());
s.setBytes(4, storedBlock.getHeader().unsafeBitcoinSerialize());
+ s.setBoolean(5, wasUndoable);
+ s.executeUpdate();
+ s.close();
+ } catch (SQLException e) {
+ // It is possible we try to add a duplicate StoredBlock if we upgraded
+ // In that case, we just update the entry to mark it wasUndoable
+ if (e.getErrorCode() != 23505 || !wasUndoable)
+ throw e;
+
+ PreparedStatement s = conn.get().prepareStatement("UPDATE headers SET wasUndoable=? WHERE hash=?");
+ s.setBoolean(1, true);
+ // We skip the first 4 bytes because (on prodnet) the minimum target has 4 0-bytes
+ byte[] hashBytes = new byte[28];
+ System.arraycopy(storedBlock.getHeader().getHash().getBytes(), 3, hashBytes, 0, 28);
+ s.setBytes(2, hashBytes);
s.executeUpdate();
s.close();
- } catch (SQLException ex) {
- throw new BlockStoreException(ex);
}
}
public void put(StoredBlock storedBlock) throws BlockStoreException {
maybeConnect();
- if (storedBlock.getHeight() > chainHeadBlock.getHeight() - fullStoreDepth)
- throw new BlockStoreException("Putting a StoredBlock in H2FullPrunedBlockStore at height higher than head - fullStoredDepth");
- putStoredBlock(storedBlock);
+ try {
+ putUpdateStoredBlock(storedBlock, false);
+ } catch (SQLException e) {
+ throw new BlockStoreException(e);
+ }
}
public void put(StoredBlock storedBlock, StoredUndoableBlock undoableBlock) throws BlockStoreException {
@@ -405,7 +440,11 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
}
s.executeUpdate();
s.close();
- putStoredBlock(storedBlock);
+ try {
+ putUpdateStoredBlock(storedBlock, true);
+ } catch (SQLException e) {
+ throw new BlockStoreException(e);
+ }
} catch (SQLException e) {
if (e.getErrorCode() != 23505)
throw new BlockStoreException(e);
@@ -430,15 +469,17 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
}
}
- public StoredBlock get(Sha256Hash hash) throws BlockStoreException {
+ public StoredBlock get(Sha256Hash hash, boolean wasUndoableOnly) throws BlockStoreException {
// Optimize for chain head
if (chainHeadHash != null && chainHeadHash.equals(hash))
return chainHeadBlock;
+ if (verifiedChainHeadHash != null && verifiedChainHeadHash.equals(hash))
+ return verifiedChainHeadBlock;
maybeConnect();
PreparedStatement s = null;
try {
s = conn.get()
- .prepareStatement("SELECT chainWork, height, header FROM headers WHERE hash = ?");
+ .prepareStatement("SELECT chainWork, height, header, wasUndoable FROM headers WHERE hash = ?");
// We skip the first 4 bytes because (on prodnet) the minimum target has 4 0-bytes
byte[] hashBytes = new byte[28];
System.arraycopy(hash.getBytes(), 3, hashBytes, 0, 28);
@@ -448,7 +489,10 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
return null;
}
// Parse it.
-
+
+ if (wasUndoableOnly && !results.getBoolean(4))
+ return null;
+
BigInteger chainWork = new BigInteger(results.getBytes(1));
int height = results.getInt(2);
Block b = new Block(params, results.getBytes(3));
@@ -472,6 +516,14 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
}
}
+ public StoredBlock get(Sha256Hash hash) throws BlockStoreException {
+ return get(hash, false);
+ }
+
+ public StoredBlock getOnceUndoableStoredBlock(Sha256Hash hash) throws BlockStoreException {
+ return get(hash, true);
+ }
+
public StoredUndoableBlock getUndoBlock(Sha256Hash hash) throws BlockStoreException {
maybeConnect();
PreparedStatement s = null;
@@ -550,6 +602,29 @@ public class H2FullPrunedBlockStore implements FullPrunedBlockStore {
} catch (SQLException ex) {
throw new BlockStoreException(ex);
}
+ }
+
+ public StoredBlock getVerifiedChainHead() throws BlockStoreException {
+ return verifiedChainHeadBlock;
+ }
+
+ public void setVerifiedChainHead(StoredBlock chainHead) throws BlockStoreException {
+ Sha256Hash hash = chainHead.getHeader().getHash();
+ this.verifiedChainHeadHash = hash;
+ this.verifiedChainHeadBlock = chainHead;
+ maybeConnect();
+ try {
+ PreparedStatement s = conn.get()
+ .prepareStatement("UPDATE settings SET value = ? WHERE name = ?");
+ s.setString(2, VERIFIED_CHAIN_HEAD_SETTING);
+ s.setBytes(1, hash.getBytes());
+ s.executeUpdate();
+ s.close();
+ } catch (SQLException ex) {
+ throw new BlockStoreException(ex);
+ }
+ if (this.chainHeadBlock.getHeight() < chainHead.getHeight())
+ setChainHead(chainHead);
removeUndoableBlocksWhereHeightIsLessThan(chainHead.getHeight() - fullStoreDepth);
}
diff --git a/core/src/main/java/com/google/bitcoin/store/MemoryFullPrunedBlockStore.java b/core/src/main/java/com/google/bitcoin/store/MemoryFullPrunedBlockStore.java
index d33fc237..6a46a03a 100644
--- a/core/src/main/java/com/google/bitcoin/store/MemoryFullPrunedBlockStore.java
+++ b/core/src/main/java/com/google/bitcoin/store/MemoryFullPrunedBlockStore.java
@@ -308,11 +308,17 @@ class TransactionalMultiKeyHashMap {
* Used primarily for unit testing.
*/
public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore {
- private TransactionalHashMap blockMap;
+ class StoredBlockAndWasUndoableFlag {
+ public StoredBlock block;
+ public boolean wasUndoable;
+ public StoredBlockAndWasUndoableFlag(StoredBlock block, boolean wasUndoable) { this.block = block; this.wasUndoable = wasUndoable; }
+ }
+ private TransactionalHashMap blockMap;
private TransactionalMultiKeyHashMap fullBlockMap;
//TODO: Use something more suited to remove-heavy use?
private TransactionalHashMap transactionOutputMap;
private StoredBlock chainHead;
+ private StoredBlock verifiedChainHead;
private int fullStoreDepth;
/**
@@ -321,7 +327,7 @@ public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore {
* @param fullStoreDepth The depth of blocks to keep FullStoredBlocks instead of StoredBlocks
*/
public MemoryFullPrunedBlockStore(NetworkParameters params, int fullStoreDepth) {
- blockMap = new TransactionalHashMap();
+ blockMap = new TransactionalHashMap();
fullBlockMap = new TransactionalMultiKeyHashMap();
transactionOutputMap = new TransactionalHashMap();
this.fullStoreDepth = fullStoreDepth > 0 ? fullStoreDepth : 1;
@@ -333,6 +339,7 @@ public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore {
StoredUndoableBlock storedGenesis = new StoredUndoableBlock(params.genesisBlock.getHash(), genesisTransactions);
put(storedGenesisHeader, storedGenesis);
setChainHead(storedGenesisHeader);
+ setVerifiedChainHead(storedGenesisHeader);
} catch (BlockStoreException e) {
throw new RuntimeException(e); // Cannot happen.
} catch (VerificationException e) {
@@ -342,21 +349,27 @@ public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore {
public synchronized void put(StoredBlock block) throws BlockStoreException {
Preconditions.checkNotNull(blockMap, "MemoryFullPrunedBlockStore is closed");
- if (block.getHeight() > chainHead.getHeight() - fullStoreDepth)
- throw new BlockStoreException("Putting a StoredBlock in MemoryFullPrunedBlockStore at height higher than head - fullStoredDepth");
Sha256Hash hash = block.getHeader().getHash();
- blockMap.put(hash, block);
+ blockMap.put(hash, new StoredBlockAndWasUndoableFlag(block, false));
}
public synchronized void put(StoredBlock storedBlock, StoredUndoableBlock undoableBlock) throws BlockStoreException {
Preconditions.checkNotNull(blockMap, "MemoryFullPrunedBlockStore is closed");
- fullBlockMap.put(storedBlock.getHeader().getHash(), storedBlock.getHeight(), undoableBlock);
- blockMap.put(storedBlock.getHeader().getHash(), storedBlock);
+ Sha256Hash hash = storedBlock.getHeader().getHash();
+ fullBlockMap.put(hash, storedBlock.getHeight(), undoableBlock);
+ blockMap.put(hash, new StoredBlockAndWasUndoableFlag(storedBlock, true));
}
public synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException {
Preconditions.checkNotNull(blockMap, "MemoryFullPrunedBlockStore is closed");
- return blockMap.get(hash);
+ StoredBlockAndWasUndoableFlag storedBlock = blockMap.get(hash);
+ return storedBlock == null ? null : storedBlock.block;
+ }
+
+ public StoredBlock getOnceUndoableStoredBlock(Sha256Hash hash) throws BlockStoreException {
+ Preconditions.checkNotNull(blockMap, "MemoryFullPrunedBlockStore is closed");
+ StoredBlockAndWasUndoableFlag storedBlock = blockMap.get(hash);
+ return (storedBlock != null && storedBlock.wasUndoable) ? storedBlock.block : null;
}
public synchronized StoredUndoableBlock getUndoBlock(Sha256Hash hash) throws BlockStoreException {
@@ -372,6 +385,18 @@ public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore {
public synchronized void setChainHead(StoredBlock chainHead) throws BlockStoreException {
Preconditions.checkNotNull(blockMap, "MemoryFullPrunedBlockStore is closed");
this.chainHead = chainHead;
+ }
+
+ public StoredBlock getVerifiedChainHead() throws BlockStoreException {
+ Preconditions.checkNotNull(blockMap, "MemoryFullPrunedBlockStore is closed");
+ return verifiedChainHead;
+ }
+
+ public void setVerifiedChainHead(StoredBlock chainHead) throws BlockStoreException {
+ Preconditions.checkNotNull(blockMap, "MemoryFullPrunedBlockStore is closed");
+ this.verifiedChainHead = chainHead;
+ if (this.chainHead.getHeight() < chainHead.getHeight())
+ setChainHead(chainHead);
// Potential leak here if not all blocks get setChainHead'd
// Though the FullPrunedBlockStore allows for this, the current AbstractBlockChain will not do it.
fullBlockMap.removeByMultiKey(chainHead.getHeight() - fullStoreDepth);