public static bool TryParse(string str, out TrackedSource trackedSource, NBXplorerNetwork network) { if (str == null) { throw new ArgumentNullException(nameof(str)); } if (network == null) { throw new ArgumentNullException(nameof(network)); } trackedSource = null; var strSpan = str.AsSpan(); if (strSpan.StartsWith("DERIVATIONSCHEME:".AsSpan(), StringComparison.Ordinal)) { if (!DerivationSchemeTrackedSource.TryParse(strSpan, out var derivationSchemeTrackedSource, network)) { return(false); } trackedSource = derivationSchemeTrackedSource; } else if (strSpan.StartsWith("ADDRESS:".AsSpan(), StringComparison.Ordinal)) { if (!AddressTrackedSource.TryParse(strSpan, out var addressTrackedSource, network.NBitcoinNetwork)) { return(false); } trackedSource = addressTrackedSource; } else { return(false); } return(true); }
public override bool Equals(object obj) { TrackedSource item = obj as TrackedSource; if (item == null) { return(false); } return(ToString().Equals(item.ToString())); }
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 override string ToString() { var conf = (BlockId == null ? "unconfirmed" : "confirmed"); string strategy = TrackedSource.ToPrettyString(); var txId = TransactionData.TransactionHash.ToString(); txId = txId.Substring(0, 6) + "..." + txId.Substring(txId.Length - 6); string keyPathSuffix = string.Empty; var keyPaths = Outputs.Select(v => v.KeyPath?.ToString()).Where(k => k != null).ToArray(); if (keyPaths.Length != 0) { keyPathSuffix = $" ({String.Join(", ", keyPaths)})"; } return($"{CryptoCode}: {strategy} matching {conf} transaction {txId}{keyPathSuffix}"); }
public static bool TryParse(ReadOnlySpan <char> strSpan, out TrackedSource addressTrackedSource, Network network) { if (strSpan == null) { throw new ArgumentNullException(nameof(strSpan)); } if (network == null) { throw new ArgumentNullException(nameof(network)); } addressTrackedSource = null; if (!strSpan.StartsWith("ADDRESS:".AsSpan(), StringComparison.Ordinal)) { return(false); } try { addressTrackedSource = new AddressTrackedSource(BitcoinAddress.Create(strSpan.Slice("ADDRESS:".Length).ToString(), network)); return(true); } catch { return(false); } }
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); }