@Test
  public void testSimplePayment() throws Exception {
    // Create a PaymentRequest and make sure the correct values are parsed by the PaymentSession.
    MockPaymentSession paymentSession = new MockPaymentSession(newSimplePaymentRequest());
    assertEquals(paymentRequestMemo, paymentSession.getMemo());
    assertEquals(nanoCoins, paymentSession.getValue());
    assertEquals(simplePaymentUrl, paymentSession.getPaymentUrl());
    assertTrue(new Date(time * 1000L).equals(paymentSession.getDate()));
    assertTrue(paymentSession.getSendRequest().tx.equals(tx));
    assertFalse(paymentSession.isExpired());

    // Send the payment and verify that the correct information is sent.
    // Add a dummy input to tx so it is considered valid.
    tx.addInput(new TransactionInput(params, tx, outputToMe.getScriptBytes()));
    ArrayList<Transaction> txns = new ArrayList<Transaction>();
    txns.add(tx);
    Address refundAddr = new Address(params, serverKey.getPubKeyHash());
    paymentSession.sendPayment(txns, refundAddr, paymentMemo);
    assertEquals(1, paymentSession.getPaymentLog().size());
    assertEquals(simplePaymentUrl, paymentSession.getPaymentLog().get(0).getUrl().toString());
    Protos.Payment payment = paymentSession.getPaymentLog().get(0).getPayment();
    assertEquals(paymentMemo, payment.getMemo());
    assertEquals(merchantData, payment.getMerchantData());
    assertEquals(1, payment.getRefundToCount());
    assertEquals(nanoCoins.longValue(), payment.getRefundTo(0).getAmount());
    TransactionOutput refundOutput = new TransactionOutput(params, null, nanoCoins, refundAddr);
    ByteString refundScript = ByteString.copyFrom(refundOutput.getScriptBytes());
    assertTrue(refundScript.equals(payment.getRefundTo(0).getScript()));
  }
 /**
  * Verify the transaction structure as follows
  *
  * <ul>
  *   <li>A transaction must have at least one input and one output
  *   <li>A transaction output may not specify a negative number of coins
  *   <li>The sum of all of the output amounts must not exceed 21,000,000 BTC
  *   <li>A non-coinbase transaction may not contain any unconnected inputs
  *   <li>A connected output may not be used by more than one input
  *   <li>The input script must contain only push-data operations
  * </ul>
  *
  * @param canonical TRUE to enforce canonical transactions
  * @throws VerificationException Script verification failed
  */
 public void verify(boolean canonical) throws VerificationException {
   try {
     // Must have at least one input and one output
     if (txInputs.isEmpty() || txOutputs.isEmpty())
       throw new VerificationException(
           "Transaction does not have at least 1 input and 1 output",
           RejectMessage.REJECT_INVALID,
           txHash);
     // No output value may be negative
     // Sum of all output values must not exceed MAX_MONEY
     BigInteger outTotal = BigInteger.ZERO;
     for (TransactionOutput txOut : txOutputs) {
       BigInteger outValue = txOut.getValue();
       if (outValue.signum() < 0)
         throw new VerificationException(
             "Transaction output value is negative", RejectMessage.REJECT_INVALID, txHash);
       outTotal = outTotal.add(outValue);
       if (outTotal.compareTo(NetParams.MAX_MONEY) > 0)
         throw new VerificationException(
             "Total transaction output amount exceeds maximum",
             RejectMessage.REJECT_INVALID,
             txHash);
       byte[] scriptBytes = txOut.getScriptBytes();
     }
     if (!coinBase) {
       // All inputs must have connected outputs
       // No outpoint may be used more than once
       // Input scripts must consist of only push-data operations
       List<OutPoint> outPoints = new ArrayList<>(txInputs.size());
       for (TransactionInput txIn : txInputs) {
         OutPoint outPoint = txIn.getOutPoint();
         if (outPoint.getHash().equals(Sha256Hash.ZERO_HASH) || outPoint.getIndex() < 0)
           throw new VerificationException(
               "Non-coinbase transaction contains unconnected inputs",
               RejectMessage.REJECT_INVALID,
               txHash);
         if (outPoints.contains(outPoint))
           throw new VerificationException(
               "Connected output used in multiple inputs", RejectMessage.REJECT_INVALID, txHash);
         outPoints.add(outPoint);
         if (canonical) {
           if (!Script.checkInputScript(txIn.getScriptBytes()))
             throw new VerificationException(
                 "Input script must contain only canonical push-data operations",
                 RejectMessage.REJECT_NONSTANDARD,
                 txHash);
         }
       }
     }
   } catch (EOFException exc) {
     throw new VerificationException(
         "End-of-data while processing script", RejectMessage.REJECT_MALFORMED, txHash);
   }
 }
