Beispiel #1
0
  public void checkConsistency() throws com.google.bitcoin.store.BlockStoreException {
    StoredBlock head = block_store.getChainHead();

    StoredBlock curr_block = head;

    Sha256Hash genisis_hash = params.getGenesisBlock().getHash();
    int checked = 0;

    while (true) {
      Sha256Hash curr_hash = curr_block.getHeader().getHash();

      if (curr_block.getHeight() % 10000 == 0) {
        System.out.println("Block: " + curr_block.getHeight());
      }
      if (!file_db.getBlockMap().containsKey(curr_hash)) {
        throw new RuntimeException("Missing block: " + curr_hash);
      }
      checked++;
      // if (checked > 20) return;

      if (curr_hash.equals(genisis_hash)) return;

      curr_block = curr_block.getPrev(block_store);
    }
  }
 /**
  * Returns the set of contiguous blocks between 'higher' and 'lower'. Higher is included, lower is
  * not.
  */
 private List<StoredBlock> getPartialChain(StoredBlock higher, StoredBlock lower)
     throws BlockStoreException {
   assert higher.getHeight() > lower.getHeight();
   LinkedList<StoredBlock> results = new LinkedList<StoredBlock>();
   StoredBlock cursor = higher;
   while (true) {
     results.add(cursor);
     cursor = cursor.getPrev(blockStore);
     assert cursor != null : "Ran off the end of the chain";
     if (cursor.equals(lower)) break;
   }
   return results;
 }
    @Override
    public View getView(final int position, final View convertView, final ViewGroup parent) {
      final ViewGroup row;
      if (convertView == null)
        row = (ViewGroup) getLayoutInflater(null).inflate(R.layout.block_row, null);
      else row = (ViewGroup) convertView;

      final StoredBlock storedBlock = getItem(position);
      final Block header = storedBlock.getHeader();

      final TextView rowHeight = (TextView) row.findViewById(R.id.block_list_row_height);
      final int height = storedBlock.getHeight();
      rowHeight.setText(Integer.toString(height));

      final TextView rowTime = (TextView) row.findViewById(R.id.block_list_row_time);
      final long timeMs = header.getTimeSeconds() * DateUtils.SECOND_IN_MILLIS;
      rowTime.setText(
          DateUtils.getRelativeDateTimeString(
              activity, timeMs, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0));

      final TextView rowHash = (TextView) row.findViewById(R.id.block_list_row_hash);
      rowHash.setText(WalletUtils.formatHash(null, header.getHashAsString(), 8, 0, ' '));

      final int transactionChildCount = row.getChildCount() - ROW_BASE_CHILD_COUNT;
      int iTransactionView = 0;

      if (transactions != null) {
        final String precision =
            prefs.getString(
                Constants.PREFS_KEY_BTC_PRECISION, Constants.PREFS_DEFAULT_BTC_PRECISION);
        final int btcPrecision = precision.charAt(0) - '0';
        final int btcShift = precision.length() == 3 ? precision.charAt(2) - '0' : 0;

        transactionsAdapter.setPrecision(btcPrecision, btcShift);

        for (final Transaction tx : transactions) {
          if (tx.getAppearsInHashes().containsKey(header.getHash())) {
            final View view;
            if (iTransactionView < transactionChildCount) {
              view = row.getChildAt(ROW_INSERT_INDEX + iTransactionView);
            } else {
              view = getLayoutInflater(null).inflate(R.layout.transaction_row_oneline, null);
              row.addView(view, ROW_INSERT_INDEX + iTransactionView);
            }

            transactionsAdapter.bindView(view, tx);

            iTransactionView++;
          }
        }
      }

      final int leftoverTransactionViews = transactionChildCount - iTransactionView;
      if (leftoverTransactionViews > 0)
        row.removeViews(ROW_INSERT_INDEX + iTransactionView, leftoverTransactionViews);

      return row;
    }
 /**
  * Locates the point in the chain at which newStoredBlock and chainHead diverge. Returns null if
  * no split point was found (ie they are part of the same chain).
  */
 private StoredBlock findSplit(StoredBlock newChainHead, StoredBlock chainHead)
     throws BlockStoreException {
   StoredBlock currentChainCursor = chainHead;
   StoredBlock newChainCursor = newChainHead;
   // Loop until we find the block both chains have in common. Example:
   //
   //    A -> B -> C -> D
   //         \--> E -> F -> G
   //
   // findSplit will return block B. chainHead = D and newChainHead = G.
   while (!currentChainCursor.equals(newChainCursor)) {
     if (currentChainCursor.getHeight() > newChainCursor.getHeight()) {
       currentChainCursor = currentChainCursor.getPrev(blockStore);
       assert currentChainCursor != null : "Attempt to follow an orphan chain";
     } else {
       newChainCursor = newChainCursor.getPrev(blockStore);
       assert newChainCursor != null : "Attempt to follow an orphan chain";
     }
   }
   return currentChainCursor;
 }
  private void connectBlock(
      StoredBlock newStoredBlock, StoredBlock storedPrev, List<Transaction> newTransactions)
      throws BlockStoreException, VerificationException {
    if (storedPrev.equals(chainHead)) {
      // This block connects to the best known block, it is a normal continuation of the system.
      setChainHead(newStoredBlock);
      log.trace("Chain is now {} blocks high", chainHead.getHeight());
      if (newTransactions != null)
        sendTransactionsToWallet(newStoredBlock, NewBlockType.BEST_CHAIN, newTransactions);
    } 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.
      boolean haveNewBestChain = newStoredBlock.moreWorkThan(chainHead);
      if (haveNewBestChain) {
        log.info("Block is causing a re-organize");
      } else {
        StoredBlock splitPoint = findSplit(newStoredBlock, chainHead);
        String splitPointHash = splitPoint != null ? splitPoint.getHeader().getHashAsString() : "?";
        log.info(
            "Block forks the chain at {}, but it did not cause a reorganize:\n{}",
            splitPointHash,
            newStoredBlock);
      }

      // We may not have any transactions if we received only a header. That never happens today but
      // will in
      // future when getheaders is used as an optimization.
      if (newTransactions != null) {
        sendTransactionsToWallet(newStoredBlock, NewBlockType.SIDE_CHAIN, newTransactions);
      }

      if (haveNewBestChain) handleNewBestChain(newStoredBlock);
    }
  }
 /**
  * Called as part of connecting a block when the new block results in a different chain having
  * higher total work.
  */
 private void handleNewBestChain(StoredBlock newChainHead)
     throws BlockStoreException, VerificationException {
   // This chain has overtaken the one we currently believe is best. Reorganize is required.
   //
   // Firstly, calculate the block at which the chain diverged. We only need to examine the
   // chain from beyond this block to find differences.
   StoredBlock splitPoint = findSplit(newChainHead, chainHead);
   log.info("Re-organize after split at height {}", splitPoint.getHeight());
   log.info("Old chain head: {}", chainHead.getHeader().getHashAsString());
   log.info("New chain head: {}", newChainHead.getHeader().getHashAsString());
   log.info("Split at block: {}", splitPoint.getHeader().getHashAsString());
   // Then build a list of all blocks in the old part of the chain and the new part.
   List<StoredBlock> oldBlocks = getPartialChain(chainHead, splitPoint);
   List<StoredBlock> newBlocks = getPartialChain(newChainHead, splitPoint);
   // Now inform the wallet. This is necessary so the set of currently active transactions (that we
   // can spend)
   // can be updated to take into account the re-organize. We might also have received new coins we
   // didn't have
   // before and our previous spends might have been undone.
   wallet.reorganize(oldBlocks, newBlocks);
   // Update the pointer to the best known block.
   setChainHead(newChainHead);
 }
  @Override
  /** Used during reorgs to connect a block previously on a fork */
  protected synchronized TransactionOutputChanges connectTransactions(StoredBlock newBlock)
      throws VerificationException, BlockStoreException, PrunedException {
    checkState(lock.isHeldByCurrentThread());
    if (!params.passesCheckpoint(newBlock.getHeight(), newBlock.getHeader().getHash()))
      throw new VerificationException("Block failed checkpoint lockin at " + newBlock.getHeight());

    blockStore.beginDatabaseBatchWrite();
    StoredUndoableBlock block = blockStore.getUndoBlock(newBlock.getHeader().getHash());
    if (block == null) {
      // We're trying to re-org too deep and the data needed has been deleted.
      blockStore.abortDatabaseBatchWrite();
      throw new PrunedException(newBlock.getHeader().getHash());
    }
    TransactionOutputChanges txOutChanges;
    try {
      List<Transaction> transactions = block.getTransactions();
      if (transactions != null) {
        LinkedList<StoredTransactionOutput> txOutsSpent = new LinkedList<StoredTransactionOutput>();
        LinkedList<StoredTransactionOutput> txOutsCreated =
            new LinkedList<StoredTransactionOutput>();
        long sigOps = 0;
        final boolean enforcePayToScriptHash =
            newBlock.getHeader().getTimeSeconds() >= NetworkParameters.BIP16_ENFORCE_TIME;
        if (!params.isCheckpoint(newBlock.getHeight())) {
          for (Transaction tx : transactions) {
            Sha256Hash hash = tx.getHash();
            if (blockStore.hasUnspentOutputs(hash, tx.getOutputs().size()))
              throw new VerificationException("Block failed BIP30 test!");
          }
        }
        Coin totalFees = Coin.ZERO;
        Coin coinbaseValue = null;

        if (scriptVerificationExecutor.isShutdown())
          scriptVerificationExecutor =
              Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        List<Future<VerificationException>> listScriptVerificationResults =
            new ArrayList<Future<VerificationException>>(transactions.size());
        for (final Transaction tx : transactions) {
          boolean isCoinBase = tx.isCoinBase();
          Coin valueIn = Coin.ZERO;
          Coin valueOut = Coin.ZERO;
          final List<Script> prevOutScripts = new LinkedList<Script>();
          if (!isCoinBase) {
            for (int index = 0; index < tx.getInputs().size(); index++) {
              final TransactionInput in = tx.getInputs().get(index);
              final StoredTransactionOutput prevOut =
                  blockStore.getTransactionOutput(
                      in.getOutpoint().getHash(), in.getOutpoint().getIndex());
              if (prevOut == null)
                throw new VerificationException(
                    "Attempted spend of a non-existent or already spent output!");
              if (newBlock.getHeight() - prevOut.getHeight() < params.getSpendableCoinbaseDepth())
                throw new VerificationException(
                    "Tried to spend coinbase at depth "
                        + (newBlock.getHeight() - prevOut.getHeight()));
              valueIn = valueIn.add(prevOut.getValue());
              if (enforcePayToScriptHash) {
                Script script = new Script(prevOut.getScriptBytes());
                if (script.isPayToScriptHash())
                  sigOps += Script.getP2SHSigOpCount(in.getScriptBytes());
                if (sigOps > Block.MAX_BLOCK_SIGOPS)
                  throw new VerificationException("Too many P2SH SigOps in block");
              }

              prevOutScripts.add(new Script(prevOut.getScriptBytes()));

              blockStore.removeUnspentTransactionOutput(prevOut);
              txOutsSpent.add(prevOut);
            }
          }
          Sha256Hash hash = tx.getHash();
          for (TransactionOutput out : tx.getOutputs()) {
            valueOut = valueOut.add(out.getValue());
            StoredTransactionOutput newOut =
                new StoredTransactionOutput(
                    hash,
                    out.getIndex(),
                    out.getValue(),
                    newBlock.getHeight(),
                    isCoinBase,
                    out.getScriptBytes());
            blockStore.addUnspentTransactionOutput(newOut);
            txOutsCreated.add(newOut);
          }
          // All values were already checked for being non-negative (as it is verified in
          // Transaction.verify())
          // but we check again here just for defence in depth. Transactions with zero output value
          // are OK.
          if (valueOut.signum() < 0 || valueOut.compareTo(NetworkParameters.MAX_MONEY) > 0)
            throw new VerificationException("Transaction output value out of range");
          if (isCoinBase) {
            coinbaseValue = valueOut;
          } else {
            if (valueIn.compareTo(valueOut) < 0
                || valueIn.compareTo(NetworkParameters.MAX_MONEY) > 0)
              throw new VerificationException("Transaction input value out of range");
            totalFees = totalFees.add(valueIn.subtract(valueOut));
          }

          if (!isCoinBase) {
            // Because correctlySpends modifies transactions, this must come after we are done with
            // tx
            FutureTask<VerificationException> future =
                new FutureTask<VerificationException>(
                    new Verifier(tx, prevOutScripts, enforcePayToScriptHash));
            scriptVerificationExecutor.execute(future);
            listScriptVerificationResults.add(future);
          }
        }
        if (totalFees.compareTo(NetworkParameters.MAX_MONEY) > 0
            || newBlock
                    .getHeader()
                    .getBlockInflation(newBlock.getHeight())
                    .add(totalFees)
                    .compareTo(coinbaseValue)
                < 0) throw new VerificationException("Transaction fees out of range");
        txOutChanges = new TransactionOutputChanges(txOutsCreated, txOutsSpent);
        for (Future<VerificationException> future : listScriptVerificationResults) {
          VerificationException e;
          try {
            e = future.get();
          } catch (InterruptedException thrownE) {
            throw new RuntimeException(thrownE); // Shouldn't happen
          } catch (ExecutionException thrownE) {
            log.error("Script.correctlySpends threw a non-normal exception: " + thrownE.getCause());
            throw new VerificationException(
                "Bug in Script.correctlySpends, likely script malformed in some new and interesting way.",
                thrownE);
          }
          if (e != null) throw e;
        }
      } else {
        txOutChanges = block.getTxOutChanges();
        if (!params.isCheckpoint(newBlock.getHeight()))
          for (StoredTransactionOutput out : txOutChanges.txOutsCreated) {
            Sha256Hash hash = out.getHash();
            if (blockStore.getTransactionOutput(hash, out.getIndex()) != null)
              throw new VerificationException("Block failed BIP30 test!");
          }
        for (StoredTransactionOutput out : txOutChanges.txOutsCreated)
          blockStore.addUnspentTransactionOutput(out);
        for (StoredTransactionOutput out : txOutChanges.txOutsSpent)
          blockStore.removeUnspentTransactionOutput(out);
      }
    } catch (VerificationException e) {
      scriptVerificationExecutor.shutdownNow();
      blockStore.abortDatabaseBatchWrite();
      throw e;
    } catch (BlockStoreException e) {
      scriptVerificationExecutor.shutdownNow();
      blockStore.abortDatabaseBatchWrite();
      throw e;
    }
    return txOutChanges;
  }
  /** 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));
  }