public StoredBlock get(Sha256Hash hash, boolean wasUndoableOnly) throws BlockStoreException {
    // Optimize for chain head
    if (chainHeadHash != null && chainHeadHash.equals(hash)) return chainHeadBlock;
    if (verifiedChainHeadHash != null && verifiedChainHeadHash.equals(hash))
      return verifiedChainHeadBlock;
    maybeConnect();
    PreparedStatement s = null;
    try {
      s =
          conn.get()
              .prepareStatement(
                  "SELECT chainWork, height, header, wasUndoable FROM headers WHERE hash = ?");
      // We skip the first 4 bytes because (on prodnet) the minimum target has 4 0-bytes
      byte[] hashBytes = new byte[28];
      System.arraycopy(hash.getBytes(), 3, hashBytes, 0, 28);
      s.setBytes(1, hashBytes);
      ResultSet results = s.executeQuery();
      if (!results.next()) {
        return null;
      }
      // Parse it.

      if (wasUndoableOnly && !results.getBoolean(4)) return null;

      BigInteger chainWork = new BigInteger(results.getBytes(1));
      int height = results.getInt(2);
      Block b = new Block(params, results.getBytes(3));
      b.verifyHeader();
      StoredBlock stored = new StoredBlock(b, chainWork, height);
      return stored;
    } catch (SQLException ex) {
      throw new BlockStoreException(ex);
    } catch (ProtocolException e) {
      // Corrupted database.
      throw new BlockStoreException(e);
    } catch (VerificationException e) {
      // Should not be able to happen unless the database contains bad
      // blocks.
      throw new BlockStoreException(e);
    } finally {
      if (s != null)
        try {
          s.close();
        } catch (SQLException e) {
          throw new BlockStoreException("Failed to close PreparedStatement");
        }
    }
  }
  // filteredTxHashList contains all transactions, filteredTxn just a subset
  private boolean add(
      Block block,
      boolean tryConnecting,
      @Nullable List<Sha256Hash> filteredTxHashList,
      @Nullable Map<Sha256Hash, Transaction> filteredTxn)
      throws BlockStoreException, VerificationException, PrunedException {
    // TODO: Use read/write locks to ensure that during chain download properties are still low
    // latency.
    lock.lock();
    try {
      // Quick check for duplicates to avoid an expensive check further down (in findSplit). This
      // can happen a lot
      // when connecting orphan transactions due to the dumb brute force algorithm we use.
      if (block.equals(getChainHead().getHeader())) {
        return true;
      }
      if (tryConnecting && orphanBlocks.containsKey(block.getHash())) {
        return false;
      }

      // If we want to verify transactions (ie we are running with full blocks), verify that block
      // has transactions
      if (shouldVerifyTransactions() && block.transactions == null)
        throw new VerificationException("Got a block header while running in full-block mode");

      // Check for already-seen block, but only for full pruned mode, where the DB is
      // more likely able to handle these queries quickly.
      if (shouldVerifyTransactions() && blockStore.get(block.getHash()) != null) {
        return true;
      }

      // Does this block contain any transactions we might care about? Check this up front before
      // verifying the
      // blocks validity so we can skip the merkle root verification if the contents aren't
      // interesting. This saves
      // a lot of time for big blocks.
      boolean contentsImportant = shouldVerifyTransactions();
      if (block.transactions != null) {
        contentsImportant = contentsImportant || containsRelevantTransactions(block);
      }

      // Prove the block is internally valid: hash is lower than target, etc. This only checks the
      // block contents
      // if there is a tx sending or receiving coins using an address in one of our wallets. And
      // those transactions
      // are only lightly verified: presence in a valid connecting block is taken as proof of
      // validity. See the
      // article here for more details: http://code.google.com/p/bitcoinj/wiki/SecurityModel
      try {
        block.verifyHeader();
        if (contentsImportant) block.verifyTransactions();

      } catch (VerificationException e) {
        log.error("Failed to verify block: ", e);
        log.error(block.getHashAsString());
        throw e;
      }

      // Try linking it to a place in the currently known blocks.
      StoredBlock storedPrev = getStoredBlockInCurrentScope(block.getPrevBlockHash());
      if (storedPrev == null) {
        // We can't find the previous block. Probably we are still in the process of downloading the
        // chain and a
        // block was solved whilst we were doing it. We put it to one side and try to connect it
        // later when we
        // have more blocks.
        checkState(tryConnecting, "bug in tryConnectingOrphans");
        log.warn(
            "Block does not connect: {} prev {}",
            block.getHashAsString(),
            block.getPrevBlockHash());
        orphanBlocks.put(block.getHash(), new OrphanBlock(block, filteredTxHashList, filteredTxn));
        return false;
      } else {
        checkState(lock.isHeldByCurrentThread());
        // It connects to somewhere on the chain. Not necessarily the top of the best known chain.
        params.checkDifficultyTransitions(storedPrev, block, blockStore);
        connectBlock(
            block, storedPrev, shouldVerifyTransactions(), filteredTxHashList, filteredTxn);
      }
      if (tryConnecting) tryConnectingOrphans();
      return true;
    } finally {
      lock.unlock();
    }
  }