@Test
  public void testLastBlockSeenHash() throws Exception {
    // Test the lastBlockSeenHash field works.

    // LastBlockSeenHash should be empty if never set.
    Wallet wallet = new Wallet(params);
    Protos.Wallet walletProto = new WalletProtobufSerializer().walletToProto(wallet);
    ByteString lastSeenBlockHash = walletProto.getLastSeenBlockHash();
    assertTrue(lastSeenBlockHash.isEmpty());

    // Create a block.
    Block block = params.getDefaultSerializer().makeBlock(BlockTest.blockBytes);
    Sha256Hash blockHash = block.getHash();
    wallet.setLastBlockSeenHash(blockHash);
    wallet.setLastBlockSeenHeight(1);

    // Roundtrip the wallet and check it has stored the blockHash.
    Wallet wallet1 = roundTrip(wallet);
    assertEquals(blockHash, wallet1.getLastBlockSeenHash());
    assertEquals(1, wallet1.getLastBlockSeenHeight());

    // Test the Satoshi genesis block (hash of all zeroes) is roundtripped ok.
    Block genesisBlock = MainNetParams.get().getGenesisBlock();
    wallet.setLastBlockSeenHash(genesisBlock.getHash());
    Wallet wallet2 = roundTrip(wallet);
    assertEquals(genesisBlock.getHash(), wallet2.getLastBlockSeenHash());
  }
Esempio n. 2
0
  @Test
  public void getLargeBlock() throws Exception {
    connect();

    Block b1 = createFakeBlock(blockStore).block;
    blockChain.add(b1);
    Block b2 = makeSolvedTestBlock(b1);
    Transaction t = new Transaction(unitTestParams);
    t.addInput(b1.getTransactions().get(0).getOutput(0));
    t.addOutput(
        new TransactionOutput(
            unitTestParams, t, BigInteger.ZERO, new byte[Block.MAX_BLOCK_SIZE - 1000]));
    b2.addTransaction(t);

    // Request the block.
    Future<Block> resultFuture = peer.getBlock(b2.getHash());
    assertFalse(resultFuture.isDone());
    // Peer asks for it.
    GetDataMessage message = (GetDataMessage) outbound(writeTarget);
    assertEquals(message.getItems().get(0).hash, b2.getHash());
    assertFalse(resultFuture.isDone());
    // Peer receives it.
    inbound(writeTarget, b2);
    Block b = resultFuture.get();
    assertEquals(b, b2);
  }
Esempio n. 3
0
  // Check that it starts downloading the block chain correctly on request.
  @Test
  public void startBlockChainDownload() throws Exception {
    Block b1 = createFakeBlock(blockStore).block;
    blockChain.add(b1);
    Block b2 = makeSolvedTestBlock(b1);
    blockChain.add(b2);

    connect();
    fail.set(true);
    peer.addEventListener(
        new AbstractPeerEventListener() {
          @Override
          public void onChainDownloadStarted(Peer p, int blocksLeft) {
            if (p == peer && blocksLeft == 108) fail.set(false);
          }
        },
        Threading.SAME_THREAD);
    peer.startBlockChainDownload();

    List<Sha256Hash> expectedLocator = new ArrayList<Sha256Hash>();
    expectedLocator.add(b2.getHash());
    expectedLocator.add(b1.getHash());
    expectedLocator.add(unitTestParams.getGenesisBlock().getHash());

    GetBlocksMessage message = (GetBlocksMessage) outbound(writeTarget);
    assertEquals(message.getLocator(), expectedLocator);
    assertEquals(Sha256Hash.ZERO_HASH, message.getStopHash());
  }
