Ejemplo n.º 1
0
 private void parsePaymentRequest(Protos.PaymentRequest request) throws PaymentProtocolException {
   try {
     if (request == null) throw new PaymentProtocolException("request cannot be null");
     if (request.getPaymentDetailsVersion() != 1)
       throw new PaymentProtocolException.InvalidVersion(
           "Version 1 required. Received version " + request.getPaymentDetailsVersion());
     paymentRequest = request;
     if (!request.hasSerializedPaymentDetails())
       throw new PaymentProtocolException("No PaymentDetails");
     paymentDetails =
         Protos.PaymentDetails.newBuilder()
             .mergeFrom(request.getSerializedPaymentDetails())
             .build();
     if (paymentDetails == null) throw new PaymentProtocolException("Invalid PaymentDetails");
     if (!paymentDetails.hasNetwork()) params = MainNetParams.get();
     else params = NetworkParameters.fromPmtProtocolID(paymentDetails.getNetwork());
     if (params == null)
       throw new PaymentProtocolException.InvalidNetwork(
           "Invalid network " + paymentDetails.getNetwork());
     if (paymentDetails.getOutputsCount() < 1)
       throw new PaymentProtocolException.InvalidOutputs("No outputs");
     for (Protos.Output output : paymentDetails.getOutputsList()) {
       if (output.hasAmount()) totalValue = totalValue.add(Coin.valueOf(output.getAmount()));
     }
     // This won't ever happen in practice. It would only happen if the user provided outputs
     // that are obviously invalid. Still, we don't want to silently overflow.
     if (params.hasMaxMoney() && totalValue.compareTo(params.getMaxMoney()) > 0)
       throw new PaymentProtocolException.InvalidOutputs("The outputs are way too big.");
   } catch (InvalidProtocolBufferException e) {
     throw new PaymentProtocolException(e);
   }
 }
 /**
  * Updates the outputs on the payment contract transaction and re-signs it. The state must be
  * READY in order to call this method. The signature that is returned should be sent to the server
  * so it has the ability to broadcast the best seen payment when the channel closes or times out.
  *
  * <p>The returned signature is over the payment transaction, which we never have a valid copy of
  * and thus there is no accessor for it on this object.
  *
  * <p>To spend the whole channel increment by {@link PaymentChannelClientState#getTotalValue()} -
  * {@link PaymentChannelClientState#getValueRefunded()}
  *
  * @param size How many satoshis to increment the payment by (note: not the new total).
  * @throws ValueOutOfRangeException If size is negative or the channel does not have sufficient
  *     money in it to complete this payment.
  */
 public synchronized IncrementedPayment incrementPaymentBy(Coin size)
     throws ValueOutOfRangeException {
   checkState(state == State.READY);
   checkNotExpired();
   checkNotNull(size); // Validity of size will be checked by makeUnsignedChannelContract.
   if (size.signum() < 0) throw new ValueOutOfRangeException("Tried to decrement payment");
   Coin newValueToMe = valueToMe.subtract(size);
   if (newValueToMe.compareTo(Transaction.MIN_NONDUST_OUTPUT) < 0 && newValueToMe.signum() > 0) {
     log.info(
         "New value being sent back as change was smaller than minimum nondust output, sending all");
     size = valueToMe;
     newValueToMe = Coin.ZERO;
   }
   if (newValueToMe.signum() < 0)
     throw new ValueOutOfRangeException(
         "Channel has too little money to pay " + size + " satoshis");
   Transaction tx = makeUnsignedChannelContract(newValueToMe);
   log.info("Signing new payment tx {}", tx);
   Transaction.SigHash mode;
   // If we spent all the money we put into this channel, we (by definition) don't care what the
   // outputs are, so
   // we sign with SIGHASH_NONE to let the server do what it wants.
   if (newValueToMe.equals(Coin.ZERO)) mode = Transaction.SigHash.NONE;
   else mode = Transaction.SigHash.SINGLE;
   TransactionSignature sig = tx.calculateSignature(0, myKey, multisigScript, mode, true);
   valueToMe = newValueToMe;
   updateChannelInWallet();
   IncrementedPayment payment = new IncrementedPayment();
   payment.signature = sig;
   payment.amount = size;
   return payment;
 }
 /**
  * Creates the initial multisig contract and incomplete refund transaction which can be requested
  * at the appropriate time using {@link PaymentChannelClientState#getIncompleteRefundTransaction}
  * and {@link PaymentChannelClientState#getMultisigContract()}. The way the contract is crafted
  * can be adjusted by overriding {@link
  * PaymentChannelClientState#editContractSendRequest(com.google.bitcoin.core.Wallet.SendRequest)}.
  * By default unconfirmed coins are allowed to be used, as for micropayments the risk should be
  * relatively low.
  *
  * @throws ValueOutOfRangeException if the value being used is too small to be accepted by the
  *     network
  * @throws InsufficientMoneyException if the wallet doesn't contain enough balance to initiate
  */
 public synchronized void initiate() throws ValueOutOfRangeException, InsufficientMoneyException {
   final NetworkParameters params = wallet.getParams();
   Transaction template = new Transaction(params);
   // We always place the client key before the server key because, if either side wants some
   // privacy, they can
   // use a fresh key for the the multisig contract and nowhere else
   List<ECKey> keys = Lists.newArrayList(myKey, serverMultisigKey);
   // There is also probably a change output, but we don't bother shuffling them as it's obvious
   // from the
   // format which one is the change. If we start obfuscating the change output better in future
   // this may
   // be worth revisiting.
   TransactionOutput multisigOutput =
       template.addOutput(totalValue, ScriptBuilder.createMultiSigOutputScript(2, keys));
   if (multisigOutput.getMinNonDustValue().compareTo(totalValue) > 0)
     throw new ValueOutOfRangeException("totalValue too small to use");
   Wallet.SendRequest req = Wallet.SendRequest.forTx(template);
   req.coinSelector = AllowUnconfirmedCoinSelector.get();
   editContractSendRequest(req);
   req.shuffleOutputs = false; // TODO: Fix things so shuffling is usable.
   wallet.completeTx(req);
   Coin multisigFee = req.tx.getFee();
   multisigContract = req.tx;
   // Build a refund transaction that protects us in the case of a bad server that's just trying to
   // cause havoc
   // by locking up peoples money (perhaps as a precursor to a ransom attempt). We time lock it so
   // the server
   // has an assurance that we cannot take back our money by claiming a refund before the channel
   // closes - this
   // relies on the fact that since Bitcoin 0.8 time locked transactions are non-final. This will
   // need to change
   // in future as it breaks the intended design of timelocking/tx replacement, but for now it
   // simplifies this
   // specific protocol somewhat.
   refundTx = new Transaction(params);
   refundTx
       .addInput(multisigOutput)
       .setSequenceNumber(0); // Allow replacement when it's eventually reactivated.
   refundTx.setLockTime(expiryTime);
   if (totalValue.compareTo(Coin.CENT) < 0) {
     // Must pay min fee.
     final Coin valueAfterFee = totalValue.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
     if (Transaction.MIN_NONDUST_OUTPUT.compareTo(valueAfterFee) > 0)
       throw new ValueOutOfRangeException("totalValue too small to use");
     refundTx.addOutput(valueAfterFee, myKey.toAddress(params));
     refundFees = multisigFee.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
   } else {
     refundTx.addOutput(totalValue, myKey.toAddress(params));
     refundFees = multisigFee;
   }
   refundTx.getConfidence().setSource(TransactionConfidence.Source.SELF);
   log.info(
       "initiated channel with multi-sig contract {}, refund {}",
       multisigContract.getHashAsString(),
       refundTx.getHashAsString());
   state = State.INITIATED;
   // Client should now call getIncompleteRefundTransaction() and send it to the server.
 }
