@Test
  public void testGetOpenTransactionOutputs() 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);
    Transaction transaction = rollingBlock.getTransactions().get(0);
    TransactionOutPoint spendableOutput = new TransactionOutPoint(params, 0, transaction.getHash());
    byte[] spendableOutputScriptPubKey = transaction.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);
    }
    rollingBlock = rollingBlock.createNextBlock(null);

    // Create bitcoin spend of 1 BTC.
    ECKey toKey = new ECKey();
    Coin amount = Coin.valueOf(100000000);
    Address address = new Address(params, toKey.getPubKeyHash());
    Coin totalAmount = Coin.ZERO;

    Transaction t = new Transaction(params);
    t.addOutput(new TransactionOutput(params, t, amount, toKey));
    t.addSignedInput(spendableOutput, new Script(spendableOutputScriptPubKey), outKey);
    rollingBlock.addTransaction(t);
    rollingBlock.solve();
    chain.add(rollingBlock);
    totalAmount = totalAmount.add(amount);

    List<UTXO> outputs = store.getOpenTransactionOutputs(Lists.newArrayList(address));
    assertNotNull(outputs);
    assertEquals("Wrong Number of Outputs", 1, outputs.size());
    UTXO output = outputs.get(0);
    assertEquals("The address is not equal", address.toString(), output.getAddress());
    assertEquals("The amount is not equal", totalAmount, output.getValue());

    outputs = null;
    output = null;
    try {
      store.close();
    } catch (Exception e) {
    }
  }
예제 #2
0
 @Override
 protected StoredBlock addToBlockStore(StoredBlock storedPrev, Block block)
     throws BlockStoreException, VerificationException {
   StoredBlock newBlock = storedPrev.build(block);
   blockStore.put(
       newBlock, new StoredUndoableBlock(newBlock.getHeader().getHash(), block.transactions));
   return newBlock;
 }
예제 #3
0
 /** Constructs a block chain connected to the given list of wallets and a store. */
 public FullPrunedBlockChain(
     Context context, List<Wallet> listeners, FullPrunedBlockStore blockStore)
     throws BlockStoreException {
   super(context, listeners, blockStore);
   this.blockStore = blockStore;
   // Ignore upgrading for now
   this.chainHead = blockStore.getVerifiedChainHead();
 }
