3
0
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:
Andreas Schildbach 2018-03-22 22:33:06 +01:00
parent 37525e6eb7
commit 8663236de0
2 changed files with 66 additions and 18 deletions

View File

@ -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);
} }

View File

@ -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);
}
} }