/** * Creates and initializes an SPV block store. Will create the given file if it's missing. This * operation will block on disk. */ public SPVBlockStore(NetworkParameters params, File file) throws BlockStoreException { checkNotNull(file); this.params = checkNotNull(params); try { this.numHeaders = DEFAULT_NUM_HEADERS; boolean exists = file.exists(); // Set up the backing file. randomAccessFile = new RandomAccessFile(file, "rw"); long fileSize = getFileSize(); if (!exists) { log.info("Creating new SPV block chain file " + file); randomAccessFile.setLength(fileSize); } else if (randomAccessFile.length() != fileSize) { throw new BlockStoreException( "File size on disk does not match expected size: " + randomAccessFile.length() + " vs " + fileSize); } FileChannel channel = randomAccessFile.getChannel(); fileLock = channel.tryLock(); if (fileLock == null) throw new BlockStoreException("Store file is already locked by another process"); // Map it into memory read/write. The kernel will take care of flushing writes to disk at the // most // efficient times, which may mean that until the map is deallocated the data on disk is // randomly // 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 // the details of mmapping vary between platforms. buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize); // Check or initialize the header bytes to ensure we don't try to open some random file. byte[] header; if (exists) { header = new byte[4]; buffer.get(header); if (!new String(header, "US-ASCII").equals(HEADER_MAGIC)) throw new BlockStoreException("Header bytes do not equal " + HEADER_MAGIC); } else { initNewStore(params); } } catch (Exception e) { try { if (randomAccessFile != null) randomAccessFile.close(); } catch (IOException e2) { throw new BlockStoreException(e2); } throw new BlockStoreException(e); } }
@Override @Nullable public StoredBlock get(Sha256Hash hash) throws BlockStoreException { final MappedByteBuffer buffer = this.buffer; if (buffer == null) throw new BlockStoreException("Store closed"); lock.lock(); try { StoredBlock cacheHit = blockCache.get(hash); if (cacheHit != null) return cacheHit; if (notFoundCache.get(hash) != null) return null; // Starting from the current tip of the ring work backwards until we have either found the // block or // wrapped around. int cursor = getRingCursor(buffer); final int startingPoint = cursor; final int fileSize = getFileSize(); final byte[] targetHashBytes = hash.getBytes(); byte[] scratch = new byte[32]; do { cursor -= RECORD_SIZE; if (cursor < FILE_PROLOGUE_BYTES) { // We hit the start, so wrap around. cursor = fileSize - RECORD_SIZE; } // Cursor is now at the start of the next record to check, so read the hash and compare it. buffer.position(cursor); buffer.get(scratch); if (Arrays.equals(scratch, targetHashBytes)) { // Found the target. StoredBlock storedBlock = StoredBlock.deserializeCompact(params, buffer); blockCache.put(hash, storedBlock); return storedBlock; } } while (cursor != startingPoint); // Not found. notFoundCache.put(hash, notFoundMarker); return null; } catch (ProtocolException e) { throw new RuntimeException(e); // Cannot happen. } finally { lock.unlock(); } }
@Override public StoredBlock getChainHead() throws BlockStoreException { final MappedByteBuffer buffer = this.buffer; if (buffer == null) throw new BlockStoreException("Store closed"); lock.lock(); try { if (lastChainHead == null) { byte[] headHash = new byte[32]; buffer.position(8); buffer.get(headHash); Sha256Hash hash = new Sha256Hash(headHash); StoredBlock block = get(hash); if (block == null) throw new BlockStoreException( "Corrupted block store: could not find chain head: " + hash); lastChainHead = block; } return lastChainHead; } finally { lock.unlock(); } }