Ejemplo n.º 4
0
 /**
  * Create a standard pay to address output for usage in {@link #createPaymentRequest} and {@link
  * #createPaymentMessage}.
  *
  * @param amount amount to pay, or null
  * @param address address to pay to
  * @return output
  */
 public static Protos.Output createPayToAddressOutput(@Nullable Coin amount, Address address) {
   Protos.Output.Builder output = Protos.Output.newBuilder();
   if (amount != null) {
     final NetworkParameters params = address.getParameters();
     if (params.hasMaxMoney() && amount.compareTo(params.getMaxMoney()) > 0)
       throw new IllegalArgumentException("Amount too big: " + amount);
     output.setAmount(amount.value);
   } else {
     output.setAmount(0);
   }
   output.setScript(ByteString.copyFrom(ScriptBuilder.createOutputScript(address).getProgram()));
   return output.build();
 }
Ejemplo n.º 5
0
 public TransactionOutput(
     NetworkParameters params, @Nullable Transaction parent, Coin value, byte[] scriptBytes) {
   super(params);
   // Negative values obviously make no sense, except for -1 which is used as a sentinel value when
   // calculating
   // SIGHASH_SINGLE signatures, so unfortunately we have to allow that here.
   checkArgument(
       value.signum() >= 0 || value.equals(Coin.NEGATIVE_SATOSHI), "Negative values not allowed");
   checkArgument(
       value.compareTo(NetworkParameters.MAX_MONEY) < 0,
       "Values larger than MAX_MONEY not allowed");
   this.value = value.value;
   this.scriptBytes = scriptBytes;
   parentTransaction = parent;
   availableForSpending = true;
   length = 8 + VarInt.sizeOf(scriptBytes.length) + scriptBytes.length;
 }
  /**
   * Closes this channel and broadcasts the highest value payment transaction on the network.
   *
   * <p>This will set the state to {@link State#CLOSED} if the transaction is successfully broadcast
   * on the network. If we fail to broadcast for some reason, the state is set to {@link
   * State#ERROR}.
   *
   * <p>If the current state is before {@link State#READY} (ie we have not finished initializing the
   * channel), we simply set the state to {@link State#CLOSED} and let the client handle getting its
   * refund transaction confirmed.
   *
   * @return a future which completes when the provided multisig contract successfully broadcasts,
   *     or throws if the broadcast fails for some reason. Note that if the network simply rejects
   *     the transaction, this future will never complete, a timeout should be used.
   * @throws InsufficientMoneyException If the payment tx would have cost more in fees to spend than
   *     it is worth.
   */
  public synchronized ListenableFuture<Transaction> close() throws InsufficientMoneyException {
    if (storedServerChannel != null) {
      StoredServerChannel temp = storedServerChannel;
      storedServerChannel = null;
      StoredPaymentChannelServerStates channels =
          (StoredPaymentChannelServerStates)
              wallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID);
      channels.closeChannel(
          temp); // May call this method again for us (if it wasn't the original caller)
      if (state.compareTo(State.CLOSING) >= 0) return closedFuture;
    }

    if (state.ordinal() < State.READY.ordinal()) {
      log.error("Attempt to settle channel in state " + state);
      state = State.CLOSED;
      closedFuture.set(null);
      return closedFuture;
    }
    if (state != State.READY) {
      // TODO: What is this codepath for?
      log.warn("Failed attempt to settle a channel in state " + state);
      return closedFuture;
    }
    Transaction tx = null;
    try {
      Wallet.SendRequest req = makeUnsignedChannelContract(bestValueToMe);
      tx = req.tx;
      // Provide a throwaway signature so that completeTx won't complain out about unsigned inputs
      // it doesn't
      // know how to sign. Note that this signature does actually have to be valid, so we can't use
      // a dummy
      // signature to save time, because otherwise completeTx will try to re-sign it to make it
      // valid and then
      // die. We could probably add features to the SendRequest API to make this a bit more
      // efficient.
      signMultisigInput(tx, Transaction.SigHash.NONE, true);
      // Let wallet handle adding additional inputs/fee as necessary.
      req.shuffleOutputs = false;
      req.missingSigsMode = Wallet.MissingSigsMode.USE_DUMMY_SIG;
      wallet.completeTx(req); // TODO: Fix things so shuffling is usable.
      feePaidForPayment = req.tx.getFee();
      log.info("Calculated fee is {}", feePaidForPayment);
      if (feePaidForPayment.compareTo(bestValueToMe) > 0) {
        final String msg =
            String.format(
                Locale.US,
                "Had to pay more in fees (%s) than the channel was worth (%s)",
                feePaidForPayment,
                bestValueToMe);
        throw new InsufficientMoneyException(feePaidForPayment.subtract(bestValueToMe), msg);
      }
      // Now really sign the multisig input.
      signMultisigInput(tx, Transaction.SigHash.ALL, false);
      // Some checks that shouldn't be necessary but it can't hurt to check.
      tx.verify(); // Sanity check syntax.
      for (TransactionInput input : tx.getInputs())
        input.verify(); // Run scripts and ensure it is valid.
    } catch (InsufficientMoneyException e) {
      throw e; // Don't fall through.
    } catch (Exception e) {
      log.error(
          "Could not verify self-built tx\nMULTISIG {}\nCLOSE {}",
          multisigContract,
          tx != null ? tx : "");
      throw new RuntimeException(e); // Should never happen.
    }
    state = State.CLOSING;
    log.info("Closing channel, broadcasting tx {}", tx);
    // The act of broadcasting the transaction will add it to the wallet.
    ListenableFuture<Transaction> future = broadcaster.broadcastTransaction(tx).future();
    Futures.addCallback(
        future,
        new FutureCallback<Transaction>() {
          @Override
          public void onSuccess(Transaction transaction) {
            log.info("TX {} propagated, channel successfully closed.", transaction.getHash());
            state = State.CLOSED;
            closedFuture.set(transaction);
          }

          @Override
          public void onFailure(Throwable throwable) {
            log.error("Failed to settle channel, could not broadcast", throwable);
            state = State.ERROR;
            closedFuture.setException(throwable);
          }
        });
    return closedFuture;
  }
  /**
   * Called when the client provides us with a new signature and wishes to increment total payment
   * by size. Verifies the provided signature and only updates values if everything checks out. If
   * the new refundSize is not the lowest we have seen, it is simply ignored.
   *
   * @param refundSize How many satoshis of the original contract are refunded to the client (the
   *     rest are ours)
   * @param signatureBytes The new signature spending the multi-sig contract to a new payment
   *     transaction
   * @throws VerificationException If the signature does not verify or size is out of range (incl
   *     being rejected by the network as dust).
   * @return true if there is more value left on the channel, false if it is now fully used up.
   */
  public synchronized boolean incrementPayment(Coin refundSize, byte[] signatureBytes)
      throws VerificationException, ValueOutOfRangeException, InsufficientMoneyException {
    checkState(state == State.READY);
    checkNotNull(refundSize);
    checkNotNull(signatureBytes);
    TransactionSignature signature = TransactionSignature.decodeFromBitcoin(signatureBytes, true);
    // We allow snapping to zero for the payment amount because it's treated specially later, but
    // not less than
    // the dust level because that would prevent the transaction from being relayed/mined.
    final boolean fullyUsedUp = refundSize.equals(Coin.ZERO);
    if (refundSize.compareTo(clientOutput.getMinNonDustValue()) < 0 && !fullyUsedUp)
      throw new ValueOutOfRangeException(
          "Attempt to refund negative value or value too small to be accepted by the network");
    Coin newValueToMe = totalValue.subtract(refundSize);
    if (newValueToMe.signum() < 0)
      throw new ValueOutOfRangeException("Attempt to refund more than the contract allows.");
    if (newValueToMe.compareTo(bestValueToMe) < 0)
      throw new ValueOutOfRangeException("Attempt to roll back payment on the channel.");

    // Get the wallet's copy of the multisigContract (ie with confidence information), if this is
    // null, the wallet
    // was not connected to the peergroup when the contract was broadcast (which may cause issues
    // down the road, and
    // disables our double-spend check next)
    Transaction walletContract = wallet.getTransaction(multisigContract.getHash());
    checkNotNull(
        walletContract,
        "Wallet did not contain multisig contract {} after state was marked READY",
        multisigContract.getHash());

    // Note that we check for DEAD state here, but this test is essentially useless in production
    // because we will
    // miss most double-spends due to bloom filtering right now anyway. This will eventually fixed
    // by network-wide
    // double-spend notifications, so we just wait instead of attempting to add all dependant
    // outpoints to our bloom
    // filters (and probably missing lots of edge-cases).
    if (walletContract.getConfidence().getConfidenceType()
        == TransactionConfidence.ConfidenceType.DEAD) {
      close();
      throw new VerificationException("Multisig contract was double-spent");
    }

    Transaction.SigHash mode;
    // If the client doesn't want anything back, they shouldn't sign any outputs at all.
    if (fullyUsedUp) mode = Transaction.SigHash.NONE;
    else mode = Transaction.SigHash.SINGLE;

    if (signature.sigHashMode() != mode || !signature.anyoneCanPay())
      throw new VerificationException(
          "New payment signature was not signed with the right SIGHASH flags.");

    Wallet.SendRequest req = makeUnsignedChannelContract(newValueToMe);
    // Now check the signature is correct.
    // Note that the client must sign with SIGHASH_{SINGLE/NONE} | SIGHASH_ANYONECANPAY to allow us
    // to add additional
    // inputs (in case we need to add significant fee, or something...) and any outputs we want to
    // pay to.
    Sha256Hash sighash = req.tx.hashForSignature(0, multisigScript, mode, true);

    if (!clientKey.verify(sighash, signature))
      throw new VerificationException("Signature does not verify on tx\n" + req.tx);
    bestValueToMe = newValueToMe;
    bestValueSignature = signatureBytes;
    updateChannelInWallet();
    return !fullyUsedUp;
  }
Ejemplo n.º 8
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<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;
  }
Ejemplo n.º 9
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);
  }