protected static Map<OutPoint, TransactionOutputEx> toMap(Collection<TransactionOutputEx> list) { Map<OutPoint, TransactionOutputEx> map = new HashMap<OutPoint, TransactionOutputEx>(); for (TransactionOutputEx t : list) { map.put(t.outPoint, t); } return map; }
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); } }