예제 #4
0
 @Override
 protected StoredBlock addToBlockStore(
     StoredBlock storedPrev, Block header, TransactionOutputChanges txOutChanges)
     throws BlockStoreException, VerificationException {
   StoredBlock newBlock = storedPrev.build(header);
   blockStore.put(newBlock, new StoredUndoableBlock(newBlock.getHeader().getHash(), txOutChanges));
   return newBlock;
 }
  /** Test that if the block height is missing from coinbase of a version 2 block, it's rejected. */
  @Test
  public void missingHeightFromCoinbase() throws Exception {
    final int UNDOABLE_BLOCKS_STORED = params.getMajorityEnforceBlockUpgrade() + 1;
    store = createStore(params, UNDOABLE_BLOCKS_STORED);
    try {
      chain = new FullPrunedBlockChain(params, store);
      ECKey outKey = new ECKey();
      int height = 1;
      Block chainHead = params.getGenesisBlock();

      // Build some blocks on genesis block to create a spendable output.

      // Put in just enough v1 blocks to stop the v2 blocks from forming a majority
      for (height = 1;
          height <= (params.getMajorityWindow() - params.getMajorityEnforceBlockUpgrade());
          height++) {
        chainHead =
            chainHead.createNextBlockWithCoinbase(
                Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height);
        chain.add(chainHead);
      }

      // Fill the rest of the window in with v2 blocks
      for (; height < params.getMajorityWindow(); height++) {
        chainHead =
            chainHead.createNextBlockWithCoinbase(
                Block.BLOCK_VERSION_BIP34, outKey.getPubKey(), height);
        chain.add(chainHead);
      }
      // Throw a broken v2 block in before we have a supermajority to enable
      // enforcement, which should validate as-is
      chainHead =
          chainHead.createNextBlockWithCoinbase(
              Block.BLOCK_VERSION_BIP34, outKey.getPubKey(), height * 2);
      chain.add(chainHead);
      height++;

      // Trying to add a broken v2 block should now result in rejection as
      // we have a v2 supermajority
      thrown.expect(VerificationException.CoinbaseHeightMismatch.class);
      chainHead =
          chainHead.createNextBlockWithCoinbase(
              Block.BLOCK_VERSION_BIP34, outKey.getPubKey(), height * 2);
      chain.add(chainHead);
    } catch (final VerificationException ex) {
      throw (Exception) ex.getCause();
    } finally {
      try {
        store.close();
      } catch (Exception e) {
        // Catch and drop any exception so a break mid-test doesn't result
        // in a new exception being thrown and the original lost
      }
    }
  }
  @Test
  public void testGeneratedChain() throws Exception {
    // Tests various test cases from FullBlockTestGenerator
    FullBlockTestGenerator generator = new FullBlockTestGenerator(params);
    RuleList blockList = generator.getBlocksToTest(false, false, null);

    store = createStore(params, blockList.maximumReorgBlockCount);
    chain = new FullPrunedBlockChain(params, store);

    for (Rule rule : blockList.list) {
      if (!(rule instanceof FullBlockTestGenerator.BlockAndValidity)) continue;
      FullBlockTestGenerator.BlockAndValidity block =
          (FullBlockTestGenerator.BlockAndValidity) rule;
      log.info("Testing rule " + block.ruleName + " with block hash " + block.block.getHash());
      boolean threw = false;
      try {
        if (chain.add(block.block) != block.connects) {
          log.error("Block didn't match connects flag on block " + block.ruleName);
          fail();
        }
      } catch (VerificationException e) {
        threw = true;
        if (!block.throwsException) {
          log.error("Block didn't match throws flag on block " + block.ruleName);
          throw e;
        }
        if (block.connects) {
          log.error("Block didn't match connects flag on block " + block.ruleName);
          fail();
        }
      }
      if (!threw && block.throwsException) {
        log.error("Block didn't match throws flag on block " + block.ruleName);
        fail();
      }
      if (!chain.getChainHead().getHeader().getHash().equals(block.hashChainTipAfterBlock)) {
        log.error("New block head didn't match the correct value after block " + block.ruleName);
        fail();
      }
      if (chain.getChainHead().getHeight() != block.heightAfterBlock) {
        log.error("New block head didn't match the correct height after block " + block.ruleName);
        fail();
      }
    }
    try {
      store.close();
    } catch (Exception e) {
    }
  }
  @Test
  public void testFirst100KBlocks() throws Exception {
    NetworkParameters params = MainNetParams.get();
    Context context = new Context(params);
    File blockFile = new File(getClass().getResource("first-100k-blocks.dat").getFile());
    BlockFileLoader loader = new BlockFileLoader(params, Arrays.asList(blockFile));

    store = createStore(params, 10);
    resetStore(store);
    chain = new FullPrunedBlockChain(context, store);
    for (Block block : loader) chain.add(block);
    try {
      store.close();
    } catch (Exception e) {
    }
  }
  @Test
  public void skipScripts() throws Exception {
    store = createStore(params, 10);
    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);
    TransactionOutput spendableOutput = rollingBlock.getTransactions().get(0).getOutput(0);
    for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) {
      rollingBlock =
          rollingBlock.createNextBlockWithCoinbase(
              Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++);
      chain.add(rollingBlock);
    }

    rollingBlock = rollingBlock.createNextBlock(null);
    Transaction t = new Transaction(params);
    t.addOutput(new TransactionOutput(params, t, FIFTY_COINS, new byte[] {}));
    TransactionInput input = t.addInput(spendableOutput);
    // Invalid script.
    input.clearScriptBytes();
    rollingBlock.addTransaction(t);
    rollingBlock.solve();
    chain.setRunScripts(false);
    try {
      chain.add(rollingBlock);
    } catch (VerificationException e) {
      fail();
    }
    try {
      store.close();
    } catch (Exception e) {
    }
  }
