fetchMissingUTXOTransactions function
Future<UTXOTxInfo>
fetchMissingUTXOTransactions({ - required Set<UTXOTransaction> cachedTransactions,
- required List<NodeWithAddress> cachedNodes,
- required Set<ElectrumTransactionInfo> allTxs,
- required Iterable<NodeWithAddress> nodes,
- required CoinEntity coin,
- required UTXONetworkType networkType,
- required Iterable<AddressType> addressTypes,
- required List<(String, int)> endpoints,
- required Stopwatch watch,
})
Implementation
Future<UTXOTxInfo> fetchMissingUTXOTransactions({
required Set<UTXOTransaction> cachedTransactions,
required List<NodeWithAddress> cachedNodes,
required Set<ElectrumTransactionInfo> allTxs,
required Iterable<NodeWithAddress> nodes,
required CoinEntity coin,
required UTXONetworkType networkType,
required Iterable<AddressType> addressTypes,
required List<(String, int)> endpoints,
required Stopwatch watch,
}) async {
/// Filter out all transactions that are already cached
final newTxs = allTxs.where(
(tx) {
final isCached = cachedTransactions.any((cTx) => cTx.id == tx.hash);
return isCached == false;
},
);
final pendingTxs = allTxs.where(
(tx) {
final cTx = cachedTransactions.singleWhereOrNull(
(cTx) => cTx.id == tx.hash,
);
if (cTx == null) return false;
return cTx.isPending || cTx is NotAvaialableUTXOTransaction;
},
);
Logger.log("Found ${pendingTxs.length} pending TXs for ${coin.symbol}");
Logger.log("Found ${newTxs.length} new TXs for ${coin.symbol}");
///
/// Fetch UTXO Details for all new transactions
///
var newUtxoTxs = await computeMissingUTXODetails(
txList: newTxs,
nodes: nodes,
type: networkType,
endpoints: endpoints,
addressTypes: addressTypes,
);
///
/// Fetch Parent Transactions of P2SH Inputs to get the redeem script and in hence the address
///
final unsupported = newUtxoTxs.where(
(tx) => tx.sender == ADDRESS_NOT_SUPPORTED,
);
final updatedUTXOs = <UTXOTransaction>[];
for (final tx in unsupported) {
final firstInput = tx.inputs.firstOrNull;
if (firstInput == null) continue;
/// Coinbase Parent TX
if (firstInput.isCoinbase) {
continue;
}
final outputIndex = firstInput.vout!;
final parentTxHash = firstInput.txid;
if (parentTxHash == null) continue;
// Check if Parent TX is already in the list
var parentTx = newUtxoTxs
.singleWhereOrNull((element) => element.id == firstInput.txid);
final (_tx, _, _) = await fetchFromRandomElectrumXNode(
(client) {
return client.getTransaction(
addressTypes: addressTypes,
nodes: nodes,
type: networkType,
txHash: parentTxHash,
);
},
client: null,
endpoints: networkType.endpoints,
token: coin,
);
parentTx ??= _tx;
if (parentTx == null) {
Logger.logWarning(
"Parent TX $parentTxHash not found for ${tx.hash}",
);
continue;
}
final output = parentTx.outputs[outputIndex];
final address = output.getAddress(networkType);
updatedUTXOs.add(tx.copyWith(sender: address));
}
// Replace the unsupported transactions with the updated ones
newUtxoTxs = newUtxoTxs.map((tx) {
final updatedTx = updatedUTXOs.singleWhereOrNull(
(upTx) => upTx.id == tx.id,
);
return updatedTx ?? tx;
});
///
/// Fetch UTXO Details for all pending transactions and replace them
///
final pendingUtxoTxs = await computeMissingUTXODetails(
txList: pendingTxs,
nodes: nodes,
type: networkType,
endpoints: endpoints,
addressTypes: addressTypes,
);
var utxoTXs = {
...newUtxoTxs,
...cachedTransactions,
};
/// Replace the pending transactions with the updated ones
utxoTXs = utxoTXs.map((tx) {
final pendingTx = pendingUtxoTxs.singleWhereOrNull(
(element) => element.id == tx.id,
);
return pendingTx ?? tx;
}).toSet();
///
/// Mark Spent Outputs
///
for (final tx in utxoTXs) {
for (final input in tx.inputs) {
final outputs = utxoTXs
.singleWhereOrNull((element) => element.id == input.txid)
?.outputs;
if (input.isCoinbase) continue;
final index = input.vout!;
if (outputs == null || outputs.length <= index) {
continue;
}
outputs[index] = outputs[index].copyWith(spent: true);
}
}
watch.stop();
Logger.logFetch("Fetched TXs in ${watch.elapsed}");
final sortedTxs = utxoTXs.sortedBy<GenericTransaction>((tx) => tx).toSet();
return (sortedTxs, nodes);
}