/**
   * 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();
    }
  }