예제 #1
0
  /**
   * Broadcast outgoing transactions.
   *
   * <p>This method should only be called from the wallet manager
   *
   * @return false if synchronization failed due to failed blockchain connection
   */
  public synchronized boolean broadcastOutgoingTransactions() {
    checkNotArchived();
    List<Sha256Hash> broadcastedIds = new LinkedList<Sha256Hash>();
    Map<Sha256Hash, byte[]> transactions = _backing.getOutgoingTransactions();

    for (byte[] rawTransaction : transactions.values()) {
      TransactionEx tex = TransactionEx.fromUnconfirmedTransaction(rawTransaction);

      BroadcastResult result = broadcastTransaction(TransactionEx.toTransaction(tex));
      if (result == BroadcastResult.SUCCESS) {
        broadcastedIds.add(tex.txid);
        _backing.removeOutgoingTransaction(tex.txid);
      } else {
        if (result == BroadcastResult.REJECTED) {
          // invalid tx
          _backing.deleteTransaction(tex.txid);
          _backing.removeOutgoingTransaction(tex.txid);
        } else {
          // No connection --> retry next sync
        }
      }
    }
    if (!broadcastedIds.isEmpty()) {
      onTransactionsBroadcasted(broadcastedIds);
    }
    return true;
  }
예제 #2
0
 protected static Collection<UnspentTransactionOutput> transform(
     Collection<TransactionOutputEx> source) {
   List<UnspentTransactionOutput> outputs = new ArrayList<UnspentTransactionOutput>();
   for (TransactionOutputEx s : source) {
     ScriptOutput script = ScriptOutput.fromScriptBytes(s.script);
     outputs.add(new UnspentTransactionOutput(s.outPoint, s.height, s.value, script));
   }
   return outputs;
 }
예제 #3
0
  @Override
  public TransactionDetails getTransactionDetails(Sha256Hash txid) {
    // Note that this method is not synchronized, and we might fetch the transaction history while
    // synchronizing
    // accounts. That should be ok as we write to the DB in a sane order.

    TransactionEx tex = _backing.getTransaction(txid);
    Transaction tx = TransactionEx.toTransaction(tex);
    if (tx == null) {
      throw new RuntimeException();
    }

    List<TransactionDetails.Item> inputs = new ArrayList<TransactionDetails.Item>(tx.inputs.length);
    if (tx.isCoinbase()) {
      // We have a coinbase transaction. Create one input with the sum of the outputs as its value,
      // and make the address the null address
      long value = 0;
      for (TransactionOutput out : tx.outputs) {
        value += out.value;
      }
      inputs.add(new TransactionDetails.Item(Address.getNullAddress(_network), value, true));
    } else {
      // Populate the inputs
      for (TransactionInput input : tx.inputs) {
        Sha256Hash parentHash = input.outPoint.hash;
        // Get the parent transaction
        TransactionOutputEx parentOutput = _backing.getParentTransactionOutput(input.outPoint);
        if (parentOutput == null) {
          // We never heard about the parent, skip
          continue;
        }
        // Determine the parent address
        Address parentAddress;
        ScriptOutput parentScript = ScriptOutput.fromScriptBytes(parentOutput.script);
        if (parentScript == null) {
          // Null address means we couldn't figure out the address, strange script
          parentAddress = Address.getNullAddress(_network);
        } else {
          parentAddress = parentScript.getAddress(_network);
        }
        inputs.add(new TransactionDetails.Item(parentAddress, parentOutput.value, false));
      }
    }
    // Populate the outputs
    TransactionDetails.Item[] outputs = new TransactionDetails.Item[tx.outputs.length];
    for (int i = 0; i < tx.outputs.length; i++) {
      Address address = tx.outputs[i].script.getAddress(_network);
      outputs[i] = new TransactionDetails.Item(address, tx.outputs[i].value, false);
    }

    return new TransactionDetails(
        txid, tex.height, tex.time, inputs.toArray(new TransactionDetails.Item[] {}), outputs);
  }
예제 #4
0
  @Override
  public List<TransactionSummary> getTransactionHistory(int offset, int limit) {
    // Note that this method is not synchronized, and we might fetch the transaction history while
    // synchronizing
    // accounts. That should be ok as we write to the DB in a sane order.

    List<TransactionSummary> history = new ArrayList<TransactionSummary>();
    checkNotArchived();
    int blockChainHeight = getBlockChainHeight();
    List<TransactionEx> list = _backing.getTransactionHistory(offset, limit);
    for (TransactionEx tex : list) {
      TransactionSummary item = transform(tex, blockChainHeight);
      if (item != null) {
        history.add(item);
      }
    }
    return history;
  }
