mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-01-30 23:02:15 +00:00
Add a FullPrunedBlockStore interface and in-memory implementation.
This commit is contained in:
parent
548333bc6f
commit
03d8c71df3
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2012 Matt Corallo.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.bitcoin.store;
|
||||
|
||||
import com.google.bitcoin.core.Sha256Hash;
|
||||
import com.google.bitcoin.core.StoredBlock;
|
||||
import com.google.bitcoin.core.StoredTransactionOutput;
|
||||
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.
|
||||
* 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>
|
||||
*
|
||||
* <p>It must store the {@link StoredBlock} of all blocks.</p>
|
||||
*
|
||||
* <p>A FullPrunedBlockStore contains a map of hashes to [Full]StoredBlock. The hash is the double digest of the
|
||||
* Bitcoin serialization of the block header, <b>not</b> the header with the extra data as well.</p>
|
||||
*
|
||||
* <p>A FullPrunedBlockStore also contains a map of hash+index to StoredTransactionOutput. Again, the hash is
|
||||
* a standard Bitcoin double-SHA256 hash of the transaction.</p>
|
||||
*
|
||||
* <p>FullPrunedBlockStores are thread safe.</p>
|
||||
*/
|
||||
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
|
||||
* @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 a {@link StoredUndoableBlock} who's block.getHash() method will be equal to the
|
||||
* parameter. If no such block is found, returns null.
|
||||
* Note that this may return null more often than get(Sha256Hash hash) as not all {@link StoredBlock}s have a
|
||||
* {@link StoredUndoableBlock} copy stored as well.
|
||||
*/
|
||||
StoredUndoableBlock getUndoBlock(Sha256Hash hash) throws BlockStoreException;
|
||||
|
||||
/**
|
||||
* Gets a {@link StoredTransactionOutput} with the given hash and index, or null if none is found
|
||||
*/
|
||||
StoredTransactionOutput getTransactionOutput(Sha256Hash hash, long index) throws BlockStoreException;
|
||||
|
||||
/**
|
||||
* Adds a {@link StoredTransactionOutput} to the list of unspent TransactionOutputs
|
||||
*/
|
||||
void addUnspentTransactionOutput(StoredTransactionOutput out) throws BlockStoreException;
|
||||
|
||||
/**
|
||||
* Removes a {@link StoredTransactionOutput} from the list of unspent TransactionOutputs
|
||||
* @throws BlockStoreException if there is an underlying storage issue, or out was not in the list.
|
||||
*/
|
||||
void removeUnspentTransactionOutput(StoredTransactionOutput out) throws BlockStoreException;
|
||||
|
||||
/**
|
||||
* True if this store has any unspent outputs from a transaction with a hash equal to the first parameter
|
||||
* @param numOutputs the number of outputs the given transaction has
|
||||
*/
|
||||
boolean hasUnspentOutputs(Sha256Hash hash, int numOutputs) throws BlockStoreException;
|
||||
|
||||
/**
|
||||
* <p>Begins/Commits/Aborts a database transaction.</p>
|
||||
*
|
||||
* <p>If abortDatabaseBatchWrite() is called by the same thread that called beginDatabaseBatchWrite(),
|
||||
* any data writes between this call and abortDatabaseBatchWrite() made by the same thread
|
||||
* should be discarded.</p>
|
||||
*
|
||||
* <p>Furthermore, any data written after a call to beginDatabaseBatchWrite() should not be readable
|
||||
* by any other threads until commitDatabaseBatchWrite() has been called by this thread.
|
||||
* Multiple calls to beginDatabaseBatchWrite() in any given thread should be ignored and treated as one call.</p>
|
||||
*/
|
||||
void beginDatabaseBatchWrite() throws BlockStoreException;
|
||||
void commitDatabaseBatchWrite() throws BlockStoreException;
|
||||
void abortDatabaseBatchWrite() throws BlockStoreException;
|
||||
}
|
@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.bitcoin.store;
|
||||
|
||||
import com.google.bitcoin.core.*;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/**
|
||||
* Used as a key for memory map (to avoid having to think about NetworkParameters,
|
||||
* which is required for {@link TransactionOutPoint}
|
||||
*/
|
||||
class StoredTransactionOutPoint implements Serializable {
|
||||
private static final long serialVersionUID = -4064230006297064377L;
|
||||
|
||||
/** Hash of the transaction to which we refer. */
|
||||
Sha256Hash hash;
|
||||
/** Which output of that transaction we are talking about. */
|
||||
long index;
|
||||
|
||||
StoredTransactionOutPoint(Sha256Hash hash, long index) {
|
||||
this.hash = hash;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
StoredTransactionOutPoint(StoredTransactionOutput out) {
|
||||
this.hash = out.getHash();
|
||||
this.index = out.getIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* The hash of the transaction to which we refer
|
||||
*/
|
||||
Sha256Hash getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* The index of the output in transaction to which we refer
|
||||
*/
|
||||
long getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return this.hash.hashCode() + (int)index;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Stored transaction out point: " + hash.toString() + ":" + index;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof StoredTransactionOutPoint)) return false;
|
||||
return ((StoredTransactionOutPoint)o).getIndex() == this.index &&
|
||||
Objects.equal(this.getHash(), ((StoredTransactionOutPoint)o).getHash());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A HashMap<KeyType, ValueType> that is DB transaction-aware
|
||||
* This class is not thread-safe.
|
||||
*/
|
||||
class TransactionalHashMap<KeyType, ValueType> {
|
||||
ThreadLocal<HashMap<KeyType, ValueType>> tempMap;
|
||||
ThreadLocal<HashSet<KeyType>> tempSetRemoved;
|
||||
private ThreadLocal<Boolean> inTransaction;
|
||||
|
||||
HashMap<KeyType, ValueType> map;
|
||||
|
||||
public TransactionalHashMap() {
|
||||
tempMap = new ThreadLocal<HashMap<KeyType, ValueType>>();
|
||||
tempSetRemoved = new ThreadLocal<HashSet<KeyType>>();
|
||||
inTransaction = new ThreadLocal<Boolean>();
|
||||
map = new HashMap<KeyType, ValueType>();
|
||||
}
|
||||
|
||||
public void beginDatabaseBatchWrite() {
|
||||
inTransaction.set(true);
|
||||
}
|
||||
|
||||
public void commitDatabaseBatchWrite() {
|
||||
if (tempSetRemoved.get() != null)
|
||||
for(KeyType key : tempSetRemoved.get())
|
||||
map.remove(key);
|
||||
if (tempMap.get() != null)
|
||||
for (Map.Entry<KeyType, ValueType> entry : tempMap.get().entrySet())
|
||||
map.put(entry.getKey(), entry.getValue());
|
||||
abortDatabaseBatchWrite();
|
||||
}
|
||||
|
||||
public void abortDatabaseBatchWrite() {
|
||||
inTransaction.set(false);
|
||||
tempSetRemoved.remove();
|
||||
tempMap.remove();
|
||||
}
|
||||
|
||||
public ValueType get(KeyType key) {
|
||||
if (Boolean.TRUE.equals(inTransaction.get())) {
|
||||
if (tempMap.get() != null) {
|
||||
ValueType value = tempMap.get().get(key);
|
||||
if (value != null)
|
||||
return value;
|
||||
}
|
||||
if (tempSetRemoved.get() != null && tempSetRemoved.get().contains(key))
|
||||
return null;
|
||||
}
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
public void put(KeyType key, ValueType value) {
|
||||
if (Boolean.TRUE.equals(inTransaction.get())) {
|
||||
if (tempSetRemoved.get() != null)
|
||||
tempSetRemoved.get().remove(key);
|
||||
if (tempMap.get() == null)
|
||||
tempMap.set(new HashMap<KeyType, ValueType>());
|
||||
tempMap.get().put(key, value);
|
||||
}else{
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public ValueType remove(KeyType key) {
|
||||
if (Boolean.TRUE.equals(inTransaction.get())) {
|
||||
ValueType retVal = map.get(key);
|
||||
if (retVal != null) {
|
||||
if (tempSetRemoved.get() == null)
|
||||
tempSetRemoved.set(new HashSet<KeyType>());
|
||||
tempSetRemoved.get().add(key);
|
||||
}
|
||||
if (tempMap.get() != null) {
|
||||
ValueType tempVal = tempMap.get().remove(key);
|
||||
if (tempVal != null)
|
||||
return tempVal;
|
||||
}
|
||||
return retVal;
|
||||
}else{
|
||||
return map.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Map with multiple key types that is DB per-thread-transaction-aware.
|
||||
* However, this class is not thread-safe.
|
||||
* @param UniqueKeyType is a key that must be unique per object
|
||||
* @param MultiKeyType is a key that can have multiple values
|
||||
*/
|
||||
class TransactionalMultiKeyHashMap<UniqueKeyType, MultiKeyType, ValueType> {
|
||||
TransactionalHashMap<UniqueKeyType, ValueType> mapValues;
|
||||
HashMap<MultiKeyType, Set<UniqueKeyType>> mapKeys;
|
||||
|
||||
public TransactionalMultiKeyHashMap() {
|
||||
mapValues = new TransactionalHashMap<UniqueKeyType, ValueType>();
|
||||
mapKeys = new HashMap<MultiKeyType, Set<UniqueKeyType>>();
|
||||
}
|
||||
|
||||
public void BeginTransaction() {
|
||||
mapValues.beginDatabaseBatchWrite();
|
||||
}
|
||||
|
||||
public void CommitTransaction() {
|
||||
mapValues.commitDatabaseBatchWrite();
|
||||
}
|
||||
|
||||
public void AbortTransaction() {
|
||||
mapValues.abortDatabaseBatchWrite();
|
||||
}
|
||||
|
||||
public ValueType get(UniqueKeyType key) {
|
||||
return mapValues.get(key);
|
||||
}
|
||||
|
||||
public void put(UniqueKeyType uniqueKey, MultiKeyType multiKey, ValueType value) {
|
||||
mapValues.put(uniqueKey, value);
|
||||
Set<UniqueKeyType> set = mapKeys.get(multiKey);
|
||||
if (set == null) {
|
||||
set = new HashSet<UniqueKeyType>();
|
||||
set.add(uniqueKey);
|
||||
mapKeys.put(multiKey, set);
|
||||
}else{
|
||||
set.add(uniqueKey);
|
||||
}
|
||||
}
|
||||
|
||||
public ValueType removeByUniqueKey(UniqueKeyType key) {
|
||||
return mapValues.remove(key);
|
||||
}
|
||||
|
||||
public void removeByMultiKey(MultiKeyType key) {
|
||||
Set<UniqueKeyType> set = mapKeys.remove(key);
|
||||
if (set != null)
|
||||
for (UniqueKeyType uniqueKey : set)
|
||||
removeByUniqueKey(uniqueKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps {@link StoredBlock}s, {@link StoredUndoableBlock}s and {@link StoredTransactionOutput}s in memory.
|
||||
* Used primarily for unit testing.
|
||||
*/
|
||||
public class MemoryFullPrunedBlockStore implements FullPrunedBlockStore {
|
||||
private TransactionalHashMap<Sha256Hash, StoredBlock> 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 int fullStoreDepth;
|
||||
|
||||
/**
|
||||
* Set up the MemoryFullPrunedBlockStore
|
||||
* @param params The network parameters of this block store - used to get genesis block
|
||||
* @param fullStoreDepth The depth of blocks to keep FullStoredBlocks instead of StoredBlocks
|
||||
*/
|
||||
public MemoryFullPrunedBlockStore(NetworkParameters params, int fullStoreDepth) {
|
||||
blockMap = new TransactionalHashMap<Sha256Hash, StoredBlock>();
|
||||
fullBlockMap = new TransactionalMultiKeyHashMap<Sha256Hash, Integer, StoredUndoableBlock>();
|
||||
transactionOutputMap = new TransactionalHashMap<StoredTransactionOutPoint, StoredTransactionOutput>();
|
||||
this.fullStoreDepth = fullStoreDepth > 0 ? fullStoreDepth : 1;
|
||||
// Insert the genesis block.
|
||||
try {
|
||||
StoredBlock storedGenesisHeader = new StoredBlock(params.genesisBlock.cloneAsHeader(), params.genesisBlock.getWork(), 0);
|
||||
|
||||
LinkedList<StoredTransaction> genesisTransactions = new LinkedList<StoredTransaction>();
|
||||
for (Transaction tx : params.genesisBlock.getTransactions())
|
||||
genesisTransactions.add(new StoredTransaction(tx, 0));
|
||||
StoredUndoableBlock storedGenesis = new StoredUndoableBlock(params.genesisBlock.getHash(), genesisTransactions);
|
||||
|
||||
put(storedGenesisHeader, storedGenesis);
|
||||
setChainHead(storedGenesisHeader);
|
||||
} catch (BlockStoreException e) {
|
||||
throw new RuntimeException(e); // Cannot happen.
|
||||
} catch (VerificationException e) {
|
||||
throw new RuntimeException(e); // Cannot happen.
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException {
|
||||
Preconditions.checkNotNull(blockMap, "MemoryFullPrunedBlockStore is closed");
|
||||
return blockMap.get(hash);
|
||||
}
|
||||
|
||||
public synchronized StoredUndoableBlock getUndoBlock(Sha256Hash hash) throws BlockStoreException {
|
||||
Preconditions.checkNotNull(fullBlockMap, "MemoryFullPrunedBlockStore is closed");
|
||||
return fullBlockMap.get(hash);
|
||||
}
|
||||
|
||||
public StoredBlock getChainHead() throws BlockStoreException {
|
||||
Preconditions.checkNotNull(blockMap, "MemoryFullPrunedBlockStore is closed");
|
||||
return chainHead;
|
||||
}
|
||||
|
||||
public synchronized void setChainHead(StoredBlock chainHead) throws BlockStoreException {
|
||||
Preconditions.checkNotNull(blockMap, "MemoryFullPrunedBlockStore is closed");
|
||||
this.chainHead = 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);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
blockMap = null;
|
||||
fullBlockMap = null;
|
||||
transactionOutputMap = null;
|
||||
}
|
||||
|
||||
public synchronized StoredTransactionOutput getTransactionOutput(Sha256Hash hash, long index) throws BlockStoreException {
|
||||
Preconditions.checkNotNull(transactionOutputMap, "MemoryFullPrunedBlockStore is closed");
|
||||
return transactionOutputMap.get(new StoredTransactionOutPoint(hash, index));
|
||||
}
|
||||
|
||||
public synchronized void addUnspentTransactionOutput(StoredTransactionOutput out) throws BlockStoreException {
|
||||
Preconditions.checkNotNull(transactionOutputMap, "MemoryFullPrunedBlockStore is closed");
|
||||
transactionOutputMap.put(new StoredTransactionOutPoint(out), out);
|
||||
}
|
||||
|
||||
public synchronized void removeUnspentTransactionOutput(StoredTransactionOutput out) throws BlockStoreException {
|
||||
Preconditions.checkNotNull(transactionOutputMap, "MemoryFullPrunedBlockStore is closed");
|
||||
if (transactionOutputMap.remove(new StoredTransactionOutPoint(out)) == null)
|
||||
throw new BlockStoreException("Tried to remove a StoredTransactionOutput from MemoryFullPrunedBlockStore that it didn't have!");
|
||||
}
|
||||
|
||||
public synchronized void beginDatabaseBatchWrite() throws BlockStoreException {
|
||||
blockMap.beginDatabaseBatchWrite();
|
||||
fullBlockMap.BeginTransaction();
|
||||
transactionOutputMap.beginDatabaseBatchWrite();
|
||||
}
|
||||
|
||||
public synchronized void commitDatabaseBatchWrite() throws BlockStoreException {
|
||||
blockMap.commitDatabaseBatchWrite();
|
||||
fullBlockMap.CommitTransaction();
|
||||
transactionOutputMap.commitDatabaseBatchWrite();
|
||||
}
|
||||
|
||||
public synchronized void abortDatabaseBatchWrite() throws BlockStoreException {
|
||||
blockMap.abortDatabaseBatchWrite();
|
||||
fullBlockMap.AbortTransaction();
|
||||
transactionOutputMap.abortDatabaseBatchWrite();
|
||||
}
|
||||
|
||||
public boolean hasUnspentOutputs(Sha256Hash hash, int numOutputs) throws BlockStoreException {
|
||||
for (int i = 0; i < numOutputs; i++)
|
||||
if (getTransactionOutput(hash, i) != null)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user