3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-31 15:22:16 +00:00

Delete BoundedOverheadBlockStore.

Although it served us well for a while, it was never actually bounded overhead. Some of its code lives on in SPVBlockStore, which replaces it.
This commit is contained in:
Mike Hearn 2013-09-20 18:04:34 +02:00
parent e67b3e540e
commit 60688752c9
2 changed files with 0 additions and 443 deletions

View File

@ -1,366 +0,0 @@
/**
* Copyright 2011 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import static com.google.common.base.Preconditions.checkState;
/**
* <p></p>Stores the block chain to disk.<p>
*
* <p>This class is deprecated and has been replaced with {@link SPVBlockStore}, which does the same thing but faster
* and using bounded disk space. The primary difference is that BoundedOverheadBlockStore stores all headers, whereas
* SPVBlockStore uses a ring buffer and discards older headers that are buried so deep they're unlikely to ever be
* needed to process a re-org.</p>
*
* <p>This class will eventually be deleted.</p>
*/
@Deprecated
public class BoundedOverheadBlockStore implements BlockStore {
private static final Logger log = LoggerFactory.getLogger(BoundedOverheadBlockStore.class);
private static final byte FILE_FORMAT_VERSION = 1;
private RandomAccessFile file;
// We keep some recently found blocks in the blockCache. It can help to optimize some cases where we are
// looking up blocks we recently stored or requested. When the cache gets too big older entries are deleted.
private LinkedHashMap<Sha256Hash, StoredBlock> blockCache = new LinkedHashMap<Sha256Hash, StoredBlock>() {
@Override
protected boolean removeEldestEntry(Map.Entry<Sha256Hash, StoredBlock> entry) {
return size() > 2050; // Slightly more than the difficulty transition period.
}
};
// Use a separate cache to track get() misses. This is to efficiently handle the case of an unconnected block
// during chain download. Each new block will do a get() on the unconnected block so if we haven't seen it yet we
// must efficiently respond.
//
// We don't care about the value in this cache. It is always notFoundMarker. Unfortunately LinkedHashSet does not
// provide the removeEldestEntry control.
private static final StoredBlock notFoundMarker = new StoredBlock(null, null, -1);
private LinkedHashMap<Sha256Hash, StoredBlock> notFoundCache = new LinkedHashMap<Sha256Hash, StoredBlock>() {
@Override
protected boolean removeEldestEntry(Map.Entry<Sha256Hash, StoredBlock> entry) {
return size() > 100; // This was chosen arbitrarily.
}
};
private Sha256Hash chainHead;
private final NetworkParameters params;
private FileChannel channel;
private FileLock lock;
private String fileName;
private static class Record {
// A BigInteger representing the total amount of work done so far on this chain. As of May 2011 it takes 8
// bytes to represent this field, so 16 bytes should be plenty for a long time.
private static final int CHAIN_WORK_BYTES = 16;
private static final byte[] EMPTY_BYTES = new byte[CHAIN_WORK_BYTES];
private int height; // 4 bytes
private byte[] chainWork; // 16 bytes
private byte[] blockHeader; // 80 bytes
public static final int SIZE = 4 + Record.CHAIN_WORK_BYTES + Block.HEADER_SIZE;
public Record() {
height = 0;
chainWork = new byte[CHAIN_WORK_BYTES];
blockHeader = new byte[Block.HEADER_SIZE];
}
public static void write(FileChannel channel, StoredBlock block) throws IOException {
ByteBuffer buf = ByteBuffer.allocate(Record.SIZE);
buf.putInt(block.getHeight());
byte[] chainWorkBytes = block.getChainWork().toByteArray();
checkState(chainWorkBytes.length <= CHAIN_WORK_BYTES, "Ran out of space to store chain work!");
if (chainWorkBytes.length < CHAIN_WORK_BYTES) {
// Pad to the right size.
buf.put(EMPTY_BYTES, 0, CHAIN_WORK_BYTES - chainWorkBytes.length);
}
buf.put(chainWorkBytes);
buf.put(block.getHeader().cloneAsHeader().bitcoinSerialize());
buf.position(0);
channel.position(channel.size());
if (channel.write(buf) < Record.SIZE)
throw new IOException("Failed to write record!");
channel.position(channel.size() - Record.SIZE);
}
public boolean read(FileChannel channel, long position, ByteBuffer buffer) throws IOException {
buffer.position(0);
long bytesRead = channel.read(buffer, position);
if (bytesRead < Record.SIZE)
return false;
buffer.position(0);
height = buffer.getInt();
buffer.get(chainWork);
buffer.get(blockHeader);
return true;
}
public BigInteger getChainWork() {
return new BigInteger(1, chainWork);
}
public Block getHeader(NetworkParameters params) throws ProtocolException {
return new Block(params, blockHeader);
}
public int getHeight() {
return height;
}
public StoredBlock toStoredBlock(NetworkParameters params) throws ProtocolException {
return new StoredBlock(getHeader(params), getChainWork(), getHeight());
}
}
public BoundedOverheadBlockStore(NetworkParameters params, File file) throws BlockStoreException {
this.params = params;
try {
this.fileName = file.getCanonicalPath();
} catch (IOException e) {
throw new RuntimeException(e);
}
if (file.exists()) {
try {
load(file);
return;
} catch (IOException e) {
log.error("Failed to load block chain from " + file, e);
// Fall through and try to create a new one.
}
}
createNewStore(params, file);
}
private void createNewStore(NetworkParameters params, File file) throws BlockStoreException {
// Create a new block store if the file wasn't found or anything went wrong whilst reading.
blockCache.clear();
try {
if (file.exists()) {
if (!file.delete())
throw new BlockStoreException("Could not delete old store in order to recreate it");
}
// Create fresh. The d makes writes synchronous.
this.file = new RandomAccessFile(file, "rwd");
this.channel = this.file.getChannel();
lock();
this.file.write(FILE_FORMAT_VERSION);
} catch (IOException e1) {
// We could not load a block store nor could we create a new one!
throw new BlockStoreException(e1);
}
try {
// Set up the genesis block. When we start out fresh, it is by definition the top of the chain.
Block genesis = params.getGenesisBlock().cloneAsHeader();
StoredBlock storedGenesis = new StoredBlock(genesis, genesis.getWork(), 0);
this.chainHead = storedGenesis.getHeader().getHash();
this.file.write(this.chainHead.getBytes());
put(storedGenesis);
} catch (VerificationException e1) {
throw new RuntimeException(e1); // Cannot happen.
} catch (IOException e) {
throw new BlockStoreException(e);
}
}
private void load(File file) throws IOException, BlockStoreException {
log.info("Reading block store from {}", file);
// Open in synchronous mode. See above.
this.file = new RandomAccessFile(file, "rwd");
channel = this.file.getChannel();
lock();
try {
// Read a version byte.
int version = this.file.read();
if (version == -1) {
// No such file or the file was empty.
close();
throw new FileNotFoundException(file.getName() + " does not exist or is empty");
}
if (version != FILE_FORMAT_VERSION) {
throw new BlockStoreException("Bad version number: " + version);
}
// Chain head pointer is the first thing in the file.
byte[] chainHeadHash = new byte[32];
if (this.file.read(chainHeadHash) < chainHeadHash.length)
throw new BlockStoreException("Truncated store: could not read chain head hash.");
this.chainHead = new Sha256Hash(chainHeadHash);
log.info("Read chain head from disk: {}", this.chainHead);
channel.position(channel.size() - Record.SIZE);
} catch (IOException e) {
if (this.file != null)
this.file.close();
throw e;
} catch (BlockStoreException e) {
this.file.close();
throw e;
}
}
private void lock() throws IOException, BlockStoreException {
try {
lock = channel.tryLock();
} catch (OverlappingFileLockException e) {
lock = null;
}
if (lock == null) {
try {
this.file.close();
} finally {
this.file = null;
}
throw new BlockStoreException("Could not lock file");
}
}
private void ensureOpen() throws BlockStoreException {
if (file == null) {
throw new BlockStoreException("BlockStore was closed");
}
}
public synchronized void put(StoredBlock block) throws BlockStoreException {
ensureOpen();
try {
Sha256Hash hash = block.getHeader().getHash();
// Append to the end of the file.
Record.write(channel, block);
blockCache.put(hash, block);
} catch (IOException e) {
throw new BlockStoreException(e);
}
}
public synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException {
ensureOpen();
// Check the memory cache first.
StoredBlock fromMem = blockCache.get(hash);
if (fromMem != null) {
return fromMem;
}
if (notFoundCache.get(hash) == notFoundMarker) {
return null;
}
try {
Record fromDisk = getRecord(hash);
StoredBlock block = null;
if (fromDisk == null) {
notFoundCache.put(hash, notFoundMarker);
} else {
block = fromDisk.toStoredBlock(params);
blockCache.put(hash, block);
}
return block;
} catch (IOException e) {
throw new BlockStoreException(e);
} catch (ProtocolException e) {
throw new BlockStoreException(e);
}
}
private ByteBuffer buf = ByteBuffer.allocateDirect(Record.SIZE);
private Record getRecord(Sha256Hash hash) throws IOException, ProtocolException {
long startPos = channel.position();
// Use our own file pointer within the tight loop as updating channel positions is really expensive.
long pos = startPos;
Record record = new Record();
int numMoves = 0;
long startTime = new Date().getTime();
do {
if (!record.read(channel, pos, buf))
throw new IOException("Failed to read buffer");
if (record.getHeader(params).getHash().equals(hash)) {
// Found it. Update file position for next time.
channel.position(pos);
long endTime = new Date().getTime();
if (endTime - startTime > 100) {
log.info("Spent {} seconds doing {} backwards seeks", (endTime - startTime) / 1000.0, numMoves);
}
return record;
}
// Did not find it.
if (pos == 1 + 32) {
// At the start so wrap around to the end.
pos = channel.size() - Record.SIZE;
} else {
// Move backwards.
pos = pos - Record.SIZE;
checkState(pos >= 1 + 32, pos);
}
numMoves++;
} while (pos != startPos);
// Was never stored.
channel.position(pos);
long endTime = new Date().getTime();
if (endTime - startTime > 1000) {
log.info("Spent {} seconds doing {} backwards seeks", (endTime - startTime) / 1000.0, numMoves);
}
return null;
}
public synchronized StoredBlock getChainHead() throws BlockStoreException {
ensureOpen();
// This will hit the cache
StoredBlock head = get(chainHead);
if (head == null)
throw new BlockStoreException("Corrupted block store: chain head not found");
return head;
}
public synchronized void setChainHead(StoredBlock chainHead) throws BlockStoreException {
ensureOpen();
try {
this.chainHead = chainHead.getHeader().getHash();
// Write out new hash to the first 32 bytes of the file past one (first byte is version number).
channel.write(ByteBuffer.wrap(this.chainHead.getBytes()), 1);
} catch (IOException e) {
throw new BlockStoreException(e);
}
}
public void close() throws BlockStoreException {
ensureOpen();
try {
file.close();
} catch (IOException e) {
throw new BlockStoreException(e);
} finally {
file = null;
}
}
}

