public AnnotatedTransactionCollection(IEnumerable <AnnotatedTransaction> transactions, Models.TrackedSource trackedSource) : base(transactions) { foreach (var tx in transactions) { foreach (var keyPathInfo in tx.Record.KnownKeyPathMapping) { _KeyPaths.TryAdd(keyPathInfo.Key, keyPathInfo.Value); } } UTXOState state = new UTXOState(); foreach (var confirmed in transactions .Where(tx => tx.Type == AnnotatedTransactionType.Confirmed).ToList() .TopologicalSort()) { if (state.Apply(confirmed.Record) == ApplyTransactionResult.Conflict) { Logs.Explorer.LogError("A conflict among confirmed transaction happened, this should be impossible"); throw new InvalidOperationException("The impossible happened"); } ConfirmedTransactions.Add(confirmed); _TxById.Add(confirmed.Record.TransactionHash, confirmed); } foreach (var unconfirmed in transactions .Where(tx => tx.Type == AnnotatedTransactionType.Unconfirmed || tx.Type == AnnotatedTransactionType.Orphan) .OrderByDescending(t => t.Record.Inserted) // OrderByDescending so that the last received is least likely to be conflicted .ToList() .TopologicalSort()) { if (_TxById.ContainsKey(unconfirmed.Record.TransactionHash)) { _TxById.Add(unconfirmed.Record.TransactionHash, unconfirmed); DuplicatedTransactions.Add(unconfirmed); } else { _TxById.Add(unconfirmed.Record.TransactionHash, unconfirmed); if (state.Apply(unconfirmed.Record) == ApplyTransactionResult.Conflict) { ReplacedTransactions.Add(unconfirmed); } else { UnconfirmedTransactions.Add(unconfirmed); } } } TrackedSource = trackedSource; }
public BalanceSheet(IEnumerable <OrderedBalanceChange> changes, ChainBase chain) { if (chain == null) { throw new ArgumentNullException("chain"); } _Chain = chain; var transactionsById = new Dictionary <uint256, OrderedBalanceChange>(); List <OrderedBalanceChange> ignoredTransactions = new List <OrderedBalanceChange>(); foreach (var trackedTx in changes) { int?txHeight = null; if (trackedTx.BlockId != null && chain.TryGetHeight(trackedTx.BlockId, out var height)) { txHeight = height; } if (trackedTx.BlockId != null && txHeight is null) { _Prunable.Add(trackedTx); continue; } if (transactionsById.TryGetValue(trackedTx.TransactionId, out var conflicted)) { if (ShouldReplace(trackedTx, conflicted)) { ignoredTransactions.Add(conflicted); transactionsById.Remove(trackedTx.TransactionId); transactionsById.Add(trackedTx.TransactionId, trackedTx); } else { ignoredTransactions.Add(trackedTx); } } else { transactionsById.Add(trackedTx.TransactionId, trackedTx); } } // Let's resolve the double spents Dictionary <OutPoint, uint256> spentBy = new Dictionary <OutPoint, uint256>(); HashSet <uint256> incoherent = new HashSet <uint256>(); foreach (var annotatedTransaction in transactionsById.Values.Where(r => r.BlockId != null)) { foreach (var spent in annotatedTransaction.SpentOutpoints) { // No way to have double spent in confirmed transactions spentBy.Add(spent, annotatedTransaction.TransactionId); if (transactionsById.TryGetValue(spent.Hash, out var spentTx) && spentTx.BlockId == null) { // It happens that we can get a conf depending on an unconf tx (because of queries on Azure not returning it) // in such case, if we see that the unconf has a conf'd parent transaction, just do like if it did not exist incoherent.Add(spent.Hash); } } } foreach (var incoherentHash in incoherent) { transactionsById.Remove(incoherentHash); } removeConflicts: HashSet <uint256> conflicts = new HashSet <uint256>(); foreach (var annotatedTransaction in transactionsById.Values.Where(r => r.BlockId == null)) { foreach (var spent in annotatedTransaction.SpentOutpoints) { if (spentBy.TryGetValue(spent, out var conflictHash) && transactionsById.TryGetValue(conflictHash, out var conflicted)) { if (conflicted == annotatedTransaction) { goto nextTransaction; } if (conflicts.Contains(conflictHash)) { spentBy.Remove(spent); spentBy.Add(spent, annotatedTransaction.TransactionId); } else if (ShouldReplace(annotatedTransaction, conflicted)) { conflicts.Add(conflictHash); spentBy.Remove(spent); spentBy.Add(spent, annotatedTransaction.TransactionId); if (conflicted.BlockId == null) { ReplacedTransactions.Add(conflicted); } else { ignoredTransactions.Add(conflicted); } } else { conflicts.Add(annotatedTransaction.TransactionId); if (conflicted.BlockId == null) { ReplacedTransactions.Add(annotatedTransaction); } else { ignoredTransactions.Add(annotatedTransaction); } } } else { spentBy.Add(spent, annotatedTransaction.TransactionId); } } nextTransaction :; } foreach (var e in conflicts) { transactionsById.Remove(e); } if (conflicts.Count != 0) { goto removeConflicts; } // Topological sort var sortedTrackedTransactions = transactionsById.Values.TopologicalSort(); // Remove all ignored transaction from the database foreach (var ignored in ignoredTransactions) { _Prunable.Add(ignored); } _All = sortedTrackedTransactions; _All.Reverse(); _Confirmed = sortedTrackedTransactions.Where(s => s.BlockId != null).ToList(); _Unconfirmed = sortedTrackedTransactions.Where(s => s.BlockId == null).ToList(); }
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); }