Example #3
0
 /**
  * If this input is connected, check the output is connected back to this input and release it if
  * so, making it spendable once again.
  *
  * @return true if the disconnection took place, false if it was not connected.
  */
 public boolean disconnect() {
   if (outpoint.fromTx == null) return false;
   TransactionOutput output = outpoint.fromTx.getOutput((int) outpoint.getIndex());
   if (output.getSpentBy() == this) {
     output.markAsUnspent();
     outpoint.fromTx = null;
     return true;
   } else {
     return false;
   }
 }
Example #4
0
 /**
  * Verifies that this input can spend the given output. Note that this input must be a part of a
  * transaction. Also note that the consistency of the outpoint will be checked, even if this input
  * has not been connected.
  *
  * @param output the output that this input is supposed to spend.
  * @throws ScriptException If the script doesn't verify.
  * @throws VerificationException If the outpoint doesn't match the given output.
  */
 public void verify(TransactionOutput output) throws VerificationException {
   if (output.parent != null) {
     if (!getOutpoint().getHash().equals(output.getParentTransaction().getHash()))
       throw new VerificationException(
           "This input does not refer to the tx containing the output.");
     if (getOutpoint().getIndex() != output.getIndex())
       throw new VerificationException("This input refers to a different output on the given tx.");
   }
   Script pubKey = output.getScriptPubKey();
   int myIndex = getParentTransaction().getInputs().indexOf(this);
   getScriptSig().correctlySpends(getParentTransaction(), myIndex, pubKey);
 }
Example #5
0
 /** Creates an UNSIGNED input that links to the given output */
 TransactionInput(
     NetworkParameters params, Transaction parentTransaction, TransactionOutput output) {
   super(params);
   long outputIndex = output.getIndex();
   if (output.getParentTransaction() != null) {
     outpoint = new TransactionOutPoint(params, outputIndex, output.getParentTransaction());
   } else {
     outpoint = new TransactionOutPoint(params, output);
   }
   scriptBytes = EMPTY_ARRAY;
   sequence = NO_SEQUENCE;
   setParent(parentTransaction);
   this.value = output.getValue();
   length = 41;
 }
Example #6
0
 @Override
 void bitcoinSerializeToStream(OutputStream stream) throws IOException {
   Utils.uint32ToByteStreamLE(height, stream);
   stream.write(chainHead.getBytes());
   stream.write(new VarInt(hits.length).encode());
   stream.write(hits);
   stream.write(new VarInt(outputs.size()).encode());
   for (TransactionOutput output : outputs) {
     // TODO: Allow these to be specified, if one day we care about sending this message ourselves
     // (currently it's just used for unit testing).
     Utils.uint32ToByteStreamLE(0L, stream); // Version
     Utils.uint32ToByteStreamLE(0L, stream); // Height
     output.bitcoinSerializeToStream(stream);
   }
 }
 private boolean isConfirmed(TransactionOutput output) {
   return output
       .getParentTransaction()
       .getConfidence()
       .getConfidenceType()
       .equals(TransactionConfidence.ConfidenceType.BUILDING);
 }
 // Create a payment transaction with valueToMe going back to us
 private synchronized Wallet.SendRequest makeUnsignedChannelContract(Coin valueToMe) {
   Transaction tx = new Transaction(wallet.getParams());
   if (!totalValue.subtract(valueToMe).equals(Coin.ZERO)) {
     clientOutput.setValue(totalValue.subtract(valueToMe));
     tx.addOutput(clientOutput);
   }
   tx.addInput(multisigContract.getOutput(0));
   return Wallet.SendRequest.forTx(tx);
 }