View File

@ -1,77 +0,0 @@
/**
* Copyright 2011 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.Address;
import com.google.bitcoin.core.ECKey;
import com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.core.StoredBlock;
import com.google.bitcoin.params.UnitTestParams;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@SuppressWarnings("deprecation")
public class BoundedOverheadBlockStoreTest {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void testStorage() throws Exception {
File temp = folder.newFile("bitcoinj-test");
System.out.println(temp.getAbsolutePath());
NetworkParameters params = UnitTestParams.get();
Address to = new ECKey().toAddress(params);
BoundedOverheadBlockStore store = new BoundedOverheadBlockStore(params, temp);
// Check the first block in a new store is the genesis block.
StoredBlock genesis = store.getChainHead();
assertEquals(params.getGenesisBlock(), genesis.getHeader());
// Build a new block.
StoredBlock b1 = genesis.build(genesis.getHeader().createNextBlock(to).cloneAsHeader());
store.put(b1);
store.setChainHead(b1);
store.close();
// Check we can get it back out again if we rebuild the store object.
store = new BoundedOverheadBlockStore(params, temp);
StoredBlock b2 = store.get(b1.getHeader().getHash());
assertEquals(b1, b2);
// Check the chain head was stored correctly also.
assertEquals(b1, store.getChainHead());
}
@Test
public void testLocking() throws Exception {
File temp = folder.newFile("bitcoinj-test");
System.out.println(temp.getAbsolutePath());
NetworkParameters params = UnitTestParams.get();
BoundedOverheadBlockStore store = new BoundedOverheadBlockStore(params, temp);
try {
BoundedOverheadBlockStore store1 = new BoundedOverheadBlockStore(params, temp);
fail();
} catch (BlockStoreException e) {
// Expected
}
}
}