protected Collection<TransactionOutputEx> getSpendableOutputs() { Collection<TransactionOutputEx> list = _backing.getAllUnspentOutputs(); // Prune confirmed outputs for coinbase outputs that are not old enough // for spending. Also prune unconfirmed receiving coins except for change int blockChainHeight = getBlockChainHeight(); Iterator<TransactionOutputEx> it = list.iterator(); while (it.hasNext()) { TransactionOutputEx output = it.next(); if (output.isCoinBase) { int confirmations = blockChainHeight - output.height; if (confirmations < COINBASE_MIN_CONFIRMATIONS) { it.remove(); continue; } } // Unless we allow zero confirmation spending we prune all unconfirmed outputs sent from // foreign addresses if (!_allowZeroConfSpending) { if (output.height == -1 && !isFromMe(output.outPoint.hash)) { // Prune receiving coins that is not change sent to ourselves it.remove(); } } } return list; }
@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; }
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); }
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; }