mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-15 11:45:51 +00:00
SPVBlockStore: Ability to grow the block store.
This commit is contained in:
parent
37525e6eb7
commit
8663236de0
@ -45,7 +45,6 @@ public class SPVBlockStore implements BlockStore {
|
|||||||
public static final String HEADER_MAGIC = "SPVB";
|
public static final String HEADER_MAGIC = "SPVB";
|
||||||
|
|
||||||
protected volatile MappedByteBuffer buffer;
|
protected volatile MappedByteBuffer buffer;
|
||||||
protected final int capacity;
|
|
||||||
protected final NetworkParameters params;
|
protected final NetworkParameters params;
|
||||||
|
|
||||||
protected ReentrantLock lock = Threading.lock("SPVBlockStore");
|
protected ReentrantLock lock = Threading.lock("SPVBlockStore");
|
||||||
@ -79,40 +78,53 @@ public class SPVBlockStore implements BlockStore {
|
|||||||
// Used to stop other applications/processes from opening the store.
|
// Used to stop other applications/processes from opening the store.
|
||||||
protected FileLock fileLock = null;
|
protected FileLock fileLock = null;
|
||||||
protected RandomAccessFile randomAccessFile = null;
|
protected RandomAccessFile randomAccessFile = null;
|
||||||
|
private int fileLength;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and initializes an SPV block store that can hold {@link #DEFAULT_CAPACITY} blocks. Will create the given
|
* Creates and initializes an SPV block store that can hold {@link #DEFAULT_CAPACITY} block headers. Will create the
|
||||||
* file if it's missing. This operation will block on disk.
|
* given file if it's missing. This operation will block on disk.
|
||||||
* @param file file to use for the block store
|
* @param file file to use for the block store
|
||||||
* @throws BlockStoreException if something goes wrong
|
* @throws BlockStoreException if something goes wrong
|
||||||
*/
|
*/
|
||||||
public SPVBlockStore(NetworkParameters params, File file) throws BlockStoreException {
|
public SPVBlockStore(NetworkParameters params, File file) throws BlockStoreException {
|
||||||
this(params, file, DEFAULT_CAPACITY);
|
this(params, file, DEFAULT_CAPACITY, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and initializes an SPV block store that can hold a given amount of blocks. Will create the given file if
|
* Creates and initializes an SPV block store that can hold a given amount of blocks. Will create the given file if
|
||||||
* it's missing. This operation will block on disk.
|
* it's missing. This operation will block on disk.
|
||||||
* @param file file to use for the block store
|
* @param file file to use for the block store
|
||||||
* @param capacity custom capacity
|
* @param capacity custom capacity in number of block headers
|
||||||
|
* @param wether or not to migrate an existing block store of different capacity
|
||||||
* @throws BlockStoreException if something goes wrong
|
* @throws BlockStoreException if something goes wrong
|
||||||
*/
|
*/
|
||||||
public SPVBlockStore(NetworkParameters params, File file, int capacity) throws BlockStoreException {
|
public SPVBlockStore(NetworkParameters params, File file, int capacity, boolean grow) throws BlockStoreException {
|
||||||
checkNotNull(file);
|
checkNotNull(file);
|
||||||
this.params = checkNotNull(params);
|
this.params = checkNotNull(params);
|
||||||
checkArgument(capacity > 0);
|
checkArgument(capacity > 0);
|
||||||
this.capacity = capacity;
|
|
||||||
try {
|
try {
|
||||||
boolean exists = file.exists();
|
boolean exists = file.exists();
|
||||||
// Set up the backing file.
|
// Set up the backing file.
|
||||||
randomAccessFile = new RandomAccessFile(file, "rw");
|
randomAccessFile = new RandomAccessFile(file, "rw");
|
||||||
long fileSize = getFileSize(capacity);
|
fileLength = getFileSize(capacity);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
log.info("Creating new SPV block chain file " + file);
|
log.info("Creating new SPV block chain file " + file);
|
||||||
randomAccessFile.setLength(fileSize);
|
randomAccessFile.setLength(fileLength);
|
||||||
} else if (randomAccessFile.length() != fileSize) {
|
} else {
|
||||||
throw new BlockStoreException("File size on disk does not match expected size: " +
|
final long currentLength = randomAccessFile.length();
|
||||||
randomAccessFile.length() + " vs " + fileSize);
|
if (currentLength != fileLength) {
|
||||||
|
if ((currentLength - FILE_PROLOGUE_BYTES) % RECORD_SIZE != 0)
|
||||||
|
throw new BlockStoreException(
|
||||||
|
"File size on disk indicates this is not a block store: " + currentLength);
|
||||||
|
else if (!grow)
|
||||||
|
throw new BlockStoreException("File size on disk does not match expected size: " + currentLength
|
||||||
|
+ " vs " + fileLength);
|
||||||
|
else if (fileLength < randomAccessFile.length())
|
||||||
|
throw new BlockStoreException(
|
||||||
|
"Shrinking is unsupported: " + currentLength + " vs " + fileLength);
|
||||||
|
else
|
||||||
|
randomAccessFile.setLength(fileLength);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileChannel channel = randomAccessFile.getChannel();
|
FileChannel channel = randomAccessFile.getChannel();
|
||||||
@ -125,12 +137,11 @@ public class SPVBlockStore implements BlockStore {
|
|||||||
// inconsistent. However the only process accessing it is us, via this mapping, so our own view will
|
// inconsistent. However the only process accessing it is us, via this mapping, so our own view will
|
||||||
// always be correct. Once we establish the mmap the underlying file and channel can go away. Note that
|
// always be correct. Once we establish the mmap the underlying file and channel can go away. Note that
|
||||||
// the details of mmapping vary between platforms.
|
// the details of mmapping vary between platforms.
|
||||||
buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);
|
buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, fileLength);
|
||||||
|
|
||||||
// Check or initialize the header bytes to ensure we don't try to open some random file.
|
// Check or initialize the header bytes to ensure we don't try to open some random file.
|
||||||
byte[] header;
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
header = new byte[4];
|
byte[] header = new byte[4];
|
||||||
buffer.get(header);
|
buffer.get(header);
|
||||||
if (!new String(header, StandardCharsets.US_ASCII).equals(HEADER_MAGIC))
|
if (!new String(header, StandardCharsets.US_ASCII).equals(HEADER_MAGIC))
|
||||||
throw new BlockStoreException("Header bytes do not equal " + HEADER_MAGIC);
|
throw new BlockStoreException("Header bytes do not equal " + HEADER_MAGIC);
|
||||||
@ -177,7 +188,7 @@ public class SPVBlockStore implements BlockStore {
|
|||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
int cursor = getRingCursor(buffer);
|
int cursor = getRingCursor(buffer);
|
||||||
if (cursor == getFileSize(capacity)) {
|
if (cursor == fileLength) {
|
||||||
// Wrapped around.
|
// Wrapped around.
|
||||||
cursor = FILE_PROLOGUE_BYTES;
|
cursor = FILE_PROLOGUE_BYTES;
|
||||||
}
|
}
|
||||||
@ -209,14 +220,13 @@ public class SPVBlockStore implements BlockStore {
|
|||||||
// wrapped around.
|
// wrapped around.
|
||||||
int cursor = getRingCursor(buffer);
|
int cursor = getRingCursor(buffer);
|
||||||
final int startingPoint = cursor;
|
final int startingPoint = cursor;
|
||||||
final int fileSize = getFileSize(capacity);
|
|
||||||
final byte[] targetHashBytes = hash.getBytes();
|
final byte[] targetHashBytes = hash.getBytes();
|
||||||
byte[] scratch = new byte[32];
|
byte[] scratch = new byte[32];
|
||||||
do {
|
do {
|
||||||
cursor -= RECORD_SIZE;
|
cursor -= RECORD_SIZE;
|
||||||
if (cursor < FILE_PROLOGUE_BYTES) {
|
if (cursor < FILE_PROLOGUE_BYTES) {
|
||||||
// We hit the start, so wrap around.
|
// We hit the start, so wrap around.
|
||||||
cursor = fileSize - RECORD_SIZE;
|
cursor = fileLength - RECORD_SIZE;
|
||||||
}
|
}
|
||||||
// Cursor is now at the start of the next record to check, so read the hash and compare it.
|
// Cursor is now at the start of the next record to check, so read the hash and compare it.
|
||||||
buffer.position(cursor);
|
buffer.position(cursor);
|
||||||
@ -283,6 +293,7 @@ public class SPVBlockStore implements BlockStore {
|
|||||||
}
|
}
|
||||||
buffer = null; // Allow it to be GCd and the underlying file mapping to go away.
|
buffer = null; // Allow it to be GCd and the underlying file mapping to go away.
|
||||||
randomAccessFile.close();
|
randomAccessFile.close();
|
||||||
|
blockCache.clear();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new BlockStoreException(e);
|
throw new BlockStoreException(e);
|
||||||
}
|
}
|
||||||
|
@ -78,4 +78,41 @@ public class SPVBlockStoreTest {
|
|||||||
store.close();
|
store.close();
|
||||||
store = new SPVBlockStore(UNITTEST, blockStoreFile);
|
store = new SPVBlockStore(UNITTEST, blockStoreFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = BlockStoreException.class)
|
||||||
|
public void twoStores_sequentially_butMismatchingCapacity() throws Exception {
|
||||||
|
SPVBlockStore store = new SPVBlockStore(UNITTEST, blockStoreFile, 10, false);
|
||||||
|
store.close();
|
||||||
|
store = new SPVBlockStore(UNITTEST, blockStoreFile, 20, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void twoStores_sequentially_grow() throws Exception {
|
||||||
|
Address to = LegacyAddress.fromKey(UNITTEST, new ECKey());
|
||||||
|
SPVBlockStore store = new SPVBlockStore(UNITTEST, blockStoreFile, 10, true);
|
||||||
|
final StoredBlock block0 = store.getChainHead();
|
||||||
|
final StoredBlock block1 = block0.build(block0.getHeader().createNextBlock(to).cloneAsHeader());
|
||||||
|
store.put(block1);
|
||||||
|
final StoredBlock block2 = block1.build(block1.getHeader().createNextBlock(to).cloneAsHeader());
|
||||||
|
store.put(block2);
|
||||||
|
store.setChainHead(block2);
|
||||||
|
store.close();
|
||||||
|
|
||||||
|
store = new SPVBlockStore(UNITTEST, blockStoreFile, 20, true);
|
||||||
|
final StoredBlock read2 = store.getChainHead();
|
||||||
|
assertEquals(block2, read2);
|
||||||
|
final StoredBlock read1 = read2.getPrev(store);
|
||||||
|
assertEquals(block1, read1);
|
||||||
|
final StoredBlock read0 = read1.getPrev(store);
|
||||||
|
assertEquals(block0, read0);
|
||||||
|
store.close();
|
||||||
|
assertEquals(SPVBlockStore.getFileSize(20), blockStoreFile.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = BlockStoreException.class)
|
||||||
|
public void twoStores_sequentially_shrink() throws Exception {
|
||||||
|
SPVBlockStore store = new SPVBlockStore(UNITTEST, blockStoreFile, 20, true);
|
||||||
|
store.close();
|
||||||
|
store = new SPVBlockStore(UNITTEST, blockStoreFile, 10, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user