Esempio n. 4
0
  // Check that inventory message containing blocks we want is processed correctly.
  @Test
  public void newBlock() throws Exception {
    Block b1 = createFakeBlock(blockStore).block;
    blockChain.add(b1);
    final Block b2 = makeSolvedTestBlock(b1);
    // Receive notification of a new block.
    final InventoryMessage inv = new InventoryMessage(unitTestParams);
    InventoryItem item = new InventoryItem(InventoryItem.Type.Block, b2.getHash());
    inv.addItem(item);

    final AtomicInteger newBlockMessagesReceived = new AtomicInteger(0);

    connect();
    // Round-trip a ping so that we never see the response verack if we attach too quick
    pingAndWait(writeTarget);
    peer.addEventListener(
        new AbstractPeerEventListener() {
          @Override
          public synchronized Message onPreMessageReceived(Peer p, Message m) {
            if (p != peer) fail.set(true);
            if (m instanceof Pong) return m;
            int newValue = newBlockMessagesReceived.incrementAndGet();
            if (newValue == 1 && !inv.equals(m)) fail.set(true);
            else if (newValue == 2 && !b2.equals(m)) fail.set(true);
            else if (newValue > 3) fail.set(true);
            return m;
          }

          @Override
          public synchronized void onBlocksDownloaded(Peer p, Block block, int blocksLeft) {
            int newValue = newBlockMessagesReceived.incrementAndGet();
            if (newValue != 3
                || p != peer
                || !block.equals(b2)
                || blocksLeft != OTHER_PEER_CHAIN_HEIGHT - 2) fail.set(true);
          }
        },
        Threading.SAME_THREAD);
    long height = peer.getBestHeight();

    inbound(writeTarget, inv);
    pingAndWait(writeTarget);
    assertEquals(height + 1, peer.getBestHeight());
    // Response to the getdata message.
    inbound(writeTarget, b2);

    pingAndWait(writeTarget);
    Threading.waitForUserCode();
    pingAndWait(writeTarget);
    assertEquals(3, newBlockMessagesReceived.get());

    GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
    List<InventoryItem> items = getdata.getItems();
    assertEquals(1, items.size());
    assertEquals(b2.getHash(), items.get(0).hash);
    assertEquals(InventoryItem.Type.Block, items.get(0).type);
  }
Esempio n. 5
0
  @Test
  public void fastCatchup() throws Exception {
    connect();

    // Check that blocks before the fast catchup point are retrieved using getheaders, and after
    // using getblocks.
    // This test is INCOMPLETE because it does not check we handle >2000 blocks correctly.
    Block b1 = createFakeBlock(blockStore).block;
    blockChain.add(b1);
    Utils.rollMockClock(60 * 10); // 10 minutes later.
    Block b2 = makeSolvedTestBlock(b1);
    Utils.rollMockClock(60 * 10); // 10 minutes later.
    Block b3 = makeSolvedTestBlock(b2);
    Utils.rollMockClock(60 * 10);
    Block b4 = makeSolvedTestBlock(b3);

    // Request headers until the last 2 blocks.
    peer.setDownloadParameters((Utils.now().getTime() / 1000) - (600 * 2) + 1, false);
    peer.startBlockChainDownload();
    GetHeadersMessage getheaders = (GetHeadersMessage) outbound(writeTarget);
    List<Sha256Hash> expectedLocator = new ArrayList<Sha256Hash>();
    expectedLocator.add(b1.getHash());
    expectedLocator.add(unitTestParams.getGenesisBlock().getHash());
    assertEquals(getheaders.getLocator(), expectedLocator);
    assertEquals(getheaders.getStopHash(), Sha256Hash.ZERO_HASH);
    // Now send all the headers.
    HeadersMessage headers =
        new HeadersMessage(
            unitTestParams, b2.cloneAsHeader(), b3.cloneAsHeader(), b4.cloneAsHeader());
    // We expect to be asked for b3 and b4 again, but this time, with a body.
    expectedLocator.clear();
    expectedLocator.add(b2.getHash());
    expectedLocator.add(b1.getHash());
    expectedLocator.add(unitTestParams.getGenesisBlock().getHash());
    inbound(writeTarget, headers);
    GetBlocksMessage getblocks = (GetBlocksMessage) outbound(writeTarget);
    assertEquals(expectedLocator, getblocks.getLocator());
    assertEquals(Sha256Hash.ZERO_HASH, getblocks.getStopHash());
    // We're supposed to get an inv here.
    InventoryMessage inv = new InventoryMessage(unitTestParams);
    inv.addItem(new InventoryItem(InventoryItem.Type.Block, b3.getHash()));
    inbound(writeTarget, inv);
    GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
    assertEquals(b3.getHash(), getdata.getItems().get(0).hash);
    // All done.
    inbound(writeTarget, b3);
    pingAndWait(writeTarget);
    closePeer(peer);
  }
