mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-07 06:44:16 +00:00
Make FullPrunedBlockStore/Chain upgrade-friendly.
This does several things to support the ability to upgrade from a BlockChain to a FullPrunedBlockChain backed by the same FullPrunedBlockStore: * Add two methods to FullPrunedBlockStore to allow it to keep track of two chain heads - one verified, one normal. * Change preSetChainHead in AbstractBlockChain to doSetChainHead so that FullPrunedBlockChain and BlockChain can set only the appropriate chain head * Add FullPrunedBlockStore.getOnceUndoableStoredBlock. This allows a BlockChain to request only StoredBlocks which were, at one point, associated with a StoredUndoableBlock, effectively splitting FullPrunedBlockStore into two independent BlockStores (one FullPruned, one normal). * Add/use AbstractBlockChain.getStoredBlockInCurrentScope which relies on the above additions to FullPrunedBlockStore to properly utilize one FullPrunedBlockStore as two separate block stores depending on the BlockChain instance used. Note that this is not used everywhere in AbstractBlockChain as, barring something being insanely broken, the prev block of a block in current scope will be in current scope.
This commit is contained in:
parent
4e0f5d8903
commit
95b5e0d894
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -24,8 +24,17 @@ import com.google.bitcoin.core.StoredUndoableBlock;
|
||||
/**
|
||||
* <p>An implementor of FullPrunedBlockStore saves StoredBlock objects to some storage mechanism.</p>
|
||||
*
|
||||
* <p>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.
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>A FullPrunedBlockStore should function well as a standard {@link BlockStore} and then be able to
|
||||
* trivially switch to being used as a FullPrunedBlockStore.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
@ -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
|
||||
* <p>Saves the given {@link StoredUndoableBlock} and {@link StoredBlock}. Calculates keys from the {@link StoredBlock}</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* <p>Begins/Commits/Aborts a database transaction.</p>
|
||||
*
|
||||
|
@ -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<Connection> conn;
|
||||
private List<Connection> 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);
|
||||
}
|
||||
|
||||
|
@ -308,11 +308,17 @@ class TransactionalMultiKeyHashMap<UniqueKeyType, MultiKeyType, ValueType> {
|
||||
* Used primarily for unit testing.
|
||||
*/
|
||||
public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore {
|
||||
private TransactionalHashMap<Sha256Hash, StoredBlock> blockMap;
|
||||
class StoredBlockAndWasUndoableFlag {
|
||||
public StoredBlock block;
|
||||
public boolean wasUndoable;
|
||||
public StoredBlockAndWasUndoableFlag(StoredBlock block, boolean wasUndoable) { this.block = block; this.wasUndoable = wasUndoable; }
|
||||
}
|
||||
private TransactionalHashMap<Sha256Hash, StoredBlockAndWasUndoableFlag> blockMap;
|
||||
private TransactionalMultiKeyHashMap<Sha256Hash, Integer, StoredUndoableBlock> fullBlockMap;
|
||||
//TODO: Use something more suited to remove-heavy use?
|
||||
private TransactionalHashMap<StoredTransactionOutPoint, StoredTransactionOutput> 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<Sha256Hash, StoredBlock>();
|
||||
blockMap = new TransactionalHashMap<Sha256Hash, StoredBlockAndWasUndoableFlag>();
|
||||
fullBlockMap = new TransactionalMultiKeyHashMap<Sha256Hash, Integer, StoredUndoableBlock>();
|
||||
transactionOutputMap = new TransactionalHashMap<StoredTransactionOutPoint, StoredTransactionOutput>();
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user