diff --git a/core/src/main/java/com/google/bitcoin/store/BoundedOverheadBlockStore.java b/core/src/main/java/com/google/bitcoin/store/BoundedOverheadBlockStore.java deleted file mode 100644 index c45418ee..00000000 --- a/core/src/main/java/com/google/bitcoin/store/BoundedOverheadBlockStore.java +++ /dev/null @@ -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; - -/** - *

Stores the block chain to disk.

- * - *

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.

- * - *

This class will eventually be deleted.

- */ -@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 blockCache = new LinkedHashMap() { - @Override - protected boolean removeEldestEntry(Map.Entry 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 notFoundCache = new LinkedHashMap() { - @Override - protected boolean removeEldestEntry(Map.Entry 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; - } - } -} diff --git a/core/src/test/java/com/google/bitcoin/store/BoundedOverheadBlockStoreTest.java b/core/src/test/java/com/google/bitcoin/store/BoundedOverheadBlockStoreTest.java deleted file mode 100644 index c97f14b9..00000000 --- a/core/src/test/java/com/google/bitcoin/store/BoundedOverheadBlockStoreTest.java +++ /dev/null @@ -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 - } - } -}