예제 #9
0
 /**
  * This is broken for blocks that do not pass BIP30, so all BIP30-failing blocks which are allowed
  * to fail BIP30 must be checkpointed.
  */
 @Override
 protected void disconnectTransactions(StoredBlock oldBlock)
     throws PrunedException, BlockStoreException {
   checkState(lock.isHeldByCurrentThread());
   blockStore.beginDatabaseBatchWrite();
   try {
     StoredUndoableBlock undoBlock = blockStore.getUndoBlock(oldBlock.getHeader().getHash());
     if (undoBlock == null) throw new PrunedException(oldBlock.getHeader().getHash());
     TransactionOutputChanges txOutChanges = undoBlock.getTxOutChanges();
     for (UTXO out : txOutChanges.txOutsSpent) blockStore.addUnspentTransactionOutput(out);
     for (UTXO out : txOutChanges.txOutsCreated) blockStore.removeUnspentTransactionOutput(out);
   } catch (PrunedException e) {
     blockStore.abortDatabaseBatchWrite();
     throw e;
   } catch (BlockStoreException e) {
     blockStore.abortDatabaseBatchWrite();
     throw e;
   }
 }
  @Test
  public void testUTXOProviderWithWallet() 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);
    Transaction transaction = rollingBlock.getTransactions().get(0);
    TransactionOutPoint spendableOutput = new TransactionOutPoint(params, 0, transaction.getHash());
    byte[] spendableOutputScriptPubKey = transaction.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);
    }
    rollingBlock = rollingBlock.createNextBlock(null);

    // Create 1 BTC spend to a key in this wallet (to ourselves).
    Wallet wallet = new Wallet(params);
    assertEquals(
        "Available balance is incorrect",
        Coin.ZERO,
        wallet.getBalance(Wallet.BalanceType.AVAILABLE));
    assertEquals(
        "Estimated balance is incorrect",
        Coin.ZERO,
        wallet.getBalance(Wallet.BalanceType.ESTIMATED));

    wallet.setUTXOProvider(store);
    ECKey toKey = wallet.freshReceiveKey();
    Coin amount = Coin.valueOf(100000000);

    Transaction t = new Transaction(params);
    t.addOutput(new TransactionOutput(params, t, amount, toKey));
    t.addSignedInput(spendableOutput, new Script(spendableOutputScriptPubKey), outKey);
    rollingBlock.addTransaction(t);
    rollingBlock.solve();
    chain.add(rollingBlock);

    // Create another spend of 1/2 the value of BTC we have available using the wallet (store coin
    // selector).
    ECKey toKey2 = new ECKey();
    Coin amount2 = amount.divide(2);
    Address address2 = new Address(params, toKey2.getPubKeyHash());
    Wallet.SendRequest req = Wallet.SendRequest.to(address2, amount2);
    wallet.completeTx(req);
    wallet.commitTx(req.tx);
    Coin fee = req.fee;

    // There should be one pending tx (our spend).
    assertEquals(
        "Wrong number of PENDING.4", 1, wallet.getPoolSize(WalletTransaction.Pool.PENDING));
    Coin totalPendingTxAmount = Coin.ZERO;
    for (Transaction tx : wallet.getPendingTransactions()) {
      totalPendingTxAmount = totalPendingTxAmount.add(tx.getValueSentToMe(wallet));
    }

    // The availbale balance should be the 0 (as we spent the 1 BTC that's pending) and estimated
    // should be 1/2 - fee BTC
    assertEquals(
        "Available balance is incorrect",
        Coin.ZERO,
        wallet.getBalance(Wallet.BalanceType.AVAILABLE));
    assertEquals(
        "Estimated balance is incorrect",
        amount2.subtract(fee),
        wallet.getBalance(Wallet.BalanceType.ESTIMATED));
    assertEquals("Pending tx amount is incorrect", amount2.subtract(fee), totalPendingTxAmount);
    try {
      store.close();
    } catch (Exception e) {
    }
  }
  @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) {
    }
  }
예제 #12
0
 @Override
 protected StoredBlock getStoredBlockInCurrentScope(Sha256Hash hash) throws BlockStoreException {
   checkState(lock.isHeldByCurrentThread());
   return blockStore.getOnceUndoableStoredBlock(hash);
 }
예제 #13
0
 @Override
 protected void notSettingChainHead() throws BlockStoreException {
   blockStore.abortDatabaseBatchWrite();
 }
예제 #14
0
 @Override
 protected void doSetChainHead(StoredBlock chainHead) throws BlockStoreException {
   checkState(lock.isHeldByCurrentThread());
   blockStore.setVerifiedChainHead(chainHead);
   blockStore.commitDatabaseBatchWrite();
 }