Esempio n. 6
0
  @Test
  public void getBlock() throws Exception {
    connect();

    Block b1 = createFakeBlock(blockStore).block;
    blockChain.add(b1);
    Block b2 = makeSolvedTestBlock(b1);
    Block b3 = makeSolvedTestBlock(b2);

    // Request the block.
    Future<Block> resultFuture = peer.getBlock(b3.getHash());
    assertFalse(resultFuture.isDone());
    // Peer asks for it.
    GetDataMessage message = (GetDataMessage) outbound(writeTarget);
    assertEquals(message.getItems().get(0).hash, b3.getHash());
    assertFalse(resultFuture.isDone());
    // Peer receives it.
    inbound(writeTarget, b3);
    Block b = resultFuture.get();
    assertEquals(b, b3);
  }
Esempio n. 7
0
  // Check that an inventory tickle is processed correctly when downloading missing blocks is
  // active.
  @Test
  public void invTickle() throws Exception {
    connect();

    Block b1 = createFakeBlock(blockStore).block;
    blockChain.add(b1);
    // Make a missing block.
    Block b2 = makeSolvedTestBlock(b1);
    Block b3 = makeSolvedTestBlock(b2);
    inbound(writeTarget, b3);
    InventoryMessage inv = new InventoryMessage(unitTestParams);
    InventoryItem item = new InventoryItem(InventoryItem.Type.Block, b3.getHash());
    inv.addItem(item);
    inbound(writeTarget, inv);

    GetBlocksMessage getblocks = (GetBlocksMessage) outbound(writeTarget);
    List<Sha256Hash> expectedLocator = new ArrayList<Sha256Hash>();
    expectedLocator.add(b1.getHash());
    expectedLocator.add(unitTestParams.getGenesisBlock().getHash());

    assertEquals(getblocks.getLocator(), expectedLocator);
    assertEquals(getblocks.getStopHash(), b3.getHash());
    assertNull(outbound(writeTarget));
  }
Esempio n. 8
0
  // Check that an inv to a peer that is not set to download missing blocks does nothing.
  @Test
  public void invNoDownload() throws Exception {
    // Don't download missing blocks.
    peer.setDownloadData(false);

    connect();

    // Make a missing block that we receive.
    Block b1 = createFakeBlock(blockStore).block;
    blockChain.add(b1);
    Block b2 = makeSolvedTestBlock(b1);

    // Receive an inv.
    InventoryMessage inv = new InventoryMessage(unitTestParams);
    InventoryItem item = new InventoryItem(InventoryItem.Type.Block, b2.getHash());
    inv.addItem(item);
    inbound(writeTarget, inv);

    // Peer does nothing with it.
    assertNull(outbound(writeTarget));
  }
  // 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);
    }
  }
  // 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();
    }
  }
