Пример #1
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);
    }
  }
Пример #2
0
  protected boolean synchronizeUnspentOutputs(Collection<Address> addresses) {
    // Get the current unspent outputs as dictated by the block chain
    QueryUnspentOutputsResponse UnspentOutputResponse;
    try {
      UnspentOutputResponse =
          _wapi
              .queryUnspentOutputs(new QueryUnspentOutputsRequest(Wapi.VERSION, addresses))
              .getResult();
    } catch (WapiException e) {
      _logger.logError("Server connection failed with error code: " + e.errorCode, e);
      postEvent(Event.SERVER_CONNECTION_ERROR);
      return false;
    }
    Collection<TransactionOutputEx> remoteUnspent = UnspentOutputResponse.unspent;
    // Store the current block height
    setBlockChainHeight(UnspentOutputResponse.height);
    // Make a map for fast lookup
    Map<OutPoint, TransactionOutputEx> remoteMap = toMap(remoteUnspent);

    // Get the current unspent outputs as it is believed to be locally
    Collection<TransactionOutputEx> localUnspent = _backing.getAllUnspentOutputs();
    // Make a map for fast lookup
    Map<OutPoint, TransactionOutputEx> localMap = toMap(localUnspent);

    // Find remotely removed unspent outputs
    for (TransactionOutputEx l : localUnspent) {
      TransactionOutputEx r = remoteMap.get(l.outPoint);
      if (r == null) {
        // An output has gone. Maybe it was spent in another wallet, or
        // never confirmed due to missing fees, double spend, or mutated.
        // Either way, we delete it locally
        _backing.deleteUnspentOutput(l.outPoint);
      }
    }

    // Find remotely added unspent outputs
    Set<Sha256Hash> transactionsToAddOrUpdate = new HashSet<Sha256Hash>();
    List<TransactionOutputEx> unspentOutputsToAddOrUpdate = new LinkedList<TransactionOutputEx>();
    for (TransactionOutputEx r : remoteUnspent) {
      TransactionOutputEx l = localMap.get(r.outPoint);
      if (l == null || l.height != r.height) {
        // New remote output or new height (Maybe it confirmed or we
        // might even have had a reorg). Either way we just update it
        unspentOutputsToAddOrUpdate.add(r);
        transactionsToAddOrUpdate.add(r.outPoint.hash);
        // Note: We are not adding the unspent output to the DB just yet. We
        // first want to verify the full set of funding transactions of the
        // transaction that this unspent output belongs to
      }
    }

    // Fetch updated or added transactions
    if (transactionsToAddOrUpdate.size() > 0) {
      GetTransactionsResponse response;
      try {
        response =
            _wapi
                .getTransactions(
                    new GetTransactionsRequest(Wapi.VERSION, transactionsToAddOrUpdate))
                .getResult();
      } catch (WapiException e) {
        _logger.logError("Server connection failed with error code: " + e.errorCode, e);
        postEvent(Event.SERVER_CONNECTION_ERROR);
        return false;
      }
      try {
        handleNewExternalTransactions(response.transactions);
      } catch (WapiException e) {
        _logger.logError("Server connection failed with error code: " + e.errorCode, e);
        postEvent(Event.SERVER_CONNECTION_ERROR);
        return false;
      }
      // Finally update out list of unspent outputs with added or updated
      // outputs
      for (TransactionOutputEx output : unspentOutputsToAddOrUpdate) {
        _backing.putUnspentOutput(output);
      }
    }

    return true;
  }