Esempio n. 1
0
  private void markTransactionAsSpent(Transaction transaction) {
    _backing.beginTransaction();
    try {
      // Remove inputs from unspent, marking them as spent
      for (TransactionInput input : transaction.inputs) {
        TransactionOutputEx parentOutput = _backing.getUnspentOutput(input.outPoint);
        if (parentOutput != null) {
          _backing.deleteUnspentOutput(input.outPoint);
          _backing.putParentTransactionOutput(parentOutput);
        }
      }

      // See if any of the outputs are for ourselves and store them as
      // unspent
      for (int i = 0; i < transaction.outputs.length; i++) {
        TransactionOutput output = transaction.outputs[i];
        if (isMine(output.script)) {
          _backing.putUnspentOutput(
              new TransactionOutputEx(
                  new OutPoint(transaction.getHash(), i),
                  -1,
                  output.value,
                  output.script.getScriptBytes(),
                  false));
        }
      }

      // Store transaction locally, so we have it in our history and don't
      // need to fetch it in a minute
      _backing.putTransaction(TransactionEx.fromUnconfirmedTransaction(transaction));
      _backing.setTransactionSuccessful();
    } finally {
      _backing.endTransaction();
    }

    // Tell account that we have a new transaction
    onNewTransaction(TransactionEx.fromUnconfirmedTransaction(transaction), transaction);

    // Calculate local balance cache. It has changed because we have done
    // some spending
    updateLocalBalance();
    persistContextIfNecessary();
  }
Esempio n. 2
0
  private void fetchStoreAndValidateParentOutputs(ArrayList<Transaction> transactions)
      throws WapiException {
    Map<Sha256Hash, TransactionEx> parentTransactions = new HashMap<Sha256Hash, TransactionEx>();
    Map<OutPoint, TransactionOutputEx> parentOutputs = new HashMap<OutPoint, TransactionOutputEx>();

    // Find list of parent outputs to fetch
    Collection<Sha256Hash> toFetch = new HashSet<Sha256Hash>();
    for (Transaction t : transactions) {
      for (TransactionInput in : t.inputs) {
        if (in.outPoint.hash.equals(OutPoint.COINBASE_OUTPOINT.hash)) {
          // Coinbase input, so no parent
          continue;
        }
        TransactionOutputEx parentOutput = _backing.getParentTransactionOutput(in.outPoint);
        if (parentOutput != null) {
          // We already have the parent output, no need to fetch the entire
          // parent transaction
          parentOutputs.put(parentOutput.outPoint, parentOutput);
          continue;
        }
        TransactionEx parentTransaction = _backing.getTransaction(in.outPoint.hash);
        if (parentTransaction != null) {
          // We had the parent transaction in our own transactions, no need to
          // fetch it remotely
          parentTransactions.put(parentTransaction.txid, parentTransaction);
        } else {
          // Need to fetch it
          toFetch.add(in.outPoint.hash);
        }
      }
    }

    // Fetch missing parent transactions
    if (toFetch.size() > 0) {
      GetTransactionsResponse result =
          _wapi.getTransactions(new GetTransactionsRequest(Wapi.VERSION, toFetch)).getResult();
      for (TransactionEx tx : result.transactions) {
        // Verify transaction hash. This is important as we don't want to
        // have a transaction output associated with an outpoint that
        // doesn't match.
        // This is the end users protection against a rogue server that lies
        // about the value of an output and makes you pay a large fee.
        Sha256Hash hash = HashUtils.doubleSha256(tx.binary).reverse();
        if (hash.equals(tx.txid)) {
          parentTransactions.put(tx.txid, tx);
        } else {
          _logger.logError(
              "Failed to validate transaction hash from server. Expected: "
                  + tx.txid
                  + " Calculated: "
                  + hash);
          throw new RuntimeException(
              "Failed to validate transaction hash from server. Expected: "
                  + tx.txid
                  + " Calculated: "
                  + hash);
        }
      }
    }

    // We should now have all parent transactions or parent outputs. There is
    // a slight probability that one of them was not found due to double
    // spends and/or malleability and network latency etc.

    // Now figure out which parent outputs we need to persist
    List<TransactionOutputEx> toPersist = new LinkedList<TransactionOutputEx>();
    for (Transaction t : transactions) {
      for (TransactionInput in : t.inputs) {
        if (in.outPoint.hash.equals(OutPoint.COINBASE_OUTPOINT.hash)) {
          // coinbase input, so no parent
          continue;
        }
        TransactionOutputEx parentOutput = parentOutputs.get(in.outPoint);
        if (parentOutput != null) {
          // We had it all along
          continue;
        }
        TransactionEx parentTex = parentTransactions.get(in.outPoint.hash);
        if (parentTex != null) {
          // Parent output not found, maybe we already have it
          parentOutput = TransactionEx.getTransactionOutput(parentTex, in.outPoint.index);
          toPersist.add(parentOutput);
          continue;
        }
        _logger.logError("Parent transaction not found: " + in.outPoint.hash);
      }
    }

    // Persist
    for (TransactionOutputEx output : toPersist) {
      _backing.putParentTransactionOutput(output);
    }
  }