mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-01-18 21:32:35 +01:00
Lock BlockStore files to prevent concurrent access.
Resolves issue 153.
This commit is contained in:
parent
7f6d636cec
commit
4b1c32584f
@ -52,4 +52,7 @@ public interface BlockStore {
|
||||
* Sets the {@link StoredBlock} that represents the top of the chain of greatest total work.
|
||||
*/
|
||||
void setChainHead(StoredBlock chainHead) throws BlockStoreException;
|
||||
|
||||
/** Closes the store. */
|
||||
void close() throws BlockStoreException;
|
||||
}
|
||||
|
@ -27,6 +27,8 @@ 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;
|
||||
@ -77,6 +79,7 @@ public class BoundedOverheadBlockStore implements BlockStore {
|
||||
private Sha256Hash chainHead;
|
||||
private final NetworkParameters params;
|
||||
private FileChannel channel;
|
||||
private FileLock lock;
|
||||
|
||||
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
|
||||
@ -192,6 +195,19 @@ public class BoundedOverheadBlockStore implements BlockStore {
|
||||
log.info("Reading block store from {}", file);
|
||||
// Open in synchronous mode. See above.
|
||||
this.file = new RandomAccessFile(file, "rwd");
|
||||
try {
|
||||
lock = this.file.getChannel().tryLock();
|
||||
} catch (OverlappingFileLockException e) {
|
||||
lock = null;
|
||||
}
|
||||
if (lock == null) {
|
||||
try {
|
||||
this.file.close();
|
||||
} finally {
|
||||
this.file = null;
|
||||
}
|
||||
throw new BlockStoreException("Could not lock file");
|
||||
}
|
||||
try {
|
||||
channel = this.file.getChannel();
|
||||
// Read a version byte.
|
||||
@ -219,7 +235,14 @@ public class BoundedOverheadBlockStore implements BlockStore {
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
@ -231,6 +254,7 @@ public class BoundedOverheadBlockStore implements BlockStore {
|
||||
}
|
||||
|
||||
public synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException {
|
||||
ensureOpen();
|
||||
// Check the memory cache first.
|
||||
StoredBlock fromMem = blockCache.get(hash);
|
||||
if (fromMem != null) {
|
||||
@ -259,7 +283,7 @@ public class BoundedOverheadBlockStore implements BlockStore {
|
||||
|
||||
private ByteBuffer buf = ByteBuffer.allocateDirect(Record.SIZE);
|
||||
|
||||
private Record getRecord(Sha256Hash hash) throws BlockStoreException, IOException, ProtocolException {
|
||||
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;
|
||||
@ -299,6 +323,7 @@ public class BoundedOverheadBlockStore implements BlockStore {
|
||||
}
|
||||
|
||||
public synchronized StoredBlock getChainHead() throws BlockStoreException {
|
||||
ensureOpen();
|
||||
// This will hit the cache
|
||||
StoredBlock head = get(chainHead);
|
||||
if (head == null)
|
||||
@ -307,6 +332,7 @@ public class BoundedOverheadBlockStore implements BlockStore {
|
||||
}
|
||||
|
||||
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).
|
||||
@ -315,4 +341,15 @@ public class BoundedOverheadBlockStore implements BlockStore {
|
||||
throw new BlockStoreException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws BlockStoreException {
|
||||
ensureOpen();
|
||||
try {
|
||||
file.close();
|
||||
} catch (IOException e) {
|
||||
throw new BlockStoreException(e);
|
||||
} finally {
|
||||
file = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -39,12 +41,27 @@ public class DiskBlockStore implements BlockStore {
|
||||
private Map<Sha256Hash, StoredBlock> blockMap;
|
||||
private Sha256Hash chainHead;
|
||||
private NetworkParameters params;
|
||||
private FileLock lock;
|
||||
|
||||
public DiskBlockStore(NetworkParameters params, File theFile) throws BlockStoreException {
|
||||
this.params = params;
|
||||
blockMap = new HashMap<Sha256Hash, StoredBlock>();
|
||||
try {
|
||||
file = new RandomAccessFile(theFile, "rwd");
|
||||
// Lock the file from other processes.
|
||||
try {
|
||||
lock = file.getChannel().tryLock();
|
||||
} catch (OverlappingFileLockException e) {
|
||||
lock = null;
|
||||
}
|
||||
if (lock == null) {
|
||||
try {
|
||||
this.file.close();
|
||||
} finally {
|
||||
this.file = null;
|
||||
}
|
||||
throw new BlockStoreException("Could not lock file");
|
||||
}
|
||||
// The file position is at BOF
|
||||
load(theFile);
|
||||
// The file position is at EOF
|
||||
@ -54,6 +71,17 @@ public class DiskBlockStore implements BlockStore {
|
||||
// The file position is at EOF
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws BlockStoreException {
|
||||
ensureOpen();
|
||||
try {
|
||||
file.close();
|
||||
} catch (IOException e) {
|
||||
throw new BlockStoreException(e);
|
||||
} finally {
|
||||
file = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void createNewStore(NetworkParameters params) throws BlockStoreException {
|
||||
// Create a new block store if the file wasn't found or anything went wrong whilst reading.
|
||||
@ -145,7 +173,14 @@ public class DiskBlockStore implements BlockStore {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
assert blockMap.get(hash) == null : "Attempt to insert duplicate";
|
||||
@ -159,14 +194,17 @@ public class DiskBlockStore implements BlockStore {
|
||||
}
|
||||
|
||||
public synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException {
|
||||
ensureOpen();
|
||||
return blockMap.get(hash);
|
||||
}
|
||||
|
||||
public synchronized StoredBlock getChainHead() throws BlockStoreException {
|
||||
ensureOpen();
|
||||
return blockMap.get(chainHead);
|
||||
}
|
||||
|
||||
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).
|
||||
|
@ -44,19 +44,27 @@ public class MemoryBlockStore implements BlockStore {
|
||||
}
|
||||
|
||||
public synchronized void put(StoredBlock block) throws BlockStoreException {
|
||||
if (blockMap == null) throw new BlockStoreException("MemoryBlockStore is closed");
|
||||
Sha256Hash hash = block.getHeader().getHash();
|
||||
blockMap.put(hash, block);
|
||||
}
|
||||
|
||||
public synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException {
|
||||
if (blockMap == null) throw new BlockStoreException("MemoryBlockStore is closed");
|
||||
return blockMap.get(hash);
|
||||
}
|
||||
|
||||
public StoredBlock getChainHead() {
|
||||
public StoredBlock getChainHead() throws BlockStoreException {
|
||||
if (blockMap == null) throw new BlockStoreException("MemoryBlockStore is closed");
|
||||
return chainHead;
|
||||
}
|
||||
|
||||
public void setChainHead(StoredBlock chainHead) throws BlockStoreException {
|
||||
if (blockMap == null) throw new BlockStoreException("MemoryBlockStore is closed");
|
||||
this.chainHead = chainHead;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
blockMap = null;
|
||||
}
|
||||
}
|
||||
|
@ -19,20 +19,32 @@ 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 org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class DiskBlockStoreTest {
|
||||
@Test
|
||||
public void testStorage() throws Exception {
|
||||
File temp = File.createTempFile("bitcoinj-test", null, null);
|
||||
private NetworkParameters params;
|
||||
private Address to;
|
||||
private File temp;
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
temp = File.createTempFile("bitcoinj-test", null, null);
|
||||
System.out.println(temp.getAbsolutePath());
|
||||
|
||||
NetworkParameters params = NetworkParameters.unitTests();
|
||||
Address to = new ECKey().toAddress(params);
|
||||
params = NetworkParameters.unitTests();
|
||||
to = new ECKey().toAddress(params);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStorage() throws Exception {
|
||||
DiskBlockStore store = new DiskBlockStore(params, temp);
|
||||
// Check the first block in a new store is the genesis block.
|
||||
StoredBlock genesis = store.getChainHead();
|
||||
@ -42,25 +54,31 @@ public class DiskBlockStoreTest {
|
||||
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 DiskBlockStore(params, temp);
|
||||
StoredBlock b2 = store.get(b1.getHeader().getHash());
|
||||
assertEquals(b1, b2);
|
||||
// Check the chain head was stored correctly also.
|
||||
assertEquals(b1, store.getChainHead());
|
||||
store.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStorage_existing() throws Exception {
|
||||
File temp = File.createTempFile("bitcoinj-test", null, null);
|
||||
System.out.println(temp.getAbsolutePath());
|
||||
|
||||
NetworkParameters params = NetworkParameters.unitTests();
|
||||
Address to = new ECKey().toAddress(params);
|
||||
DiskBlockStore store = new DiskBlockStore(params, temp);
|
||||
// Check the first block in a new store is the genesis block.
|
||||
|
||||
StoredBlock genesis = store.getChainHead();
|
||||
|
||||
// Test locking
|
||||
try {
|
||||
store = new DiskBlockStore(params, temp);
|
||||
fail();
|
||||
} catch (BlockStoreException ex) {
|
||||
// expected
|
||||
}
|
||||
store.close();
|
||||
|
||||
// Reopen.
|
||||
store = new DiskBlockStore(params, temp);
|
||||
@ -69,6 +87,7 @@ public class DiskBlockStoreTest {
|
||||
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 reopen the store.
|
||||
store = new DiskBlockStore(params, temp);
|
||||
@ -76,5 +95,6 @@ public class DiskBlockStoreTest {
|
||||
assertEquals(b1, b2);
|
||||
// Check the chain head was stored correctly also.
|
||||
assertEquals(b1, store.getChainHead());
|
||||
store.close();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user