예제 #15
0
  @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<UTXO> txOutsSpent = new LinkedList<UTXO>();
        LinkedList<UTXO> txOutsCreated = new LinkedList<UTXO>();
        long sigOps = 0;
        final Set<VerifyFlag> verifyFlags = EnumSet.noneOf(VerifyFlag.class);
        if (newBlock.getHeader().getTimeSeconds() >= NetworkParameters.BIP16_ENFORCE_TIME)
          verifyFlags.add(VerifyFlag.P2SH);
        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 UTXO 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 (prevOut.isCoinbase()
                  && 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 (verifyFlags.contains(VerifyFlag.P2SH)) {
                if (prevOut.getScript().isPayToScriptHash())
                  sigOps += Script.getP2SHSigOpCount(in.getScriptBytes());
                if (sigOps > Block.MAX_BLOCK_SIGOPS)
                  throw new VerificationException("Too many P2SH SigOps in block");
              }

              prevOutScripts.add(prevOut.getScript());

              blockStore.removeUnspentTransactionOutput(prevOut);
              txOutsSpent.add(prevOut);
            }
          }
          Sha256Hash hash = tx.getHash();
          for (TransactionOutput out : tx.getOutputs()) {
            valueOut = valueOut.add(out.getValue());
            Script script = getScript(out.getScriptBytes());
            UTXO newOut =
                new UTXO(
                    hash,
                    out.getIndex(),
                    out.getValue(),
                    newBlock.getHeight(),
                    isCoinBase,
                    script,
                    getScriptAddress(script));
            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(params.getMaxMoney()) > 0)
            throw new VerificationException("Transaction output value out of range");
          if (isCoinBase) {
            coinbaseValue = valueOut;
          } else {
            if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(params.getMaxMoney()) > 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, verifyFlags));
            scriptVerificationExecutor.execute(future);
            listScriptVerificationResults.add(future);
          }
        }
        if (totalFees.compareTo(params.getMaxMoney()) > 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 (UTXO out : txOutChanges.txOutsCreated) {
            Sha256Hash hash = out.getHash();
            if (blockStore.getTransactionOutput(hash, out.getIndex()) != null)
              throw new VerificationException("Block failed BIP30 test!");
          }
        for (UTXO out : txOutChanges.txOutsCreated) blockStore.addUnspentTransactionOutput(out);
        for (UTXO 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;
  }
예제 #16
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<UTXO> txOutsSpent = new LinkedList<UTXO>();
    LinkedList<UTXO> txOutsCreated = new LinkedList<UTXO>();
    long sigOps = 0;
    final Set<VerifyFlag> verifyFlags = EnumSet.noneOf(VerifyFlag.class);
    if (block.getTimeSeconds() >= NetworkParameters.BIP16_ENFORCE_TIME)
      verifyFlags.add(VerifyFlag.P2SH);

    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 (verifyFlags.contains(
              VerifyFlag
                  .P2SH)) // 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);
            UTXO 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 (prevOut.isCoinbase()) {
              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 (verifyFlags.contains(VerifyFlag.P2SH)) {
              if (prevOut.getScript().isPayToScriptHash())
                sigOps += Script.getP2SHSigOpCount(in.getScriptBytes());
              if (sigOps > Block.MAX_BLOCK_SIGOPS)
                throw new VerificationException("Too many P2SH SigOps in block");
            }

            prevOutScripts.add(prevOut.getScript());
            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.
          Script script = getScript(out.getScriptBytes());
          UTXO newOut =
              new UTXO(
                  hash,
                  out.getIndex(),
                  out.getValue(),
                  height,
                  isCoinBase,
                  script,
                  getScriptAddress(script));
          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(params.getMaxMoney()) > 0)
          throw new VerificationException("Transaction output value out of range");
        if (isCoinBase) {
          coinbaseValue = valueOut;
        } else {
          if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(params.getMaxMoney()) > 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, verifyFlags));
          scriptVerificationExecutor.execute(future);
          listScriptVerificationResults.add(future);
        }
      }
      if (totalFees.compareTo(params.getMaxMoney()) > 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);
  }