Example #9
0
 /**
  * Connects this input to the relevant output of the referenced transaction. Connecting means
  * updating the internal pointers and spent flags. If the mode is to ABORT_ON_CONFLICT then the
  * spent output won't be changed, but the outpoint.fromTx pointer will still be updated.
  *
  * @param transaction The transaction to try.
  * @param mode Whether to abort if there's a pre-existing connection or not.
  * @return NO_SUCH_TX if transaction is not the prevtx, ALREADY_SPENT if there was a conflict,
  *     SUCCESS if not.
  */
 public ConnectionResult connect(Transaction transaction, ConnectMode mode) {
   if (!transaction.getHash().equals(outpoint.getHash())) return ConnectionResult.NO_SUCH_TX;
   checkElementIndex(
       (int) outpoint.getIndex(), transaction.getOutputs().size(), "Corrupt transaction");
   TransactionOutput out = transaction.getOutput((int) outpoint.getIndex());
   if (!out.isAvailableForSpending()) {
     if (getParentTransaction().equals(outpoint.fromTx)) {
       // Already connected.
       return ConnectionResult.SUCCESS;
     } else if (mode == ConnectMode.DISCONNECT_ON_CONFLICT) {
       out.markAsUnspent();
     } else if (mode == ConnectMode.ABORT_ON_CONFLICT) {
       outpoint.fromTx = out.getParentTransaction();
       return TransactionInput.ConnectionResult.ALREADY_SPENT;
     }
   }
   connect(out);
   return TransactionInput.ConnectionResult.SUCCESS;
 }
 @Override
 public CoinSelection select(Coin target, List<TransactionOutput> candidates) {
   try {
     LinkedList<TransactionOutput> gathered = Lists.newLinkedList();
     Coin valueGathered = Coin.ZERO;
     for (TransactionOutput output : candidates) {
       if (ignorePending && !isConfirmed(output)) continue;
       // Find the key that controls output, assuming it's a regular pay-to-pubkey or
       // pay-to-address output.
       // We ignore any other kind of exotic output on the assumption we can't spend it ourselves.
       final Script scriptPubKey = output.getScriptPubKey();
       ECKey controllingKey;
       if (scriptPubKey.isSentToRawPubKey()) {
         controllingKey = wallet.findKeyFromPubKey(scriptPubKey.getPubKey());
       } else if (scriptPubKey.isSentToAddress()) {
         controllingKey = wallet.findKeyFromPubHash(scriptPubKey.getPubKeyHash());
       } else {
         log.info("Skipping tx output {} because it's not of simple form.", output);
         continue;
       }
       checkNotNull(
           controllingKey, "Coin selector given output as candidate for which we lack the key");
       if (controllingKey.getCreationTimeSeconds() >= unixTimeSeconds) continue;
       // It's older than the cutoff time so select.
       valueGathered = valueGathered.add(output.getValue());
       gathered.push(output);
       if (gathered.size() >= MAX_SIMULTANEOUS_INPUTS) {
         log.warn(
             "Reached {} inputs, going further would yield a tx that is too large, stopping here.",
             gathered.size());
         break;
       }
     }
     return new CoinSelection(valueGathered, gathered);
   } catch (ScriptException e) {
     throw new RuntimeException(
         e); // We should never have problems understanding scripts in our wallet.
   }
 }
 @Test
 public void testDefaults() throws Exception {
   Protos.Output.Builder outputBuilder =
       Protos.Output.newBuilder().setScript(ByteString.copyFrom(outputToMe.getScriptBytes()));
   Protos.PaymentDetails paymentDetails =
       Protos.PaymentDetails.newBuilder().setTime(time).addOutputs(outputBuilder).build();
   Protos.PaymentRequest paymentRequest =
       Protos.PaymentRequest.newBuilder()
           .setSerializedPaymentDetails(paymentDetails.toByteString())
           .build();
   MockPaymentSession paymentSession = new MockPaymentSession(paymentRequest);
   assertEquals(BigInteger.ZERO, paymentSession.getValue());
   assertNull(paymentSession.getPaymentUrl());
   assertNull(paymentSession.getMemo());
 }
 @Test
 public void testExpiredPaymentRequest() throws Exception {
   MockPaymentSession paymentSession = new MockPaymentSession(newExpiredPaymentRequest());
   assertTrue(paymentSession.isExpired());
   // Send the payment and verify that an exception is thrown.
   // Add a dummy input to tx so it is considered valid.
   tx.addInput(new TransactionInput(params, tx, outputToMe.getScriptBytes()));
   ArrayList<Transaction> txns = new ArrayList<Transaction>();
   txns.add(tx);
   try {
     paymentSession.sendPayment(txns, null, null);
   } catch (PaymentRequestException.Expired e) {
     assertEquals(0, paymentSession.getPaymentLog().size());
     assertEquals(e.getMessage(), "PaymentRequest is expired");
     return;
   }
   fail("Expected exception due to expired PaymentRequest");
 }
 private Protos.PaymentRequest newSimplePaymentRequest() {
   Protos.Output.Builder outputBuilder =
       Protos.Output.newBuilder()
           .setAmount(nanoCoins.longValue())
           .setScript(ByteString.copyFrom(outputToMe.getScriptBytes()));
   Protos.PaymentDetails paymentDetails =
       Protos.PaymentDetails.newBuilder()
           .setNetwork("test")
           .setTime(time)
           .setPaymentUrl(simplePaymentUrl)
           .addOutputs(outputBuilder)
           .setMemo(paymentRequestMemo)
           .setMerchantData(merchantData)
           .build();
   Protos.PaymentRequest paymentRequest =
       Protos.PaymentRequest.newBuilder()
           .setPaymentDetailsVersion(1)
           .setPkiType("none")
           .setSerializedPaymentDetails(paymentDetails.toByteString())
           .build();
   return paymentRequest;
 }
  private static Protos.Transaction makeTxProto(WalletTransaction wtx) {
    Transaction tx = wtx.getTransaction();
    Protos.Transaction.Builder txBuilder = Protos.Transaction.newBuilder();

    txBuilder
        .setPool(getProtoPool(wtx))
        .setHash(hashToByteString(tx.getHash()))
        .setVersion((int) tx.getVersion())
        .setTime(tx.getTime());

    if (tx.getUpdateTime() != null) {
      txBuilder.setUpdatedAt(tx.getUpdateTime().getTime());
    }

    if (tx.getLockTime() > 0) {
      txBuilder.setLockTime((int) tx.getLockTime());
    }

    // Handle inputs.
    for (TransactionInput input : tx.getInputs()) {
      Protos.TransactionInput.Builder inputBuilder =
          Protos.TransactionInput.newBuilder()
              .setScriptBytes(ByteString.copyFrom(input.getScriptBytes()))
              .setTransactionOutPointHash(hashToByteString(input.getOutpoint().getHash()))
              .setTransactionOutPointIndex((int) input.getOutpoint().getIndex());
      if (input.hasSequence()) inputBuilder.setSequence((int) input.getSequenceNumber());
      if (input.getValue() != null) inputBuilder.setValue(input.getValue().value);
      txBuilder.addTransactionInput(inputBuilder);
    }

    // Handle outputs.
    for (TransactionOutput output : tx.getOutputs()) {
      Protos.TransactionOutput.Builder outputBuilder =
          Protos.TransactionOutput.newBuilder()
              .setScriptBytes(ByteString.copyFrom(output.getScriptBytes()))
              .setValue(output.getValue().value);
      final TransactionInput spentBy = output.getSpentBy();
      if (spentBy != null) {
        Sha256Hash spendingHash = spentBy.getParentTransaction().getHash();
        int spentByTransactionIndex = spentBy.getParentTransaction().getInputs().indexOf(spentBy);
        outputBuilder
            .setSpentByTransactionHash(hashToByteString(spendingHash))
            .setSpentByTransactionIndex(spentByTransactionIndex);
      }
      txBuilder.addTransactionOutput(outputBuilder);
    }

    // Handle which blocks tx was seen in.
    final Map<Sha256Hash, Integer> appearsInHashes = tx.getAppearsInHashes();
    if (appearsInHashes != null) {
      for (Map.Entry<Sha256Hash, Integer> entry : appearsInHashes.entrySet()) {
        txBuilder.addBlockHash(hashToByteString(entry.getKey()));
        txBuilder.addBlockRelativityOffsets(entry.getValue());
      }
    }

    if (tx.hasConfidence()) {
      TransactionConfidence confidence = tx.getConfidence();
      Protos.TransactionConfidence.Builder confidenceBuilder =
          Protos.TransactionConfidence.newBuilder();
      writeConfidence(txBuilder, confidence, confidenceBuilder);
    }

    Protos.Transaction.Purpose purpose;
    switch (tx.getPurpose()) {
      case UNKNOWN:
        purpose = Protos.Transaction.Purpose.UNKNOWN;
        break;
      case USER_PAYMENT:
        purpose = Protos.Transaction.Purpose.USER_PAYMENT;
        break;
      case KEY_ROTATION:
        purpose = Protos.Transaction.Purpose.KEY_ROTATION;
        break;
      case ASSURANCE_CONTRACT_CLAIM:
        purpose = Protos.Transaction.Purpose.ASSURANCE_CONTRACT_CLAIM;
        break;
      case ASSURANCE_CONTRACT_PLEDGE:
        purpose = Protos.Transaction.Purpose.ASSURANCE_CONTRACT_PLEDGE;
        break;
      case ASSURANCE_CONTRACT_STUB:
        purpose = Protos.Transaction.Purpose.ASSURANCE_CONTRACT_STUB;
        break;
      default:
        throw new RuntimeException("New tx purpose serialization not implemented.");
    }
    txBuilder.setPurpose(purpose);

    ExchangeRate exchangeRate = tx.getExchangeRate();
    if (exchangeRate != null) {
      Protos.ExchangeRate.Builder exchangeRateBuilder =
          Protos.ExchangeRate.newBuilder()
              .setCoinValue(exchangeRate.coin.value)
              .setFiatValue(exchangeRate.fiat.value)
              .setFiatCurrencyCode(exchangeRate.fiat.currencyCode);
      txBuilder.setExchangeRate(exchangeRateBuilder);
    }

    if (tx.getMemo() != null) txBuilder.setMemo(tx.getMemo());

    return txBuilder.build();
  }