예제 #5
0
  protected boolean monitorYoungTransactions() {
    Collection<TransactionEx> list = _backing.getYoungTransactions(5, getBlockChainHeight());
    if (list.isEmpty()) {
      return true;
    }
    List<Sha256Hash> txids = new ArrayList<Sha256Hash>(list.size());
    for (TransactionEx tex : list) {
      txids.add(tex.txid);
    }
    CheckTransactionsResponse result;
    try {
      result = _wapi.checkTransactions(new CheckTransactionsRequest(txids)).getResult();
    } catch (WapiException e) {
      postEvent(Event.SERVER_CONNECTION_ERROR);
      _logger.logError("Server connection failed with error code: " + e.errorCode, e);
      // We failed to check transactions
      return false;
    }
    for (TransactionStatus t : result.transactions) {
      if (!t.found) {
        // We have a transaction locally that does not exist in the
        // blockchain. Must be a residue due to double-spend or malleability

        _backing.deleteTransaction(t.txid);
        continue;
      }
      TransactionEx tex = _backing.getTransaction(t.txid);
      Preconditions.checkNotNull(tex);
      if (tex.height != t.height || tex.time != t.time) {
        // The transaction got a new height or timestamp. There could be
        // several reasons for that. It got a new timestamp from the server,
        // it confirmed, or might also be a reorg.
        TransactionEx newTex = new TransactionEx(tex.txid, t.height, t.time, tex.binary);
        System.out.println("Replacing:\n" + tex.toString() + "\nWith:\n" + newTex.toString());
        postEvent(Event.TRANSACTION_HISTORY_CHANGED);
        _backing.deleteTransaction(tex.txid);
        _backing.putTransaction(newTex);
      }
    }
    return true;
  }
예제 #6
0
  @Override
  public List<TransactionOutputSummary> getUnspentTransactionOutputSummary() {
    // Note that this method is not synchronized, and we might fetch the transaction history while
    // synchronizing
    // accounts. That should be ok as we write to the DB in a sane order.

    // Get all unspent outputs for this account
    Collection<TransactionOutputEx> outputs = _backing.getAllUnspentOutputs();

    // Transform it to a list of summaries
    List<TransactionOutputSummary> list = new ArrayList<TransactionOutputSummary>();
    int blockChainHeight = getBlockChainHeight();
    for (TransactionOutputEx output : outputs) {

      ScriptOutput script = ScriptOutput.fromScriptBytes(output.script);
      Address address;
      if (script == null) {
        address = Address.getNullAddress(_network);
        // This never happens as we have parsed this script before
      } else {
        address = script.getAddress(_network);
      }
      int confirmations;
      if (output.height == -1) {
        confirmations = 0;
      } else {
        confirmations = Math.max(0, blockChainHeight - output.height + 1);
      }

      TransactionOutputSummary summary =
          new TransactionOutputSummary(
              output.outPoint, output.value, output.height, confirmations, address);
      list.add(summary);
    }
    // Sort & return
    Collections.sort(list);
    return list;
  }
예제 #7
0
  protected Balance calculateLocalBalance() {

    Collection<TransactionOutputEx> unspentOutputs =
        new HashSet<TransactionOutputEx>(_backing.getAllUnspentOutputs());
    long confirmed = 0;
    long pendingChange = 0;
    long pendingSending = 0;
    long pendingReceiving = 0;

    //
    // Determine the value we are receiving and create a set of outpoints for fast lookup
    //
    Set<OutPoint> unspentOutPoints = new HashSet<OutPoint>();
    for (TransactionOutputEx output : unspentOutputs) {
      if (output.height == -1) {
        if (isFromMe(output.outPoint.hash)) {
          pendingChange += output.value;
        } else {
          pendingReceiving += output.value;
        }
      } else {
        confirmed += output.value;
      }
      unspentOutPoints.add(output.outPoint);
    }

    //
    // Determine the value we are sending
    //

    // Get the current set of unconfirmed transactions
    List<Transaction> unconfirmed = new ArrayList<Transaction>();
    for (TransactionEx tex : _backing.getUnconfirmedTransactions()) {
      try {
        Transaction t = Transaction.fromByteReader(new ByteReader(tex.binary));
        unconfirmed.add(t);
      } catch (TransactionParsingException e) {
        // never happens, we have parsed it before
      }
    }

    for (Transaction t : unconfirmed) {
      // For each input figure out if WE are sending it by fetching the
      // parent transaction and looking at the address
      boolean weSend = false;
      for (TransactionInput input : t.inputs) {
        // Find the parent transaction
        if (input.outPoint.hash.equals(Sha256Hash.ZERO_HASH)) {
          continue;
        }
        TransactionOutputEx parent = _backing.getParentTransactionOutput(input.outPoint);
        if (parent == null) {
          _logger.logError("Unable to find parent transaction output: " + input.outPoint);
          continue;
        }
        TransactionOutput parentOutput = transform(parent);
        Address fundingAddress = parentOutput.script.getAddress(_network);
        if (isMine(fundingAddress)) {
          // One of our addresses are sending coins
          pendingSending += parentOutput.value;
          weSend = true;
        }
      }

      // Now look at the outputs and if it contains change for us, then subtract that from the
      // sending amount
      // if it is already spent in another transaction
      for (int i = 0; i < t.outputs.length; i++) {
        TransactionOutput output = t.outputs[i];
        Address destination = output.script.getAddress(_network);
        if (weSend && isMine(destination)) {
          // The funds are sent from us to us
          OutPoint outPoint = new OutPoint(t.getHash(), i);
          if (!unspentOutPoints.contains(outPoint)) {
            // This output has been spent, subtract it from the amount sent
            pendingSending -= output.value;
          }
        }
      }
    }

    int blockHeight = getBlockChainHeight();
    return new Balance(
        confirmed,
        pendingReceiving,
        pendingSending,
        pendingChange,
        System.currentTimeMillis(),
        blockHeight,
        true,
        _allowZeroConfSpending);
  }
예제 #8
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);
    }
  }
예제 #9
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;
  }