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; }
@Override public synchronized BroadcastResult broadcastTransaction(Transaction transaction) { checkNotArchived(); try { WapiResponse<BroadcastTransactionResponse> response = _wapi.broadcastTransaction( new BroadcastTransactionRequest(Wapi.VERSION, transaction.toBytes())); if (response.getErrorCode() == Wapi.ERROR_CODE_SUCCESS) { if (response.getResult().success) { markTransactionAsSpent(transaction); postEvent(Event.BROADCASTED_TRANSACTION_ACCEPTED); return BroadcastResult.SUCCESS; } else { // This transaction was rejected must be double spend or // malleability, delete it locally. _logger.logError( "Failed to broadcast transaction due to a double spend or malleability issue"); postEvent(Event.BROADCASTED_TRANSACTION_DENIED); return BroadcastResult.REJECTED; } } else if (response.getErrorCode() == Wapi.ERROR_CODE_NO_SERVER_CONNECTION) { postEvent(Event.SERVER_CONNECTION_ERROR); _logger.logError("Server connection failed with ERROR_CODE_NO_SERVER_CONNECTION"); return BroadcastResult.NO_SERVER_CONNECTION; } else { postEvent(Event.BROADCASTED_TRANSACTION_DENIED); _logger.logError("Server connection failed with error: " + response.getErrorCode()); return BroadcastResult.REJECTED; } } catch (WapiException e) { postEvent(Event.SERVER_CONNECTION_ERROR); _logger.logError("Server connection failed with error code: " + e.errorCode, e); return BroadcastResult.NO_SERVER_CONNECTION; } }
protected AbstractAccount(AccountBacking backing, NetworkParameters network, Wapi wapi) { _network = network; _logger = wapi.getLogger(); _wapi = wapi; _backing = backing; }
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); } }
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; }