Example #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<StoredTransactionOutput> txOutsSpent = new LinkedList<StoredTransactionOutput>();
        LinkedList<StoredTransactionOutput> txOutsCreated =
            new LinkedList<StoredTransactionOutput>();
        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!");
          }
        }

        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 (verifyFlags.contains(VerifyFlag.P2SH)) {
                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) {
            // 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);
          }
        }
        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;
  }
Example #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<StoredTransactionOutput> txOutsSpent = new LinkedList<StoredTransactionOutput>();
    LinkedList<StoredTransactionOutput> txOutsCreated = new LinkedList<StoredTransactionOutput>();
    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.matthewmitchell/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();
        }
      }
      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 (verifyFlags.contains(VerifyFlag.P2SH)) {
              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 && 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);
        }
      }
      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);
  }
Example #17
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.isLocked());
    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!");
          }
        }
        BigInteger totalFees = BigInteger.ZERO;
        BigInteger 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();
          BigInteger valueIn = BigInteger.ZERO;
          BigInteger valueOut = BigInteger.ZERO;
          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(
                        params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length);
                if (script.isPayToScriptHash())
                  sigOps += Script.getP2SHSigOpCount(in.getScriptBytes());
                if (sigOps > Block.MAX_BLOCK_SIGOPS)
                  throw new VerificationException("Too many P2SH SigOps in block");
              }

              // All of these copies are terribly ugly, however without them,
              // I see some odd concurrency issues where scripts throw exceptions
              // (mostly "Attempted OP_* on empty stack" or similar) when they shouldn't.
              // In my tests, total time spent in net.usecredits.credits.core when
              // downloading the chain is < 0.5%, so doing this is no big efficiency issue.
              // TODO: Find out the underlying issue and create a better work-around
              // TODO: Thoroughly test that this fixes the issue like the non-StoredBlock version
              // does
              final int currentIndex = index;
              final Script scriptSig = in.getScriptSig();
              final Script scriptPubKey =
                  new Script(params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length);
              FutureTask<VerificationException> future =
                  new FutureTask<VerificationException>(
                      new Callable<VerificationException>() {
                        public VerificationException call() {
                          try {
                            scriptSig.correctlySpends(
                                tx, currentIndex, scriptPubKey, enforcePayToScriptHash);
                          } catch (VerificationException e) {
                            return e;
                          }
                          return null;
                        }
                      });
              scriptVerificationExecutor.execute(future);
              listScriptVerificationResults.add(future);

              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.compareTo(BigInteger.ZERO) < 0 || valueOut.compareTo(params.MAX_MONEY) > 0)
            throw new VerificationException("Transaction output value out of rage");
          if (isCoinBase) {
            coinbaseValue = valueOut;
          } else {
            if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(params.MAX_MONEY) > 0)
              throw new VerificationException("Transaction input value out of range");
            totalFees = totalFees.add(valueIn.subtract(valueOut));
          }
        }
        if (totalFees.compareTo(params.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.");
          }
          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;
  }
