public AnnotatedTransactionCollection(ICollection <TrackedTransaction> transactions, Models.TrackedSource trackedSource, SlimChain headerChain, Network network) : base(transactions.Count) { _TxById = new Dictionary <uint256, AnnotatedTransaction>(transactions.Count); foreach (var tx in transactions) { foreach (var keyPathInfo in tx.KnownKeyPathMapping) { _KeyPaths.TryAdd(keyPathInfo.Key, keyPathInfo.Value); } } // Let's remove the dups and let's get the current height of the transactions foreach (var trackedTx in transactions) { int? txHeight = null; bool isMature = true; if (trackedTx.BlockHash != null && headerChain.TryGetHeight(trackedTx.BlockHash, out var height)) { txHeight = height; isMature = trackedTx.IsCoinBase ? headerChain.Height - height >= network.Consensus.CoinbaseMaturity : true; } AnnotatedTransaction annotatedTransaction = new AnnotatedTransaction(txHeight, trackedTx, isMature); if (_TxById.TryGetValue(trackedTx.TransactionHash, out var conflicted)) { if (ShouldReplace(annotatedTransaction, conflicted)) { CleanupTransactions.Add(conflicted); _TxById.Remove(trackedTx.TransactionHash); _TxById.Add(trackedTx.TransactionHash, annotatedTransaction); } else { CleanupTransactions.Add(annotatedTransaction); } } else { _TxById.Add(trackedTx.TransactionHash, annotatedTransaction); } } // Let's resolve the double spents Dictionary <OutPoint, uint256> spentBy = new Dictionary <OutPoint, uint256>(transactions.SelectMany(t => t.SpentOutpoints).Count()); foreach (var annotatedTransaction in _TxById.Values.Where(r => r.Height is int)) { foreach (var spent in annotatedTransaction.Record.SpentOutpoints) { // No way to have double spent in confirmed transactions spentBy.Add(spent, annotatedTransaction.Record.TransactionHash); } } var unconfs = _TxById.Where(r => r.Value.Height is null) .ToDictionary(kv => kv.Key, kv => kv.Value); var replaced = new Dictionary <uint256, AnnotatedTransaction>(); removeConflicts: HashSet <uint256> toRemove = new HashSet <uint256>(); foreach (var annotatedTransaction in unconfs.Values) { foreach (var spent in annotatedTransaction.Record.SpentOutpoints) { // All children of a replaced transaction should be replaced if (replaced.TryGetValue(spent.Hash, out var parent) && parent.ReplacedBy is uint256) { annotatedTransaction.Replaceable = false; annotatedTransaction.ReplacedBy = parent.ReplacedBy; replaced.TryAdd(annotatedTransaction.Record.TransactionHash, annotatedTransaction); toRemove.Add(annotatedTransaction.Record.TransactionHash); goto nextTransaction; } // If there is a conflict, let's see who get replaced if (spentBy.TryGetValue(spent, out var conflictHash) && _TxById.TryGetValue(conflictHash, out var conflicted)) { // Conflict with one-self... not a conflict. if (conflicted == annotatedTransaction) { goto nextTransaction; } // We know the conflict is already removed, so this transaction replace it if (toRemove.Contains(conflictHash)) { spentBy.Remove(spent); spentBy.Add(spent, annotatedTransaction.Record.TransactionHash); } else { AnnotatedTransaction toKeep = null, toReplace = null; var shouldReplace = ShouldReplace(annotatedTransaction, conflicted); if (shouldReplace) { toReplace = conflicted; toKeep = annotatedTransaction; } else { toReplace = annotatedTransaction; toKeep = conflicted; } toRemove.Add(toReplace.Record.TransactionHash); if (toKeep == annotatedTransaction) { spentBy.Remove(spent); spentBy.Add(spent, annotatedTransaction.Record.TransactionHash); } if (toKeep.Height is null && toReplace.Height is null) { toReplace.ReplacedBy = toKeep.Record.TransactionHash; toReplace.Replaceable = false; toKeep.Replacing = toReplace.Record.TransactionHash; replaced.TryAdd(toReplace.Record.TransactionHash, toReplace); } else { CleanupTransactions.Add(toReplace); } } }
public AnnotatedTransactionCollection(ICollection <TrackedTransaction> transactions, Models.TrackedSource trackedSource, SlimChain headerChain, Network network) : base(transactions.Count) { _TxById = new Dictionary <uint256, AnnotatedTransaction>(transactions.Count); foreach (var tx in transactions) { foreach (var keyPathInfo in tx.KnownKeyPathMapping) { _KeyPaths.TryAdd(keyPathInfo.Key, keyPathInfo.Value); } } // Let's remove the dups and let's get the current height of the transactions foreach (var trackedTx in transactions) { int? txHeight = null; bool isMature = true; if (trackedTx.BlockHash != null && headerChain.TryGetHeight(trackedTx.BlockHash, out var height)) { txHeight = height; isMature = trackedTx.IsCoinBase ? headerChain.Height - height >= network.Consensus.CoinbaseMaturity : true; } AnnotatedTransaction annotatedTransaction = new AnnotatedTransaction(txHeight, trackedTx, isMature); if (_TxById.TryGetValue(trackedTx.TransactionHash, out var conflicted)) { if (ShouldReplace(annotatedTransaction, conflicted)) { CleanupTransactions.Add(conflicted); _TxById.Remove(trackedTx.TransactionHash); _TxById.Add(trackedTx.TransactionHash, annotatedTransaction); } else { CleanupTransactions.Add(annotatedTransaction); } } else { _TxById.Add(trackedTx.TransactionHash, annotatedTransaction); } } // Let's resolve the double spents Dictionary <OutPoint, uint256> spentBy = new Dictionary <OutPoint, uint256>(transactions.SelectMany(t => t.SpentOutpoints).Count()); foreach (var annotatedTransaction in _TxById.Values.Where(r => r.Height is int)) { foreach (var spent in annotatedTransaction.Record.SpentOutpoints) { // No way to have double spent in confirmed transactions spentBy.Add(spent, annotatedTransaction.Record.TransactionHash); } } removeConflicts: HashSet <uint256> toRemove = new HashSet <uint256>(); foreach (var annotatedTransaction in _TxById.Values.Where(r => r.Height is null)) { foreach (var spent in annotatedTransaction.Record.SpentOutpoints) { if (spentBy.TryGetValue(spent, out var conflictHash) && _TxById.TryGetValue(conflictHash, out var conflicted)) { if (conflicted == annotatedTransaction) { goto nextTransaction; } if (toRemove.Contains(conflictHash)) { spentBy.Remove(spent); spentBy.Add(spent, annotatedTransaction.Record.TransactionHash); } else if (ShouldReplace(annotatedTransaction, conflicted)) { toRemove.Add(conflictHash); spentBy.Remove(spent); spentBy.Add(spent, annotatedTransaction.Record.TransactionHash); if (conflicted.Height is null && annotatedTransaction.Height is null) { ReplacedTransactions.Add(conflicted); } else { CleanupTransactions.Add(conflicted); } } else { toRemove.Add(annotatedTransaction.Record.TransactionHash); if (conflicted.Height is null && annotatedTransaction.Height is null) { ReplacedTransactions.Add(annotatedTransaction); }