Esempio n. 11
0
  @Override
  protected TransactionOutputChanges connectTransactions(int height, Block block)
      throws VerificationException, BlockStoreException {
    checkState(lock.isHeldByCurrentThread());
    if (block.transactions == null)
      throw new RuntimeException(
          "connectTransactions called with Block that didn't have transactions!");
    if (!params.passesCheckpoint(height, block.getHash()))
      throw new VerificationException("Block failed checkpoint lockin at " + height);

    blockStore.beginDatabaseBatchWrite();

    LinkedList<StoredTransactionOutput> txOutsSpent = new LinkedList<StoredTransactionOutput>();
    LinkedList<StoredTransactionOutput> txOutsCreated = new LinkedList<StoredTransactionOutput>();
    long sigOps = 0;
    final boolean enforcePayToScriptHash =
        block.getTimeSeconds() >= NetworkParameters.BIP16_ENFORCE_TIME;

    if (scriptVerificationExecutor.isShutdown())
      scriptVerificationExecutor =
          Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    List<Future<VerificationException>> listScriptVerificationResults =
        new ArrayList<Future<VerificationException>>(block.transactions.size());
    try {
      if (!params.isCheckpoint(height)) {
        // BIP30 violator blocks are ones that contain a duplicated transaction. They are all in the
        // checkpoints list and we therefore only check non-checkpoints for duplicated transactions
        // here. See the
        // BIP30 document for more details on this:
        // https://github.com/bitcoin/bips/blob/master/bip-0030.mediawiki
        for (Transaction tx : block.transactions) {
          Sha256Hash hash = tx.getHash();
          // If we already have unspent outputs for this hash, we saw the tx already. Either the
          // block is
          // being added twice (bug) or the block is a BIP30 violator.
          if (blockStore.hasUnspentOutputs(hash, tx.getOutputs().size()))
            throw new VerificationException("Block failed BIP30 test!");
          if (enforcePayToScriptHash) // We already check non-BIP16 sigops in
            // Block.verifyTransactions(true)
            sigOps += tx.getSigOpCount();
        }
      }
      Coin totalFees = Coin.ZERO;
      Coin coinbaseValue = null;
      for (final Transaction tx : block.transactions) {
        boolean isCoinBase = tx.isCoinBase();
        Coin valueIn = Coin.ZERO;
        Coin valueOut = Coin.ZERO;
        final List<Script> prevOutScripts = new LinkedList<Script>();
        if (!isCoinBase) {
          // For each input of the transaction remove the corresponding output from the set of
          // unspent
          // outputs.
          for (int index = 0; index < tx.getInputs().size(); index++) {
            TransactionInput in = tx.getInputs().get(index);
            StoredTransactionOutput prevOut =
                blockStore.getTransactionOutput(
                    in.getOutpoint().getHash(), in.getOutpoint().getIndex());
            if (prevOut == null)
              throw new VerificationException(
                  "Attempted to spend a non-existent or already spent output!");
            // Coinbases can't be spent until they mature, to avoid re-orgs destroying entire
            // transaction
            // chains. The assumption is there will ~never be re-orgs deeper than the spendable
            // coinbase
            // chain depth.
            if (height - prevOut.getHeight() < params.getSpendableCoinbaseDepth())
              throw new VerificationException(
                  "Tried to spend coinbase at depth " + (height - prevOut.getHeight()));
            // TODO: Check we're not spending the genesis transaction here. Satoshis code won't
            // allow it.
            valueIn = valueIn.add(prevOut.getValue());
            if (enforcePayToScriptHash) {
              if (new Script(prevOut.getScriptBytes()).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()));

            // in.getScriptSig().correctlySpends(tx, index, new Script(params,
            // prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length));

            blockStore.removeUnspentTransactionOutput(prevOut);
            txOutsSpent.add(prevOut);
          }
        }
        Sha256Hash hash = tx.getHash();
        for (TransactionOutput out : tx.getOutputs()) {
          valueOut = valueOut.add(out.getValue());
          // For each output, add it to the set of unspent outputs so it can be consumed in future.
          StoredTransactionOutput newOut =
              new StoredTransactionOutput(
                  hash, out.getIndex(), out.getValue(), height, 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 && runScripts) {
          // 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
          || block.getBlockInflation(height).add(totalFees).compareTo(coinbaseValue) < 0)
        throw new VerificationException("Transaction fees out of range");
      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;
      }
    } catch (VerificationException e) {
      scriptVerificationExecutor.shutdownNow();
      blockStore.abortDatabaseBatchWrite();
      throw e;
    } catch (BlockStoreException e) {
      scriptVerificationExecutor.shutdownNow();
      blockStore.abortDatabaseBatchWrite();
      throw e;
    }
    return new TransactionOutputChanges(txOutsCreated, txOutsSpent);
  }
Esempio n. 12
0
 private void load(File file) throws IOException, BlockStoreException {
   log.info("Reading block store from {}", file);
   InputStream input = null;
   try {
     input = new BufferedInputStream(new FileInputStream(file));
     // Read a version byte.
     int version = input.read();
     if (version == -1) {
       // No such file or the file was empty.
       throw new FileNotFoundException(file.getName() + " does not exist or is empty");
     }
     if (version != 1) {
       throw new BlockStoreException("Bad version number: " + version);
     }
     // Chain head pointer is the first thing in the file.
     byte[] chainHeadHash = new byte[32];
     if (input.read(chainHeadHash) < chainHeadHash.length)
       throw new BlockStoreException("Truncated block store: cannot read chain head hash");
     this.chainHead = new Sha256Hash(chainHeadHash);
     log.info("Read chain head from disk: {}", this.chainHead);
     long now = System.currentTimeMillis();
     // Rest of file is raw block headers.
     byte[] headerBytes = new byte[Block.HEADER_SIZE];
     try {
       while (true) {
         // Read a block from disk.
         if (input.read(headerBytes) < 80) {
           // End of file.
           break;
         }
         // Parse it.
         Block b = new Block(params, headerBytes);
         // Look up the previous block it connects to.
         StoredBlock prev = get(b.getPrevBlockHash());
         StoredBlock s;
         if (prev == null) {
           // First block in the stored chain has to be treated specially.
           if (b.equals(params.genesisBlock)) {
             s =
                 new StoredBlock(
                     params.genesisBlock.cloneAsHeader(), params.genesisBlock.getWork(), 0);
           } else {
             throw new BlockStoreException(
                 "Could not connect "
                     + b.getHash().toString()
                     + " to "
                     + b.getPrevBlockHash().toString());
           }
         } else {
           // Don't try to verify the genesis block to avoid upsetting the unit tests.
           b.verifyHeader();
           // Calculate its height and total chain work.
           s = prev.build(b);
         }
         // Save in memory.
         blockMap.put(b.getHash(), s);
       }
     } catch (ProtocolException e) {
       // Corrupted file.
       throw new BlockStoreException(e);
     } catch (VerificationException e) {
       // Should not be able to happen unless the file contains bad blocks.
       throw new BlockStoreException(e);
     }
     long elapsed = System.currentTimeMillis() - now;
     log.info("Block chain read complete in {}ms", elapsed);
   } finally {
     if (input != null) input.close();
   }
 }
  @Test
  public void testFinalizedBlocks() throws Exception {
    final int UNDOABLE_BLOCKS_STORED = 10;
    store = createStore(params, UNDOABLE_BLOCKS_STORED);
    chain = new FullPrunedBlockChain(params, store);

    // Check that we aren't accidentally leaving any references
    // to the full StoredUndoableBlock's lying around (ie memory leaks)

    ECKey outKey = new ECKey();
    int height = 1;

    // Build some blocks on genesis block to create a spendable output
    Block rollingBlock =
        params
            .getGenesisBlock()
            .createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++);
    chain.add(rollingBlock);
    TransactionOutPoint spendableOutput =
        new TransactionOutPoint(params, 0, rollingBlock.getTransactions().get(0).getHash());
    byte[] spendableOutputScriptPubKey =
        rollingBlock.getTransactions().get(0).getOutputs().get(0).getScriptBytes();
    for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) {
      rollingBlock =
          rollingBlock.createNextBlockWithCoinbase(
              Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++);
      chain.add(rollingBlock);
    }

    WeakReference<UTXO> out =
        new WeakReference<UTXO>(
            store.getTransactionOutput(spendableOutput.getHash(), spendableOutput.getIndex()));
    rollingBlock = rollingBlock.createNextBlock(null);

    Transaction t = new Transaction(params);
    // Entirely invalid scriptPubKey
    t.addOutput(new TransactionOutput(params, t, FIFTY_COINS, new byte[] {}));
    t.addSignedInput(spendableOutput, new Script(spendableOutputScriptPubKey), outKey);
    rollingBlock.addTransaction(t);
    rollingBlock.solve();

    chain.add(rollingBlock);
    WeakReference<StoredUndoableBlock> undoBlock =
        new WeakReference<StoredUndoableBlock>(store.getUndoBlock(rollingBlock.getHash()));

    StoredUndoableBlock storedUndoableBlock = undoBlock.get();
    assertNotNull(storedUndoableBlock);
    assertNull(storedUndoableBlock.getTransactions());
    WeakReference<TransactionOutputChanges> changes =
        new WeakReference<TransactionOutputChanges>(storedUndoableBlock.getTxOutChanges());
    assertNotNull(changes.get());
    storedUndoableBlock = null; // Blank the reference so it can be GCd.

    // Create a chain longer than UNDOABLE_BLOCKS_STORED
    for (int i = 0; i < UNDOABLE_BLOCKS_STORED; i++) {
      rollingBlock = rollingBlock.createNextBlock(null);
      chain.add(rollingBlock);
    }
    // Try to get the garbage collector to run
    System.gc();
    assertNull(undoBlock.get());
    assertNull(changes.get());
    assertNull(out.get());
    try {
      store.close();
    } catch (Exception e) {
    }
  }