Example #18
0
  @Override
  protected TransactionOutputChanges connectTransactions(int height, Block block)
      throws VerificationException, BlockStoreException {
    checkState(lock.isLocked());
    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 enforceBIP16 = 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://en.litecoin.it/wiki/BIP_0030
        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 (enforceBIP16) // We already check non-BIP16 sigops in Block.verifyTransactions(true)
          sigOps += tx.getSigOpCount();
        }
      }
      BigInteger totalFees = BigInteger.ZERO;
      BigInteger coinbaseValue = null;
      for (Transaction tx : block.transactions) {
        boolean isCoinBase = tx.isCoinBase();
        BigInteger valueIn = BigInteger.ZERO;
        BigInteger valueOut = BigInteger.ZERO;
        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 (enforceBIP16) {
              if (new Script(params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length)
                  .isPayToScriptHash()) sigOps += Script.getP2SHSigOpCount(in.getScriptBytes());
              if (sigOps > Block.MAX_BLOCK_SIGOPS)
                throw new VerificationException("Too many P2SH SigOps in block");
            }

            // All of these copies are terribly ugly, however without them,
            // I see some odd concurrency issues where scripts throw exceptions
            // (mostly "Attempted OP_* on empty stack" or similar) when they shouldn't.
            // In my tests, total time spent in net.usecredits.credits.core when
            // downloading the chain is < 0.5%, so doing this is no big efficiency issue.
            // TODO: Find out the underlying issue and create a better work-around
            final int currentIndex = index;
            final Transaction txCache;
            try {
              txCache = new Transaction(params, tx.unsafeLitecoinSerialize());
            } catch (ProtocolException e1) {
              throw new RuntimeException(e1);
            }
            final Script scriptSig = in.getScriptSig();
            final Script scriptPubKey =
                new Script(params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length);
            FutureTask<VerificationException> future =
                new FutureTask<VerificationException>(
                    new Callable<VerificationException>() {
                      public VerificationException call() {
                        try {
                          scriptSig.correctlySpends(
                              txCache, currentIndex, scriptPubKey, enforceBIP16);
                        } catch (VerificationException e) {
                          return e;
                        }
                        return null;
                      }
                    });
            scriptVerificationExecutor.execute(future);
            listScriptVerificationResults.add(future);

            // 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.compareTo(BigInteger.ZERO) < 0 || valueOut.compareTo(params.MAX_MONEY) > 0)
          throw new VerificationException("Transaction output value out of rage");
        if (isCoinBase) {
          coinbaseValue = valueOut;
        } else {
          if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(params.MAX_MONEY) > 0)
            throw new VerificationException("Transaction input value out of range");
          totalFees = totalFees.add(valueIn.subtract(valueOut));
        }
      }
      if (totalFees.compareTo(params.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.");
        }
        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);
  }
 /**
  * Serializes the transaction for use in a signature
  *
  * @param index Current transaction index
  * @param sigHashType Signature hash type
  * @param subScriptBytes Replacement script for the current input
  * @param outBuffer Output buffer
  * @throws ScriptException Transaction index out-of-range
  */
 public final void serializeForSignature(
     int index, int sigHashType, byte[] subScriptBytes, SerializedBuffer outBuffer)
     throws ScriptException {
   int hashType;
   boolean anyoneCanPay;
   //
   // The transaction input must be within range
   //
   if (index < 0 || index >= txInputs.size())
     throw new ScriptException("Transaction input index is not valid");
   //
   // Check for a valid hash type
   //
   // Note that SIGHASH_ANYONE_CAN_PAY is or'ed with one of the other hash types.  So we need
   // to remove it when checking for a valid signature.
   //
   // SIGHASH_ALL:    This is the default. It indicates that everything about the transaction is
   // signed
   //                 except for the input scripts. Signing the input scripts as well would
   // obviously make
   //                 it impossible to construct a transaction.
   // SIGHASH_NONE:   The outputs are not signed and can be anything. This mode allows others to
   // update
   //                 the transaction by changing their inputs sequence numbers.  This means that
   // all
   //                 input sequence numbers are set to 0 except for the current input.
   // SIGHASH_SINGLE: Outputs up to and including the current input index number are included.
   // Outputs
   //                 before the current index have a -1 value and an empty script.  All input
   // sequence
   //                 numbers are set to 0 except for the current input.
   //
   // The SIGHASH_ANYONE_CAN_PAY modifier can be combined with the above three modes. When set,
   // only that
   // input is signed and the other inputs can be anything.
   //
   // In all cases, the script for the current input is replaced with the script from the connected
   // output.  All other input scripts are set to an empty script.
   //
   // The reference client accepts an invalid hash types and treats it as SIGHASH_ALL.  So we need
   // to
   // do the same.
   //
   anyoneCanPay = ((sigHashType & ScriptOpCodes.SIGHASH_ANYONE_CAN_PAY) != 0);
   hashType = sigHashType & (255 - ScriptOpCodes.SIGHASH_ANYONE_CAN_PAY);
   if (hashType != ScriptOpCodes.SIGHASH_ALL
       && hashType != ScriptOpCodes.SIGHASH_NONE
       && hashType != ScriptOpCodes.SIGHASH_SINGLE) hashType = ScriptOpCodes.SIGHASH_ALL;
   //
   // Serialize the version
   //
   outBuffer.putInt(txVersion);
   //
   // Serialize the inputs
   //
   // For SIGHASH_ANYONE_CAN_PAY, only the current input is included in the signature.
   // Otherwise, all inputs are included.
   //
   List<TransactionInput> sigInputs;
   if (anyoneCanPay) {
     sigInputs = new ArrayList<>(1);
     sigInputs.add(txInputs.get(index));
   } else {
     sigInputs = txInputs;
   }
   outBuffer.putVarInt(sigInputs.size());
   byte[] emptyScriptBytes = new byte[0];
   for (TransactionInput txInput : sigInputs)
     txInput.serializeForSignature(
         index,
         hashType,
         (txInput.getIndex() == index ? subScriptBytes : emptyScriptBytes),
         outBuffer);
   //
   // Serialize the outputs
   //
   if (hashType == ScriptOpCodes.SIGHASH_NONE) {
     //
     // There are no outputs for SIGHASH_NONE
     //
     outBuffer.putVarInt(0);
   } else if (hashType == ScriptOpCodes.SIGHASH_SINGLE) {
     //
     // The output list is resized to the input index+1
     //
     if (txOutputs.size() <= index)
       throw new ScriptException("Input index out-of-range for SIGHASH_SINGLE");
     outBuffer.putVarInt(index + 1);
     for (TransactionOutput txOutput : txOutputs) {
       if (txOutput.getIndex() > index) break;
       txOutput.serializeForSignature(index, hashType, outBuffer);
     }
   } else {
     //
     // All outputs are serialized for SIGHASH_ALL
     //
     outBuffer.putVarInt(txOutputs.size());
     for (TransactionOutput txOutput : txOutputs)
       txOutput.serializeForSignature(index, hashType, outBuffer);
   }
   //
   // Serialize the lock time
   //
   outBuffer.putUnsignedInt(txLockTime);
 }
  /**
   * 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;
  }
Example #21
0
 /**
  * Internal use only: connects this TransactionInput to the given output (updates pointers and
  * spent flags)
  */
 public void connect(TransactionOutput out) {
   outpoint.fromTx = out.getParentTransaction();
   out.markAsSpent(this);
   value = out.getValue();
 }