public void CanTrack3() { using (var tester = ServerTester.Create()) { var key = new BitcoinExtKey(new ExtKey(), tester.Network); var pubkey = CreateDerivationStrategy(key.Neuter()); tester.Client.Track(pubkey); tester.Client.Sync(pubkey, null, null, true); //Track things do not wait var id = tester.RPC.SendToAddress(AddressOf(key, "0/0"), Money.Coins(1.0m)); id = tester.RPC.SendToAddress(AddressOf(key, "0/1"), Money.Coins(1.1m)); id = tester.RPC.SendToAddress(AddressOf(key, "0/2"), Money.Coins(1.2m)); UTXOChanges utxo = null; while (utxo == null || utxo.Unconfirmed.UTXOs.Count != 3) { utxo = tester.Client.Sync(pubkey, null, null); } tester.RPC.Generate(1); utxo = tester.Client.Sync(pubkey, utxo); Assert.True(utxo.HasChanges); Assert.Equal(3, utxo.Confirmed.UTXOs.Count); utxo = tester.Client.Sync(pubkey, utxo, true); Assert.False(utxo.HasChanges); } }
private async Task UpdateInvoice(string invoiceId) { UTXOChanges changes = null; while (true) { try { var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId).ConfigureAwait(false); if (invoice == null) { break; } var stateBefore = invoice.Status; var result = await UpdateInvoice(changes, invoice).ConfigureAwait(false); changes = result.Changes; if (result.NeedSave) { await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.Status, invoice.ExceptionStatus).ConfigureAwait(false); } var changed = stateBefore != invoice.Status; if (changed) { Logs.PayServer.LogInformation($"Invoice {invoice.Id}: {stateBefore} => {invoice.Status}"); } var expirationMonitoring = invoice.MonitoringExpiration.HasValue ? invoice.MonitoringExpiration.Value : invoice.InvoiceTime + TimeSpan.FromMinutes(60); if (invoice.Status == "complete" || ((invoice.Status == "invalid" || invoice.Status == "expired") && expirationMonitoring < DateTimeOffset.UtcNow)) { if (await _InvoiceRepository.RemovePendingInvoice(invoice.Id).ConfigureAwait(false)) { Logs.PayServer.LogInformation("Stopped watching invoice " + invoiceId); } break; } if (!changed || _Cts.Token.IsCancellationRequested) { break; } } catch (OperationCanceledException) when(_Cts.Token.IsCancellationRequested) { break; } catch (Exception ex) { Logs.PayServer.LogError(ex, "Unhandled error on watching invoice " + invoiceId); await Task.Delay(10000, _Cts.Token).ConfigureAwait(false); } } }
public async Task <UTXOChanges> GetUTXOs( string cryptoCode, [ModelBinder(BinderType = typeof(DestinationModelBinder))] DerivationStrategyBase extPubKey, [ModelBinder(BinderType = typeof(BookmarksModelBinding))] HashSet <Bookmark> confirmedBookmarks = null, [ModelBinder(BinderType = typeof(BookmarksModelBinding))] HashSet <Bookmark> unconfirmedBookmarks = null, bool longPolling = false) { unconfirmedBookmarks = unconfirmedBookmarks ?? new HashSet <Bookmark>(); confirmedBookmarks = confirmedBookmarks ?? new HashSet <Bookmark>(); if (extPubKey == null) { throw new ArgumentNullException(nameof(extPubKey)); } var network = GetNetwork(cryptoCode); var chain = ChainProvider.GetChain(network); var repo = RepositoryProvider.GetRepository(network); var waitingTransaction = longPolling ? WaitingTransaction(extPubKey) : Task.FromResult(false); UTXOChanges changes = null; while (true) { changes = new UTXOChanges(); changes.CurrentHeight = chain.Height; var transactions = GetAnnotatedTransactions(repo, chain, extPubKey); Func <Script[], bool[]> matchScript = (scripts) => scripts.Select(s => transactions.GetKeyPath(s) != null).ToArray(); var states = UTXOStateResult.CreateStates(matchScript, unconfirmedBookmarks, transactions.UnconfirmedTransactions.Values.Select(c => c.Record.Transaction), confirmedBookmarks, transactions.ConfirmedTransactions.Values.Select(c => c.Record.Transaction)); changes.Confirmed = SetUTXOChange(states.Confirmed); changes.Unconfirmed = SetUTXOChange(states.Unconfirmed, states.Confirmed.Actual); FillUTXOsInformation(changes.Confirmed.UTXOs, transactions, changes.CurrentHeight); FillUTXOsInformation(changes.Unconfirmed.UTXOs, transactions, changes.CurrentHeight); if (changes.HasChanges || !(await waitingTransaction)) { break; } waitingTransaction = Task.FromResult(false); //next time, will not wait } changes.DerivationStrategy = extPubKey; return(changes); }
private async Task <UTXOChanges> GetUTXOChanges(DerivationStrategyBase strategy, CancellationToken cancellation) { var thisCompletionSource = new TaskCompletionSource <UTXOChanges>(); var completionSource = _FetchingUTXOs.GetOrAdd(strategy.ToString(), (s) => thisCompletionSource); if (thisCompletionSource != completionSource) { return(await completionSource.Task); } try { var utxos = await _MemoryCache.GetOrCreateAsync("CACHEDCOINS_" + strategy.ToString(), async entry => { var now = DateTimeOffset.UtcNow; UTXOChanges result = null; try { result = await _Client.GetUTXOsAsync(strategy, cancellation).ConfigureAwait(false); } catch { Logs.PayServer.LogError($"{Network.CryptoCode}: Call to NBXplorer GetUTXOsAsync timed out, this should never happen, please report this issue to NBXplorer developers"); throw; } var spentTime = DateTimeOffset.UtcNow - now; if (spentTime.TotalSeconds > 30) { Logs.PayServer.LogWarning($"{Network.CryptoCode}: NBXplorer took {(int)spentTime.TotalSeconds} seconds to reply, there is something wrong, please report this issue to NBXplorer developers"); } entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan; return(result); }); _FetchingUTXOs.TryRemove(strategy.ToString(), out var unused); completionSource.TrySetResult(utxos); } catch (Exception ex) { completionSource.TrySetException(ex); } finally { _FetchingUTXOs.TryRemove(strategy.ToString(), out var unused); } return(await completionSource.Task); }
public async Task <UTXOChanges> SyncAsync(DerivationStrategyBase extKey, uint256 confHash, uint256 unconfHash, bool noWait = false, CancellationToken cancellation = default(CancellationToken)) { Dictionary <string, string> parameters = new Dictionary <string, string>(); if (confHash != null) { parameters.Add("confHash", confHash.ToString()); } if (unconfHash != null) { parameters.Add("unconfHash", unconfHash.ToString()); } parameters.Add("noWait", noWait.ToString()); var query = String.Join("&", parameters.Select(p => p.Key + "=" + p.Value).ToArray()); var bytes = await SendAsync <byte[]>(HttpMethod.Get, null, "v1/sync/{0}?" + query, new object[] { extKey.ToString() }, cancellation).ConfigureAwait(false); UTXOChanges changes = new UTXOChanges(); changes.FromBytes(bytes); return(changes); }
public UTXOChanges GetUTXOs(DerivationStrategyBase extKey, UTXOChanges previousChange, bool longPolling = true, CancellationToken cancellation = default) { return(GetUTXOsAsync(extKey, previousChange, longPolling, cancellation).GetAwaiter().GetResult()); }
private async Task <(bool NeedSave, UTXOChanges Changes)> UpdateInvoice(UTXOChanges changes, InvoiceEntity invoice) { bool needSave = false; //Fetch unknown payments var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy); changes = await _ExplorerClient.SyncAsync(strategy, changes, !LongPollingMode, _Cts.Token).ConfigureAwait(false); var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).ToArray(); List <Coin> receivedCoins = new List <Coin>(); foreach (var received in utxos) { if (invoice.AvailableAddressHashes.Contains(received.Output.ScriptPubKey.Hash.ToString())) { receivedCoins.Add(new Coin(received.Outpoint, received.Output)); } } var alreadyAccounted = new HashSet <OutPoint>(invoice.Payments.Select(p => p.Outpoint)); bool dirtyAddress = false; foreach (var coin in receivedCoins.Where(c => !alreadyAccounted.Contains(c.Outpoint))) { var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin).ConfigureAwait(false); invoice.Payments.Add(payment); dirtyAddress = true; } ////// if (invoice.Status == "new" && invoice.ExpirationTime < DateTimeOffset.UtcNow) { needSave = true; await _InvoiceRepository.UnaffectAddress(invoice.Id); invoice.Status = "expired"; if (invoice.FullNotifications) { _NotificationManager.Notify(invoice); } } if (invoice.Status == "new" || invoice.Status == "expired") { var totalPaid = (await GetPaymentsWithTransaction(invoice)).Select(p => p.Payment.Output.Value).Sum(); if (totalPaid >= invoice.GetTotalCryptoDue()) { if (invoice.Status == "new") { invoice.Status = "paid"; if (invoice.FullNotifications) { _NotificationManager.Notify(invoice); } invoice.ExceptionStatus = null; await _InvoiceRepository.UnaffectAddress(invoice.Id); needSave = true; } else if (invoice.Status == "expired") { invoice.ExceptionStatus = "paidLate"; needSave = true; } } if (totalPaid > invoice.GetTotalCryptoDue() && invoice.ExceptionStatus != "paidOver") { invoice.ExceptionStatus = "paidOver"; await _InvoiceRepository.UnaffectAddress(invoice.Id); needSave = true; } if (totalPaid < invoice.GetTotalCryptoDue() && invoice.Payments.Count != 0 && invoice.ExceptionStatus != "paidPartial") { Logs.PayServer.LogInformation("Paid to " + invoice.DepositAddress); invoice.ExceptionStatus = "paidPartial"; needSave = true; if (dirtyAddress) { var address = await _Wallet.ReserveAddressAsync(_DerivationFactory.Parse(invoice.DerivationStrategy)); Logs.PayServer.LogInformation("Generate new " + address); await _InvoiceRepository.NewAddress(invoice.Id, address); } } } if (invoice.Status == "paid") { var transactions = await GetPaymentsWithTransaction(invoice); var chainConfirmedTransactions = transactions.Where(t => t.Confirmations >= 1); if (invoice.SpeedPolicy == SpeedPolicy.HighSpeed) { transactions = transactions.Where(t => !t.Transaction.RBF); } else if (invoice.SpeedPolicy == SpeedPolicy.MediumSpeed) { transactions = transactions.Where(t => t.Confirmations >= 1); } else if (invoice.SpeedPolicy == SpeedPolicy.LowSpeed) { transactions = transactions.Where(t => t.Confirmations >= 6); } var chainTotalConfirmed = chainConfirmedTransactions.Select(t => t.Payment.Output.Value).Sum(); if (// Is after the monitoring deadline (invoice.MonitoringExpiration < DateTimeOffset.UtcNow) && // And not enough amount confirmed (chainTotalConfirmed < invoice.GetTotalCryptoDue())) { await _InvoiceRepository.UnaffectAddress(invoice.Id); invoice.Status = "invalid"; needSave = true; if (invoice.FullNotifications) { _NotificationManager.Notify(invoice); } } else { var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum(); if (totalConfirmed >= invoice.GetTotalCryptoDue()) { await _InvoiceRepository.UnaffectAddress(invoice.Id); invoice.Status = "confirmed"; _NotificationManager.Notify(invoice); needSave = true; } } } if (invoice.Status == "confirmed") { var transactions = await GetPaymentsWithTransaction(invoice); transactions = transactions.Where(t => t.Confirmations >= 6); var totalConfirmed = transactions.Select(t => t.Payment.Output.Value).Sum(); if (totalConfirmed >= invoice.GetTotalCryptoDue()) { invoice.Status = "complete"; if (invoice.FullNotifications) { _NotificationManager.Notify(invoice); } needSave = true; } } return(needSave, changes); }
public Money GetBalance(UTXOChanges utxoChanges) { return(new Money(utxoChanges.GetUnspentUTXOs().Select(c => c.Value).Sum(money => money.GetValue(null)), MoneyUnit.BTC)); }
public Task <UTXOChanges> GetUTXOsAsync(TrackedSource trackedSource, UTXOChanges previousChange, bool longPolling = true, CancellationToken cancellation = default) { return(GetUTXOsAsync(trackedSource, previousChange?.Confirmed?.Bookmark, previousChange?.Unconfirmed?.Bookmark, longPolling, cancellation)); }
public async Task <Money> GetBalance(DerivationStrategyBase derivationStrategy, CancellationToken cancellation = default(CancellationToken)) { UTXOChanges changes = await GetUTXOChanges(derivationStrategy, cancellation); return(changes.GetUnspentUTXOs().Select(c => c.Value).Sum()); }
void UseUtxoCoins(IEnumerable <WalletAddr> addrs, List <CoinCandidate> candidates, UTXOChanges utxos, int minConfs = 0) { if (minConfs <= 0) { foreach (var utxo in utxos.Unconfirmed.UTXOs) { var addrStr = utxo.ScriptPubKey.GetDestinationAddress(GetNetwork()).ToString(); var addr = addrs.Where(a => a.Address == addrStr).FirstOrDefault(); if (addr != null) { candidates.Add(new CoinCandidate(addr, utxo.AsCoin(), utxo)); } } } foreach (var utxo in utxos.Confirmed.UTXOs) { if (minConfs > 0 && utxo.Confirmations < minConfs) { continue; } // check is not already spent but as yet unconfirmed if (utxos.Unconfirmed.SpentOutpoints.Any(so => so.Hash == utxo.Outpoint.Hash)) { continue; } var addrStr = utxo.ScriptPubKey.GetDestinationAddress(GetNetwork()).ToString(); var addr = addrs.Where(a => a.Address == addrStr).FirstOrDefault(); if (addr != null) { candidates.Add(new CoinCandidate(addr, utxo.AsCoin(), utxo)); } } }
public async Task <FileContentResult> Sync( [ModelBinder(BinderType = typeof(DestinationModelBinder))] DerivationStrategyBase extPubKey, [ModelBinder(BinderType = typeof(UInt256ModelBinding))] uint256 confHash = null, [ModelBinder(BinderType = typeof(UInt256ModelBinding))] uint256 unconfHash = null, bool noWait = false) { if (extPubKey == null) { throw new ArgumentNullException(nameof(extPubKey)); } var waitingTransaction = noWait ? Task.FromResult(false) : WaitingTransaction(extPubKey); UTXOChanges changes = null; var getKeyPaths = GetKeyPaths(extPubKey); var matchScript = MatchKeyPaths(getKeyPaths); while (true) { changes = new UTXOChanges(); changes.CurrentHeight = Chain.Height; var transactions = GetAnnotatedTransactions(extPubKey); var unconf = transactions.Where(tx => tx.Height == MempoolHeight); var conf = transactions.Where(tx => tx.Height != MempoolHeight); conf = conf.TopologicalSort(DependsOn(conf.ToList())).ToList(); unconf = unconf.OrderByDescending(t => t.Record.Inserted) .TopologicalSort(DependsOn(unconf.ToList())).ToList(); var states = UTXOStateResult.CreateStates(matchScript, unconfHash, unconf.Select(c => c.Record.Transaction), confHash, conf.Select(c => c.Record.Transaction)); var conflicted = states.Unconfirmed.Actual.Conflicts .SelectMany(c => c.Value) .SelectMany(txid => transactions.GetByTxId(txid)) .Where(a => a.Height == MempoolHeight) .Select(a => a.Record) .Distinct() .ToList(); if (conflicted.Count != 0) { Logs.Explorer.LogInformation($"Clean {conflicted.Count} conflicted transactions"); if (Logs.Explorer.IsEnabled(LogLevel.Debug)) { foreach (var conflict in conflicted) { Logs.Explorer.LogDebug($"Transaction {conflict.Transaction.GetHash()} is conflicted"); } } Repository.CleanTransactions(extPubKey, conflicted); } changes.Confirmed = SetUTXOChange(states.Confirmed); changes.Unconfirmed = SetUTXOChange(states.Unconfirmed, states.Confirmed.Actual); FillUTXOsInformation(changes.Confirmed.UTXOs, getKeyPaths, transactions, changes.CurrentHeight); FillUTXOsInformation(changes.Unconfirmed.UTXOs, getKeyPaths, transactions, changes.CurrentHeight); if (changes.HasChanges || !(await waitingTransaction)) { break; } waitingTransaction = Task.FromResult(false); //next time, will not wait } return(new FileContentResult(changes.ToBytes(), "application/octet-stream")); }
private async Task UpdateInvoice(string invoiceId) { UTXOChanges changes = null; while (true) { try { var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, true).ConfigureAwait(false); if (invoice == null) { break; } var stateBefore = invoice.Status; var postSaveActions = new List <Action>(); var result = await UpdateInvoice(changes, invoice, postSaveActions).ConfigureAwait(false); changes = result.Changes; if (result.NeedSave) { await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.Status, invoice.ExceptionStatus).ConfigureAwait(false); _EventAggregator.Publish(new InvoiceDataChangedEvent() { InvoiceId = invoice.Id }); } var changed = stateBefore != invoice.Status; foreach (var saveAction in postSaveActions) { saveAction(); } if (invoice.Status == "complete" || ((invoice.Status == "invalid" || invoice.Status == "expired") && invoice.MonitoringExpiration < DateTimeOffset.UtcNow)) { if (await _InvoiceRepository.RemovePendingInvoice(invoice.Id).ConfigureAwait(false)) { Logs.PayServer.LogInformation("Stopped watching invoice " + invoiceId); } break; } if (!changed || _Cts.Token.IsCancellationRequested) { break; } } catch (OperationCanceledException) when(_Cts.Token.IsCancellationRequested) { break; } catch (Exception ex) { Logs.PayServer.LogError(ex, "Unhandled error on watching invoice " + invoiceId); await Task.Delay(10000, _Cts.Token).ConfigureAwait(false); } } }
public Money GetBalance(UTXOChanges utxoChanges) { return(utxoChanges.GetUnspentUTXOs().Select(c => c.Value).Sum()); }
private async Task <(bool NeedSave, UTXOChanges Changes)> UpdateInvoice(UTXOChanges changes, InvoiceEntity invoice) { bool needSave = false; if (invoice.Status != "invalid" && invoice.ExpirationTime < DateTimeOffset.UtcNow && (invoice.Status == "new" || invoice.Status == "paidPartial")) { needSave = true; invoice.Status = "invalid"; } if (invoice.Status == "invalid" || invoice.Status == "new" || invoice.Status == "paidPartial") { var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy); changes = await _ExplorerClient.SyncAsync(strategy, changes, !LongPollingMode, _Cts.Token).ConfigureAwait(false); var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).ToArray(); var invoiceIds = utxos.Select(u => _Wallet.GetInvoiceId(u.Output.ScriptPubKey)).ToArray(); utxos = utxos .Where((u, i) => invoiceIds[i].GetAwaiter().GetResult() == invoice.Id) .ToArray(); List <Coin> receivedCoins = new List <Coin>(); foreach (var received in utxos) { if (received.Output.ScriptPubKey == invoice.DepositAddress.ScriptPubKey) { receivedCoins.Add(new Coin(received.Outpoint, received.Output)); } } var alreadyAccounted = new HashSet <OutPoint>(invoice.Payments.Select(p => p.Outpoint)); foreach (var coin in receivedCoins.Where(c => !alreadyAccounted.Contains(c.Outpoint))) { var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin).ConfigureAwait(false); invoice.Payments.Add(payment); if (invoice.Status == "new") { invoice.Status = "paidPartial"; needSave = true; } } } if (invoice.Status == "paidPartial") { var totalPaid = invoice.Payments.Select(p => p.Output.Value).Sum(); if (totalPaid == invoice.GetTotalCryptoDue()) { invoice.Status = "paid"; if (invoice.FullNotifications) { _NotificationManager.Notify(invoice); } invoice.ExceptionStatus = null; needSave = true; } if (totalPaid > invoice.GetTotalCryptoDue()) { invoice.Status = "paidOver"; invoice.ExceptionStatus = "paidOver"; needSave = true; } if (totalPaid < invoice.GetTotalCryptoDue() && invoice.ExceptionStatus == null) { invoice.ExceptionStatus = "paidPartial"; needSave = true; } } if (invoice.Status == "paid" || invoice.Status == "paidOver") { var getTransactions = invoice.Payments.Select(o => o.Outpoint.Hash).Select(o => _ExplorerClient.GetTransactionAsync(o, _Cts.Token)).ToArray(); await Task.WhenAll(getTransactions).ConfigureAwait(false); var transactions = getTransactions.Select(c => c.GetAwaiter().GetResult()).ToArray(); bool confirmed = false; var minConf = transactions.Select(t => t.Confirmations).Min(); if (invoice.SpeedPolicy == SpeedPolicy.HighSpeed) { if (minConf > 0) { confirmed = true; } else { confirmed = !transactions.Any(t => t.Transaction.RBF); } } else if (invoice.SpeedPolicy == SpeedPolicy.MediumSpeed) { confirmed = minConf >= 1; } else if (invoice.SpeedPolicy == SpeedPolicy.LowSpeed) { confirmed = minConf >= 6; } if (confirmed) { invoice.Status = "confirmed"; _NotificationManager.Notify(invoice); needSave = true; } } if (invoice.Status == "confirmed") { var getTransactions = invoice.Payments.Select(o => o.Outpoint.Hash).Select(o => _ExplorerClient.GetTransactionAsync(o, _Cts.Token)).ToArray(); await Task.WhenAll(getTransactions).ConfigureAwait(false); var transactions = getTransactions.Select(c => c.GetAwaiter().GetResult()).ToArray(); var minConf = transactions.Select(t => t.Confirmations).Min(); if (minConf >= 6) { invoice.Status = "complete"; if (invoice.FullNotifications) { _NotificationManager.Notify(invoice); } needSave = true; } } return(needSave, changes); }
public UTXOChanges GetUTXOs(TrackedSource trackedSource, UTXOChanges previousChange, bool longPolling = true, CancellationToken cancellation = default) { return(GetUTXOsAsync(trackedSource, previousChange, longPolling, cancellation).GetAwaiter().GetResult()); }
public Task <UTXOChanges> GetUTXOsAsync(DerivationStrategyBase extKey, UTXOChanges previousChange, bool longPolling = true, CancellationToken cancellation = default) { return(GetUTXOsAsync(extKey, previousChange?.Confirmed?.Bookmark, previousChange?.Unconfirmed?.Bookmark, longPolling, cancellation)); }
public UTXOChanges Sync(DerivationStrategyBase extKey, UTXOChanges previousChange, bool noWait = false, CancellationToken cancellation = default(CancellationToken)) { return(SyncAsync(extKey, previousChange, noWait, cancellation).GetAwaiter().GetResult()); }
public Task <UTXOChanges> SyncAsync(DerivationStrategyBase extKey, UTXOChanges previousChange, bool noWait = false, CancellationToken cancellation = default(CancellationToken)) { return(SyncAsync(extKey, previousChange?.Confirmed?.Hash, previousChange?.Unconfirmed?.Hash, noWait, cancellation)); }
public async Task <FileContentResult> Sync( [ModelBinder(BinderType = typeof(DestinationModelBinder))] BitcoinExtPubKey extPubKey, [ModelBinder(BinderType = typeof(UInt256ModelBinding))] uint256 lastBlockHash = null, [ModelBinder(BinderType = typeof(UInt256ModelBinding))] uint256 unconfirmedHash = null, bool noWait = false) { lastBlockHash = lastBlockHash ?? uint256.Zero; var actualLastBlockHash = uint256.Zero; var waitingTransaction = noWait ? Task.FromResult(false) : WaitingTransaction(extPubKey); Runtime.Repository.MarkAsUsed(new KeyInformation(extPubKey)); UTXOChanges changes = null; UTXOChanges previousChanges = null; List <TrackedTransaction> cleanList = null; var getKeyPath = GetKeyPaths(extPubKey); while (true) { cleanList = new List <TrackedTransaction>(); HashSet <uint256> conflictedUnconf = new HashSet <uint256>(); changes = new UTXOChanges(); List <AnnotatedTransaction> transactions = GetAnnotatedTransactions(extPubKey); var unconf = transactions.Where(tx => tx.Height == MempoolHeight); var conf = transactions.Where(tx => tx.Height != MempoolHeight); conf = conf.TopologicalSort(DependsOn(conf.ToList())).ToList(); unconf = unconf.TopologicalSort(DependsOn(unconf.ToList())).ToList(); foreach (var item in conf.Concat(unconf)) { var record = item.Record; if (record.BlockHash == null) { if ( //A parent conflicted with the current utxo record.Transaction.Inputs.Any(i => conflictedUnconf.Contains(i.PrevOut.Hash)) || //Conflict with the confirmed utxo changes.Confirmed.HasConflict(record.Transaction)) { cleanList.Add(record); conflictedUnconf.Add(record.Transaction.GetHash()); continue; } if (changes.Unconfirmed.HasConflict(record.Transaction)) { Logs.Explorer.LogInformation($"Conflicts in the mempool. {record.Transaction.GetHash()} ignored"); continue; } changes.Unconfirmed.LoadChanges(record.Transaction, getKeyPath); } else { if (changes.Confirmed.HasConflict(record.Transaction)) { Logs.Explorer.LogError("A conflict among confirmed transaction happened, this should be impossible"); throw new InvalidOperationException("The impossible happened"); } changes.Unconfirmed.LoadChanges(record.Transaction, getKeyPath); changes.Confirmed.LoadChanges(record.Transaction, getKeyPath); changes.Confirmed.Hash = record.BlockHash; actualLastBlockHash = record.BlockHash; if (record.BlockHash == lastBlockHash) { previousChanges = changes.Clone(); } } } changes.Unconfirmed = changes.Unconfirmed.Diff(changes.Confirmed); changes.Unconfirmed.Hash = changes.Unconfirmed.GetHash(); if (changes.Unconfirmed.Hash == unconfirmedHash) { changes.Unconfirmed.Clear(); } else { changes.Unconfirmed.Reset = true; } if (actualLastBlockHash == lastBlockHash) { changes.Confirmed.Clear(); } else if (previousChanges != null) { changes.Confirmed.Reset = false; changes.Confirmed = changes.Confirmed.Diff(previousChanges.Confirmed); } else { changes.Confirmed.Reset = true; changes.Confirmed.SpentOutpoints.Clear(); } if (changes.HasChanges || !(await waitingTransaction)) { break; } waitingTransaction = Task.FromResult(false); //next time, will not wait } Runtime.Repository.CleanTransactions(extPubKey.ExtPubKey, cleanList); return(new FileContentResult(changes.ToBytes(), "application/octet-stream")); }