List <TransactionInformationMatch> ToMatch(AnnotatedTransactionCollection txs, List <TxOut> outputs, DerivationStrategyBase derivation, Repository.TransactionMiniKeyInformation[] keyInformations) { var result = new List <TransactionInformationMatch>(); for (int i = 0; i < outputs.Count; i++) { if (outputs[i] == null) { continue; } var keyPath = txs.GetKeyPath(outputs[i].ScriptPubKey); if (keyPath == null) { continue; } result.Add(new TransactionInformationMatch() { Index = i, KeyPath = keyPath, Value = outputs[i].Value }); } return(result); }
private async Task UpdateUTXO(UpdatePSBTRequest update, Repository repo, BitcoinDWaiter rpc) { AnnotatedTransactionCollection txs = null; // First, we check for data in our history foreach (var input in update.PSBT.Inputs.Where(NeedUTXO)) { txs = txs ?? await GetAnnotatedTransactions(repo, ChainProvider.GetChain(repo.Network), new DerivationSchemeTrackedSource(update.DerivationScheme)); if (txs.GetByTxId(input.PrevOut.Hash) is AnnotatedTransaction tx) { if (!tx.Record.Key.IsPruned) { input.NonWitnessUtxo = tx.Record.Transaction; } else { input.WitnessUtxo = tx.Record.ReceivedCoins.FirstOrDefault(c => c.Outpoint.N == input.Index)?.TxOut; } } } // then, we search data in the saved transactions await Task.WhenAll(update.PSBT.Inputs .Where(NeedUTXO) .Select(async(input) => { // If this is not segwit, or we are unsure of it, let's try to grab from our saved transactions if (input.NonWitnessUtxo == null) { var prev = await repo.GetSavedTransactions(input.PrevOut.Hash); if (prev.FirstOrDefault() is Repository.SavedTransaction saved) { input.NonWitnessUtxo = saved.Transaction; } } }).ToArray()); // finally, we check with rpc's txindex if (rpc?.RPCAvailable is true && rpc?.HasTxIndex is true) { var batch = rpc.RPC.PrepareBatch(); var getTransactions = Task.WhenAll(update.PSBT.Inputs .Where(NeedUTXO) .Where(input => input.NonWitnessUtxo == null) .Select(async input => { var tx = await batch.GetRawTransactionAsync(input.PrevOut.Hash, false); if (tx != null) { input.NonWitnessUtxo = tx; } }).ToArray()); await batch.SendBatchAsync(); await getTransactions; } }
private AnnotatedTransactionCollection GetAnnotatedTransactions(Repository repo, ConcurrentChain chain, DerivationStrategyBase extPubKey) { var annotatedTransactions = new AnnotatedTransactionCollection(repo .GetTransactions(extPubKey) .Select(t => new AnnotatedTransaction(t, chain)) .ToList()); CleanConflicts(repo, extPubKey, annotatedTransactions); return(annotatedTransactions); }
private void FillUTXOsInformation(List <UTXO> utxos, AnnotatedTransactionCollection transactions, int currentHeight) { for (int i = 0; i < utxos.Count; i++) { var utxo = utxos[i]; utxo.KeyPath = transactions.GetKeyPath(utxo.ScriptPubKey); utxo.Feature = DerivationStrategyBase.GetFeature(utxo.KeyPath); var txHeight = transactions.GetByTxId(utxo.Outpoint.Hash) .Select(t => t.Height) .Where(h => h.HasValue) .Select(t => t.Value) .Concat(MaxValue) .Min(); var firstSeen = transactions .GetByTxId(utxo.Outpoint.Hash) .Select(o => o.Record.FirstSeen) .FirstOrDefault(); var isUnconf = txHeight == MaxValue[0]; utxo.Confirmations = isUnconf ? 0 : currentHeight - txHeight + 1; utxo.Timestamp = firstSeen; } }
List <TransactionInformationMatch> ToMatch(AnnotatedTransactionCollection txs, List <TxOut> outputs, TrackedSource derivation) { var result = new List <TransactionInformationMatch>(); for (int i = 0; i < outputs.Count; i++) { if (outputs[i] == null) { continue; } if (!IsMatching(derivation, outputs[i].ScriptPubKey, txs)) { continue; } var keyPath = txs.GetKeyPath(outputs[i].ScriptPubKey); result.Add(new TransactionInformationMatch() { Index = i, KeyPath = keyPath, Value = outputs[i].Value }); } return(result); }
private void CleanConflicts(Repository repo, DerivationStrategyBase extPubKey, AnnotatedTransactionCollection transactions) { var cleaned = transactions.DuplicatedTransactions.Where(c => (DateTimeOffset.UtcNow - c.Record.Inserted) > TimeSpan.FromDays(1.0)).Select(c => c.Record).ToArray(); if (cleaned.Length != 0) { foreach (var tx in cleaned) { _EventAggregator.Publish(new EvictedTransactionEvent(tx.Transaction.GetHash())); } repo.CleanTransactions(extPubKey, cleaned.ToList()); } }
private void FillUTXOsInformation(List <UTXO> utxos, Func <Script[], KeyPath[]> getKeyPaths, AnnotatedTransactionCollection transactionsById, int currentHeight) { var keyPaths = getKeyPaths(utxos.Select(u => u.Output.ScriptPubKey).ToArray()); for (int i = 0; i < utxos.Count; i++) { var utxo = utxos[i]; utxo.KeyPath = keyPaths[i]; var txHeight = transactionsById.GetByTxId(utxo.Outpoint.Hash).Select(r => r.Height).Min(); txHeight = txHeight == MempoolHeight ? currentHeight + 1 : txHeight; utxo.Confirmations = currentHeight - txHeight + 1; } }