Esempio n. 1
0
  private synchronized boolean add(Block block, boolean tryConnecting)
      throws BlockStoreException, VerificationException, ScriptException {
    if (System.currentTimeMillis() - statsLastTime > 1000) {
      // More than a second passed since last stats logging.
      log.info("{} blocks per second", statsBlocksAdded);
      statsLastTime = System.currentTimeMillis();
      statsBlocksAdded = 0;
    }
    // We check only the chain head for double adds here to avoid potentially expensive block chain
    // misses.
    if (block.equals(chainHead.getHeader())) {
      // Duplicate add of the block at the top of the chain, can be a natural artifact of the
      // download process.
      return true;
    }

    // Prove the block is internally valid: hash is lower than target, merkle root is correct and so
    // on.
    try {
      block.verify();
    } catch (VerificationException e) {
      log.error("Failed to verify block:", e);
      log.error(block.toString());
      throw e;
    }

    // Try linking it to a place in the currently known blocks.
    StoredBlock storedPrev = blockStore.get(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.
      log.warn("Block does not connect: {}", block.getHashAsString());
      unconnectedBlocks.add(block);
      return false;
    } else {
      // It connects to somewhere on the chain. Not necessarily the top of the best known chain.
      //
      // Create a new StoredBlock from this block. It will throw away the transaction data so when
      // block goes
      // out of scope we will reclaim the used memory.
      StoredBlock newStoredBlock = storedPrev.build(block);
      checkDifficultyTransitions(storedPrev, newStoredBlock);
      blockStore.put(newStoredBlock);
      // block.transactions may be null here if we received only a header and not a full block. This
      // does not
      // happen currently but might in future if getheaders is implemented.
      connectBlock(newStoredBlock, storedPrev, block.transactions);
    }

    if (tryConnecting) tryConnectingUnconnected();

    statsBlocksAdded++;
    return true;
  }
  // expensiveChecks enables checks that require looking at blocks further back in the chain
  // than the previous one when connecting (eg median timestamp check)
  // It could be exposed, but for now we just set it to shouldVerifyTransactions()
  private void connectBlock(
      final Block block,
      StoredBlock storedPrev,
      boolean expensiveChecks,
      @Nullable final List<Sha256Hash> filteredTxHashList,
      @Nullable final Map<Sha256Hash, Transaction> filteredTxn)
      throws BlockStoreException, VerificationException, PrunedException {
    checkState(lock.isHeldByCurrentThread());
    boolean filtered = filteredTxHashList != null && filteredTxn != null;
    // Check that we aren't connecting a block that fails a checkpoint check
    if (!params.passesCheckpoint(storedPrev.getHeight() + 1, block.getHash()))
      throw new VerificationException(
          "Block failed checkpoint lockin at " + (storedPrev.getHeight() + 1));
    if (shouldVerifyTransactions()) {
      checkNotNull(block.transactions);
      for (Transaction tx : block.transactions)
        if (!tx.isFinal(storedPrev.getHeight() + 1, block.getTimeSeconds()))
          throw new VerificationException("Block contains non-final transaction");
    }

    StoredBlock head = getChainHead();
    if (storedPrev.equals(head)) {
      if (filtered && filteredTxn.size() > 0) {
        log.debug(
            "Block {} connects to top of best chain with {} transaction(s) of which we were sent {}",
            block.getHashAsString(),
            filteredTxHashList.size(),
            filteredTxn.size());
        for (Sha256Hash hash : filteredTxHashList) log.debug("  matched tx {}", hash);
      }
      if (expensiveChecks
          && block.getTimeSeconds() <= getMedianTimestampOfRecentBlocks(head, blockStore))
        throw new VerificationException("Block's timestamp is too early");

      // This block connects to the best known block, it is a normal continuation of the system.
      TransactionOutputChanges txOutChanges = null;
      if (shouldVerifyTransactions())
        txOutChanges = connectTransactions(storedPrev.getHeight() + 1, block);
      StoredBlock newStoredBlock =
          addToBlockStore(
              storedPrev, block.transactions == null ? block : block.cloneAsHeader(), txOutChanges);
      setChainHead(newStoredBlock);
      log.debug("Chain is now {} blocks high, running listeners", newStoredBlock.getHeight());
      informListenersForNewBlock(
          block, NewBlockType.BEST_CHAIN, filteredTxHashList, filteredTxn, newStoredBlock);
    } else {
      // This block connects to somewhere other than the top of the best known chain. We treat these
      // differently.
      //
      // Note that we send the transactions to the wallet FIRST, even if we're about to re-organize
      // this block
      // to become the new best chain head. This simplifies handling of the re-org in the Wallet
      // class.
      StoredBlock newBlock = storedPrev.build(block);
      boolean haveNewBestChain = newBlock.moreWorkThan(head);
      if (haveNewBestChain) {
        log.info("Block is causing a re-organize");
      } else {
        StoredBlock splitPoint = findSplit(newBlock, head, blockStore);
        if (splitPoint != null && splitPoint.equals(newBlock)) {
          // newStoredBlock is a part of the same chain, there's no fork. This happens when we
          // receive a block
          // that we already saw and linked into the chain previously, which isn't the chain head.
          // Re-processing it is confusing for the wallet so just skip.
          log.warn(
              "Saw duplicated block in main chain at height {}: {}",
              newBlock.getHeight(),
              newBlock.getHeader().getHash());
          return;
        }
        if (splitPoint == null) {
          // This should absolutely never happen
          // (lets not write the full block to disk to keep any bugs which allow this to happen
          //  from writing unreasonable amounts of data to disk)
          throw new VerificationException("Block forks the chain but splitPoint is null");
        } else {
          // We aren't actually spending any transactions (yet) because we are on a fork
          addToBlockStore(storedPrev, block);
          int splitPointHeight = splitPoint.getHeight();
          String splitPointHash = splitPoint.getHeader().getHashAsString();
          log.info(
              "Block forks the chain at height {}/block {}, but it did not cause a reorganize:\n{}",
              splitPointHeight,
              splitPointHash,
              newBlock.getHeader().getHashAsString());
        }
      }

      // We may not have any transactions if we received only a header, which can happen during fast
      // catchup.
      // If we do, send them to the wallet but state that they are on a side chain so it knows not
      // to try and
      // spend them until they become activated.
      if (block.transactions != null || filtered) {
        informListenersForNewBlock(
            block, NewBlockType.SIDE_CHAIN, filteredTxHashList, filteredTxn, newBlock);
      }

      if (haveNewBestChain) handleNewBestChain(storedPrev, newBlock, block, expensiveChecks);
    }
  }