/** Constructs a BlockChain connected to the given list of listeners (eg, wallets) and a store. */
 public AbstractBlockChain(
     Context context, List<BlockChainListener> listeners, BlockStore blockStore)
     throws BlockStoreException {
   this.blockStore = blockStore;
   chainHead = blockStore.getChainHead();
   log.info("chain head is at height {}:\n{}", chainHead.getHeight(), chainHead.getHeader());
   this.params = context.getParams();
   this.listeners = new CopyOnWriteArrayList<ListenerRegistration<BlockChainListener>>();
   for (BlockChainListener l : listeners) addListener(l, Threading.SAME_THREAD);
 }
  // 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();
    }
  }
  private static void darkGravityWaveCheck(
      StoredBlock prevBlock, Block added, BlockStore store, NetworkParameters params) {
    StoredBlock blockReading = prevBlock;

    long blockTimeAverage = 0;
    long blockTimeAveragePrev = 0;
    long blockTimeCount = 0;
    long blockTimeSum2 = 0;
    long blockTimeCount2 = 0;
    long lastBlockTime = 0;
    long pastBlocksMin = 14;
    long pastBlocksMax = 140;
    long countBlocks = 0;

    BigInteger pastDifficultyAverage = BigInteger.valueOf(0);
    BigInteger pastDifficultyAveragePrev = BigInteger.valueOf(0);

    if (prevBlock == null || prevBlock.getHeight() == 0 || prevBlock.getHeight() < pastBlocksMin) {
      verifyDifficulty(prevBlock, added, params.getMaxTarget(), params);
      return;
    }

    for (int i = 1; blockReading.getHeight() > 0; i++) {
      if (i > pastBlocksMax) {
        break;
      }
      countBlocks++;

      if (countBlocks <= pastBlocksMin) {
        if (countBlocks == 1) {
          pastDifficultyAverage = blockReading.getHeader().getDifficultyTargetAsInteger();
        } else {
          pastDifficultyAverage =
              blockReading
                  .getHeader()
                  .getDifficultyTargetAsInteger()
                  .subtract(pastDifficultyAveragePrev)
                  .divide(BigInteger.valueOf(countBlocks))
                  .add(pastDifficultyAveragePrev);
        }
        pastDifficultyAveragePrev = pastDifficultyAverage;
      }

      if (lastBlockTime > 0) {
        long diff = lastBlockTime - blockReading.getHeader().getTimeSeconds();
        if (blockTimeCount <= pastBlocksMin) {
          blockTimeCount++;

          if (blockTimeCount == 1) {
            blockTimeAverage = diff;
          } else {
            blockTimeAverage =
                (diff - blockTimeAveragePrev) / blockTimeCount + blockTimeAveragePrev;
          }
          blockTimeAveragePrev = blockTimeAverage;
        }
        blockTimeCount2++;
        blockTimeSum2 += diff;
      }
      lastBlockTime = blockReading.getHeader().getTimeSeconds();

      try {
        blockReading = store.get(blockReading.getHeader().getPrevBlockHash());
        if (blockReading == null) {
          return;
        }
      } catch (BlockStoreException ex) {
        log.warn("Dark gravity wave 3 descended to start of the chain");
        return;
      }
    }

    BigInteger bnNew = pastDifficultyAverage;

    if (blockTimeCount != 0 && blockTimeCount2 != 0) {
      double smartAverage =
          ((double) blockTimeAverage) * 0.7
              + ((double) blockTimeSum2 / (double) blockTimeCount2) * 0.3;
      if (smartAverage < 1) smartAverage = 1;

      final int targetSpacing =
          params.getTargetSpacing(prevBlock.getHeader(), prevBlock.getHeight());
      final double shift = targetSpacing / smartAverage;

      final double dCountBlocks = (double) countBlocks;
      double actualTimespan = dCountBlocks * ((double) targetSpacing) / shift;
      double targetTimespan = dCountBlocks * targetSpacing;

      if (actualTimespan < targetTimespan / 3) actualTimespan = targetTimespan / 3;
      if (actualTimespan > targetTimespan * 3) actualTimespan = targetTimespan * 3;

      // Retarget
      bnNew = bnNew.multiply(BigInteger.valueOf((long) actualTimespan));
      bnNew = bnNew.divide(BigInteger.valueOf((long) targetTimespan));
    }

    verifyDifficulty(prevBlock, added, bnNew, params);
  }
  private static void darkGravityWave3Check(
      StoredBlock prevBlock, Block added, BlockStore store, NetworkParameters params) {
    StoredBlock blockReading = prevBlock;

    long actualTimespan = 0;
    long lastBlockTime = 0;
    long pastBlocksMin = 24;
    long pastBlocksMax = 24;
    long countBlocks = 0;

    BigInteger pastDifficultyAverage = BigInteger.ZERO;
    BigInteger pastDifficultyAveragePrev = BigInteger.ZERO;

    if (prevBlock == null || prevBlock.getHeight() == 0 || prevBlock.getHeight() < pastBlocksMin) {
      verifyDifficulty(prevBlock, added, params.getMaxTarget(), params);
      return;
    }

    for (int i = 1; blockReading.getHeight() > 0; i++) {
      if (i > pastBlocksMax) {
        break;
      }
      countBlocks++;

      if (countBlocks <= pastBlocksMin) {
        if (countBlocks == 1) {
          pastDifficultyAverage = blockReading.getHeader().getDifficultyTargetAsInteger();
        } else {
          pastDifficultyAverage =
              pastDifficultyAveragePrev
                  .multiply(BigInteger.valueOf(countBlocks))
                  .add(blockReading.getHeader().getDifficultyTargetAsInteger())
                  .divide(BigInteger.valueOf(countBlocks + 1));
        }
        pastDifficultyAveragePrev = pastDifficultyAverage;
      }

      if (lastBlockTime > 0) {
        actualTimespan += (lastBlockTime - blockReading.getHeader().getTimeSeconds());
      }
      lastBlockTime = blockReading.getHeader().getTimeSeconds();

      try {
        blockReading = store.get(blockReading.getHeader().getPrevBlockHash());
        if (blockReading == null) {
          return;
        }
      } catch (BlockStoreException ex) {
        log.warn("Dark gravity wave 3 descended to start of the chain");
        return;
      }
    }

    BigInteger bnNew = pastDifficultyAverage;

    long targetTimespan =
        countBlocks * params.getTargetSpacing(prevBlock.getHeader(), prevBlock.getHeight());

    if (actualTimespan < targetTimespan / 3) {
      actualTimespan = targetTimespan / 3;
    }
    if (actualTimespan > targetTimespan * 3) {
      actualTimespan = targetTimespan * 3;
    }

    // Retarget
    bnNew = bnNew.multiply(BigInteger.valueOf(actualTimespan));
    bnNew = bnNew.divide(BigInteger.valueOf(targetTimespan));

    verifyDifficulty(prevBlock, added, bnNew, params);
  }
  private static void kimotoGravityWellCheck(
      StoredBlock prevBlock, Block added, BlockStore store, NetworkParameters params)
      throws BlockStoreException {
    final long blocksTargetSpacing = (long) (2.5 * 60); // 2.5 minutes
    int timeDaySeconds = 60 * 60 * 24;
    long pastSecondsMin = timeDaySeconds / 40;
    long pastSecondsMax = timeDaySeconds * 7;
    long pastBlocksMin = pastSecondsMin / blocksTargetSpacing;
    long pastBlocksMax = pastSecondsMax / blocksTargetSpacing;

    StoredBlock blockReading = prevBlock;

    long pastBlocksMass = 0;
    long pastRateActualSeconds = 0;
    long pastRateTargetSeconds = 0;
    double pastRateAdjustmentRatio = 1.0f;
    BigInteger pastDifficultyAverage = BigInteger.valueOf(0);
    BigInteger pastDifficultyAveragePrev = BigInteger.valueOf(0);
    double eventHorizonDeviation;
    double eventHorizonDeviationFast;
    double eventHorizonDeviationSlow;

    if (prevBlock == null
        || prevBlock.getHeight() == 0
        || (long) prevBlock.getHeight() < pastBlocksMin) {
      verifyDifficulty(prevBlock, added, params.getMaxTarget(), params);
      return;
    }

    final Block prevHeader = prevBlock.getHeader();
    long latestBlockTime = prevHeader.getTimeSeconds();

    for (int i = 1; blockReading.getHeight() > 0; i++) {
      if (pastBlocksMax > 0 && i > pastBlocksMax) {
        break;
      }
      pastBlocksMass++;

      if (i == 1) {
        pastDifficultyAverage = blockReading.getHeader().getDifficultyTargetAsInteger();
      } else {
        pastDifficultyAverage =
            (blockReading
                    .getHeader()
                    .getDifficultyTargetAsInteger()
                    .subtract(pastDifficultyAveragePrev))
                .divide(BigInteger.valueOf(i))
                .add(pastDifficultyAveragePrev);
      }
      pastDifficultyAveragePrev = pastDifficultyAverage;

      if (blockReading.getHeight() > 646120
          && latestBlockTime < blockReading.getHeader().getTimeSeconds()) {
        // eliminates the ability to go back in time
        latestBlockTime = blockReading.getHeader().getTimeSeconds();
      }

      pastRateActualSeconds =
          prevHeader.getTimeSeconds() - blockReading.getHeader().getTimeSeconds();
      pastRateTargetSeconds = blocksTargetSpacing * pastBlocksMass;
      if (blockReading.getHeight() > 646120) {
        // this should slow down the upward difficulty change
        if (pastRateActualSeconds < 5) {
          pastRateActualSeconds = 5;
        }
      } else {
        if (pastRateActualSeconds < 0) {
          pastRateActualSeconds = 0;
        }
      }
      if (pastRateActualSeconds != 0 && pastRateTargetSeconds != 0) {
        pastRateAdjustmentRatio = (double) pastRateTargetSeconds / pastRateActualSeconds;
      }
      eventHorizonDeviation = 1 + 0.7084 * Math.pow((double) pastBlocksMass / 28.2d, -1.228);
      eventHorizonDeviationFast = eventHorizonDeviation;
      eventHorizonDeviationSlow = 1 / eventHorizonDeviation;

      if (pastBlocksMass >= pastBlocksMin) {
        if (pastRateAdjustmentRatio <= eventHorizonDeviationSlow
            || pastRateAdjustmentRatio >= eventHorizonDeviationFast) {
          break;
        }
      }
      blockReading = store.get(blockReading.getHeader().getPrevBlockHash());
      if (blockReading == null) {
        return;
      }
    }

    BigInteger newDifficulty = pastDifficultyAverage;
    if (pastRateActualSeconds != 0 && pastRateTargetSeconds != 0) {
      newDifficulty = newDifficulty.multiply(BigInteger.valueOf(pastRateActualSeconds));
      newDifficulty = newDifficulty.divide(BigInteger.valueOf(pastRateTargetSeconds));
    }

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

    verifyDifficulty(prevBlock, added, newDifficulty, params);
  }