/** Throws an exception if the blocks difficulty is not correct. */
  private void checkDifficultyTransitions(StoredBlock storedPrev, StoredBlock storedNext)
      throws BlockStoreException, VerificationException {
    Block prev = storedPrev.getHeader();
    Block next = storedNext.getHeader();
    // Is this supposed to be a difficulty transition point?
    if ((storedPrev.getHeight() + 1) % params.interval != 0) {
      // No ... so check the difficulty didn't actually change.
      if (next.getDifficultyTarget() != prev.getDifficultyTarget())
        throw new VerificationException(
            "Unexpected change in difficulty at height "
                + storedPrev.getHeight()
                + ": "
                + Long.toHexString(next.getDifficultyTarget())
                + " vs "
                + Long.toHexString(prev.getDifficultyTarget()));
      return;
    }

    // We need to find a block far back in the chain. It's OK that this is expensive because it only
    // occurs every
    // two weeks after the initial block chain download.
    long now = System.currentTimeMillis();
    StoredBlock cursor = blockStore.get(prev.getHash());
    for (int i = 0; i < params.interval - 1; i++) {
      if (cursor == null) {
        // This should never happen. If it does, it means we are following an incorrect or busted
        // chain.
        throw new VerificationException(
            "Difficulty transition point but we did not find a way back to the genesis block.");
      }
      cursor = blockStore.get(cursor.getHeader().getPrevBlockHash());
    }
    log.info("Difficulty transition traversal took {}msec", System.currentTimeMillis() - now);

    Block blockIntervalAgo = cursor.getHeader();
    int timespan = (int) (prev.getTime() - blockIntervalAgo.getTime());
    // Limit the adjustment step.
    if (timespan < params.targetTimespan / 4) timespan = params.targetTimespan / 4;
    if (timespan > params.targetTimespan * 4) timespan = params.targetTimespan * 4;

    BigInteger newDifficulty = Utils.decodeCompactBits(blockIntervalAgo.getDifficultyTarget());
    newDifficulty = newDifficulty.multiply(BigInteger.valueOf(timespan));
    newDifficulty = newDifficulty.divide(BigInteger.valueOf(params.targetTimespan));

    if (newDifficulty.compareTo(params.proofOfWorkLimit) > 0) {
      log.warn("Difficulty hit proof of work limit: {}", newDifficulty.toString(16));
      newDifficulty = params.proofOfWorkLimit;
    }

    int accuracyBytes = (int) (next.getDifficultyTarget() >>> 24) - 3;
    BigInteger receivedDifficulty = next.getDifficultyTargetAsInteger();

    // The calculated difficulty is to a higher precision than received, so reduce here.
    BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8);
    newDifficulty = newDifficulty.and(mask);

    if (newDifficulty.compareTo(receivedDifficulty) != 0)
      throw new VerificationException(
          "Network provided difficulty bits do not match what was calculated: "
              + receivedDifficulty.toString(16)
              + " vs "
              + newDifficulty.toString(16));
  }