Esempio n. 14
0
 public void addBlock(Block block) {
   addItem(new InventoryItem(InventoryItem.Type.Block, block.getHash()));
 }
Esempio n. 15
0
  @Test
  public void chainDownloadEnd2End() throws Exception {
    // A full end-to-end test of the chain download process, with a new block being solved in the
    // middle.
    Block b1 = createFakeBlock(blockStore).block;
    blockChain.add(b1);
    Block b2 = makeSolvedTestBlock(b1);
    Block b3 = makeSolvedTestBlock(b2);
    Block b4 = makeSolvedTestBlock(b3);
    Block b5 = makeSolvedTestBlock(b4);

    connect();

    peer.startBlockChainDownload();
    GetBlocksMessage getblocks = (GetBlocksMessage) outbound(writeTarget);
    assertEquals(blockStore.getChainHead().getHeader().getHash(), getblocks.getLocator().get(0));
    assertEquals(Sha256Hash.ZERO_HASH, getblocks.getStopHash());
    // Remote peer sends us an inv with some blocks.
    InventoryMessage inv = new InventoryMessage(unitTestParams);
    inv.addBlock(b2);
    inv.addBlock(b3);
    // We do a getdata on them.
    inbound(writeTarget, inv);
    GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
    assertEquals(b2.getHash(), getdata.getItems().get(0).hash);
    assertEquals(b3.getHash(), getdata.getItems().get(1).hash);
    assertEquals(2, getdata.getItems().size());
    // Remote peer sends us the blocks. The act of doing a getdata for b3 results in getting an inv
    // with just the
    // best chain head in it.
    inbound(writeTarget, b2);
    inbound(writeTarget, b3);

    inv = new InventoryMessage(unitTestParams);
    inv.addBlock(b5);
    // We request the head block.
    inbound(writeTarget, inv);
    getdata = (GetDataMessage) outbound(writeTarget);
    assertEquals(b5.getHash(), getdata.getItems().get(0).hash);
    assertEquals(1, getdata.getItems().size());
    // Peer sends us the head block. The act of receiving the orphan block triggers a getblocks to
    // fill in the
    // rest of the chain.
    inbound(writeTarget, b5);
    getblocks = (GetBlocksMessage) outbound(writeTarget);
    assertEquals(b5.getHash(), getblocks.getStopHash());
    assertEquals(b3.getHash(), getblocks.getLocator().get(0));
    // At this point another block is solved and broadcast. The inv triggers a getdata but we do NOT
    // send another
    // getblocks afterwards, because that would result in us receiving the same set of blocks twice
    // which is a
    // timewaste. The getblocks message that would have been generated is set to be the same as the
    // previous
    // because we walk backwards down the orphan chain and then discover we already asked for those
    // blocks, so
    // nothing is done.
    Block b6 = makeSolvedTestBlock(b5);
    inv = new InventoryMessage(unitTestParams);
    inv.addBlock(b6);
    inbound(writeTarget, inv);
    getdata = (GetDataMessage) outbound(writeTarget);
    assertEquals(1, getdata.getItems().size());
    assertEquals(b6.getHash(), getdata.getItems().get(0).hash);
    inbound(writeTarget, b6);
    assertNull(outbound(writeTarget)); // Nothing is sent at this point.
    // We're still waiting for the response to the getblocks (b3,b5) sent above.
    inv = new InventoryMessage(unitTestParams);
    inv.addBlock(b4);
    inv.addBlock(b5);
    inbound(writeTarget, inv);
    getdata = (GetDataMessage) outbound(writeTarget);
    assertEquals(1, getdata.getItems().size());
    assertEquals(b4.getHash(), getdata.getItems().get(0).hash);
    // We already have b5 from before, so it's not requested again.
    inbound(writeTarget, b4);
    assertNull(outbound(writeTarget));
    // b5 and b6 are now connected by the block chain and we're done.
    assertNull(outbound(writeTarget));
    closePeer(peer);
  }
Esempio n. 16
0
  /** 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));
  }