public async Task <IActionResult> GetStatus(string cryptoCode) { var network = GetNetwork(cryptoCode, false); var waiter = Waiters.GetWaiter(network); var chain = ChainProvider.GetChain(network); var repo = RepositoryProvider.GetRepository(network); var now = DateTimeOffset.UtcNow; var location = waiter.GetLocation(); var blockchainInfoAsync = waiter.RPCAvailable ? waiter.RPC.GetBlockchainInfoAsyncEx() : null; var networkInfoAsync = waiter.RPCAvailable ? waiter.RPC.GetNetworkInfoAsync() : null; await repo.Ping(); var pingAfter = DateTimeOffset.UtcNow; GetBlockchainInfoResponse blockchainInfo = blockchainInfoAsync == null ? null : await blockchainInfoAsync; GetNetworkInfoResponse networkInfo = networkInfoAsync == null ? null : await networkInfoAsync; var status = new StatusResult() { NetworkType = network.NBitcoinNetwork.NetworkType, CryptoCode = network.CryptoCode, Version = typeof(MainController).GetTypeInfo().Assembly.GetCustomAttribute <AssemblyFileVersionAttribute>().Version, SupportedCryptoCodes = Waiters.All().Select(w => w.Network.CryptoCode).ToArray(), RepositoryPingTime = (pingAfter - now).TotalSeconds, IsFullySynched = true }; if (status.RepositoryPingTime > 30) { Logs.Explorer.LogWarning($"Repository ping exceeded 30 seconds ({(int)status.RepositoryPingTime}), please report the issue to NBXplorer developers"); } if (blockchainInfo != null) { status.BitcoinStatus = new BitcoinStatus() { IsSynched = !waiter.IsSynchingCore(blockchainInfo), Blocks = (int)blockchainInfo.Blocks, Headers = (int)blockchainInfo.Headers, VerificationProgress = blockchainInfo.VerificationProgress, MinRelayTxFee = new FeeRate(Money.Coins((decimal)networkInfo.relayfee), 1000), IncrementalRelayFee = new FeeRate(Money.Coins((decimal)networkInfo.incrementalfee), 1000), Capabilities = new NodeCapabilities() { CanScanTxoutSet = waiter.RPC.Capabilities.SupportScanUTXOSet, CanSupportSegwit = waiter.RPC.Capabilities.SupportSegwit } }; status.IsFullySynched &= status.BitcoinStatus.IsSynched; } status.ChainHeight = chain.Height; status.SyncHeight = location == null ? (int?)null : chain.FindFork(location).Height; status.IsFullySynched &= blockchainInfo != null && waiter.State == BitcoinDWaiterState.Ready && status.SyncHeight.HasValue && blockchainInfo.Headers - status.SyncHeight.Value < 3; return(Json(status)); }
public Task StartAsync(CancellationToken cancellationToken) { if (_Disposed) { throw new ObjectDisposedException(nameof(BrokerHostedService)); } _senderBlock = CreateClientBlock(); _senderTransactions = CreateClientTransaction(); _subscriptions.Add(_EventAggregator.Subscribe <Events.NewBlockEvent>(async o => { var chain = ChainProvider.GetChain(o.CryptoCode); if (chain == null) { return; } var block = chain.GetBlock(o.BlockId); if (block != null) { var nbe = new Models.NewBlockEvent() { CryptoCode = o.CryptoCode, Hash = block.Hash, Height = block.Height, PreviousBlockHash = block?.Previous }; await _senderBlock.Send(nbe); } })); _subscriptions.Add(_EventAggregator.Subscribe <Events.NewTransactionMatchEvent>(async o => { var network = Waiters.GetWaiter(o.CryptoCode); if (network == null) { return; } var chain = ChainProvider.GetChain(o.CryptoCode); if (chain == null) { return; } var txe = new Models.NewTransactionEvent() { TrackedSource = o.Match.TrackedSource, DerivationStrategy = o.Match.DerivationStrategy, CryptoCode = o.CryptoCode, BlockId = o.BlockId, TransactionData = Utils.ToTransactionResult(true, chain, new[] { o.SavedTransaction }), Inputs = o.Match.Inputs, Outputs = o.Match.Outputs }; await _senderTransactions.Send(txe); })); Logs.Configuration.LogInformation("Starting Azure Service Bus Message Broker"); return(Task.CompletedTask); }
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; } }
public async Task <IActionResult> GetTransactions( string cryptoCode, [ModelBinder(BinderType = typeof(DerivationStrategyModelBinder))] DerivationStrategyBase derivationScheme, [ModelBinder(BinderType = typeof(BitcoinAddressModelBinder))] BitcoinAddress address, [ModelBinder(BinderType = typeof(UInt256ModelBinding))] uint256 txId = null, bool includeTransaction = true) { var trackedSource = GetTrackedSource(derivationScheme, address); if (trackedSource == null) { throw new ArgumentNullException(nameof(trackedSource)); } TransactionInformation fetchedTransactionInfo = null; var network = GetNetwork(cryptoCode, false); var chain = ChainProvider.GetChain(network); var repo = RepositoryProvider.GetRepository(network); var response = new GetTransactionsResponse(); int currentHeight = chain.Height; response.Height = currentHeight; var txs = await GetAnnotatedTransactions(repo, chain, trackedSource, txId); foreach (var item in new[] { new { TxSet = response.ConfirmedTransactions, AnnotatedTx = txs.ConfirmedTransactions }, new { TxSet = response.UnconfirmedTransactions, AnnotatedTx = txs.UnconfirmedTransactions }, new { TxSet = response.ReplacedTransactions, AnnotatedTx = txs.ReplacedTransactions }, }) { foreach (var tx in item.AnnotatedTx) { var txInfo = new TransactionInformation() { BlockHash = tx.Height.HasValue ? tx.Record.BlockHash : null, Height = tx.Height, TransactionId = tx.Record.TransactionHash, Transaction = includeTransaction ? tx.Record.Transaction : null, Confirmations = tx.Height.HasValue ? currentHeight - tx.Height.Value + 1 : 0, Timestamp = tx.Record.FirstSeen, Inputs = tx.Record.SpentOutpoints.Select(o => txs.GetUTXO(o)).Where(o => o != null).ToList(), Outputs = tx.Record.GetReceivedOutputs().ToList() }; if (txId == null || txId == txInfo.TransactionId) { item.TxSet.Transactions.Add(txInfo); } if (txId != null && txId == txInfo.TransactionId) { fetchedTransactionInfo = txInfo; } txInfo.BalanceChange = txInfo.Outputs.Select(o => o.Value).Sum() - txInfo.Inputs.Select(o => o.Value).Sum(); } item.TxSet.Transactions.Reverse(); // So the youngest transaction is generally first } if (txId == null) { return(Json(response)); } else if (fetchedTransactionInfo == null) { return(NotFound()); } else { return(Json(fetchedTransactionInfo)); } }
public async Task <IActionResult> GetStatus(string cryptoCode) { var network = GetNetwork(cryptoCode, false); var waiter = Waiters.GetWaiter(network); var chain = ChainProvider.GetChain(network); var repo = RepositoryProvider.GetRepository(network); var location = waiter.GetLocation(); GetBlockchainInfoResponse blockchainInfo = null; if (waiter.RPCAvailable) { try { var rpc = waiter.RPC.Clone(); rpc.RequestTimeout = TimeSpan.FromMinutes(1.0); blockchainInfo = await rpc.GetBlockchainInfoAsyncEx(); } catch (OperationCanceledException) // Timeout, can happen if core is really busy { } } var status = new StatusResult() { NetworkType = network.NBitcoinNetwork.NetworkType, CryptoCode = network.CryptoCode, Version = typeof(MainController).GetTypeInfo().Assembly.GetCustomAttribute <AssemblyFileVersionAttribute>().Version, SupportedCryptoCodes = Waiters.All().Select(w => w.Network.CryptoCode).ToArray(), IsFullySynched = true }; GetNetworkInfoResponse networkInfo = waiter.NetworkInfo; if (blockchainInfo != null && networkInfo != null) { status.BitcoinStatus = new BitcoinStatus() { IsSynched = !waiter.IsSynchingCore(blockchainInfo), Blocks = (int)blockchainInfo.Blocks, Headers = (int)blockchainInfo.Headers, VerificationProgress = blockchainInfo.VerificationProgress, MinRelayTxFee = networkInfo.GetRelayFee(), IncrementalRelayFee = networkInfo.GetIncrementalFee(), Capabilities = new NodeCapabilities() { CanScanTxoutSet = waiter.RPC.Capabilities.SupportScanUTXOSet, CanSupportSegwit = waiter.RPC.Capabilities.SupportSegwit }, ExternalAddresses = (networkInfo.localaddresses ?? Array.Empty <GetNetworkInfoResponse.LocalAddress>()) .Select(l => $"{l.address}:{l.port}").ToArray() }; status.IsFullySynched &= status.BitcoinStatus.IsSynched; } status.ChainHeight = chain.Height; status.SyncHeight = location == null ? (int?)null : chain.FindFork(location).Height; status.IsFullySynched &= blockchainInfo != null && waiter.State == BitcoinDWaiterState.Ready && status.SyncHeight.HasValue && blockchainInfo.Headers - status.SyncHeight.Value < 3; if (status.IsFullySynched) { var now = DateTimeOffset.UtcNow; await repo.Ping(); var pingAfter = DateTimeOffset.UtcNow; status.RepositoryPingTime = (pingAfter - now).TotalSeconds; if (status.RepositoryPingTime > 30) { Logs.Explorer.LogWarning($"Repository ping exceeded 30 seconds ({(int)status.RepositoryPingTime}), please report the issue to NBXplorer developers"); } } return(Json(status)); }
public async Task <IActionResult> CreatePSBT( [ModelBinder(BinderType = typeof(NetworkModelBinder))] NBXplorerNetwork network, [ModelBinder(BinderType = typeof(DerivationStrategyModelBinder))] DerivationStrategyBase strategy, [FromBody] JObject body) { if (body == null) { throw new ArgumentNullException(nameof(body)); } CreatePSBTRequest request = ParseJObject <CreatePSBTRequest>(body, network); if (strategy == null) { throw new ArgumentNullException(nameof(strategy)); } var repo = RepositoryProvider.GetRepository(network); var txBuilder = request.Seed is int s?network.NBitcoinNetwork.CreateTransactionBuilder(s) : network.NBitcoinNetwork.CreateTransactionBuilder(); CreatePSBTSuggestions suggestions = null; if (!(request.DisableFingerprintRandomization is true) && fingerprintService.GetDistribution(network) is FingerprintDistribution distribution) { suggestions ??= new CreatePSBTSuggestions(); var known = new List <(Fingerprint feature, bool value)>(); if (request.RBF is bool rbf) { known.Add((Fingerprint.RBF, rbf)); } if (request.DiscourageFeeSniping is bool feeSnipping) { known.Add((Fingerprint.FeeSniping, feeSnipping)); } if (request.LockTime is LockTime l) { if (l == LockTime.Zero) { known.Add((Fingerprint.TimelockZero, true)); } } if (request.Version is uint version) { if (version == 1) { known.Add((Fingerprint.V1, true)); } if (version == 2) { known.Add((Fingerprint.V2, true)); } } known.Add((Fingerprint.SpendFromMixed, false)); known.Add((Fingerprint.SequenceMixed, false)); if (strategy is DirectDerivationStrategy direct) { if (direct.Segwit) { known.Add((Fingerprint.SpendFromP2WPKH, true)); } else { known.Add((Fingerprint.SpendFromP2PKH, true)); } } else { // TODO: What if multisig? For now we consider it p2wpkh known.Add((Fingerprint.SpendFromP2SHP2WPKH, true)); } Fingerprint fingerprint = distribution.PickFingerprint(txBuilder.ShuffleRandom); try { fingerprint = distribution.KnowingThat(known.ToArray()) .PickFingerprint(txBuilder.ShuffleRandom); } catch (InvalidOperationException) { } request.RBF ??= fingerprint.HasFlag(Fingerprint.RBF); request.DiscourageFeeSniping ??= fingerprint.HasFlag(Fingerprint.FeeSniping); if (request.LockTime is null && fingerprint.HasFlag(Fingerprint.TimelockZero)) { request.LockTime = new LockTime(0); } if (request.Version is null && fingerprint.HasFlag(Fingerprint.V1)) { request.Version = 1; } if (request.Version is null && fingerprint.HasFlag(Fingerprint.V2)) { request.Version = 2; } suggestions.ShouldEnforceLowR = fingerprint.HasFlag(Fingerprint.LowR); } var waiter = Waiters.GetWaiter(network); if (waiter.NetworkInfo?.GetRelayFee() is FeeRate feeRate) { txBuilder.StandardTransactionPolicy.MinRelayTxFee = feeRate; } txBuilder.OptInRBF = !(request.RBF is false); if (request.LockTime is LockTime lockTime) { txBuilder.SetLockTime(lockTime); } // Discourage fee sniping. // // For a large miner the value of the transactions in the best block and // the mempool can exceed the cost of deliberately attempting to mine two // blocks to orphan the current best block. By setting nLockTime such that // only the next block can include the transaction, we discourage this // practice as the height restricted and limited blocksize gives miners // considering fee sniping fewer options for pulling off this attack. // // A simple way to think about this is from the wallet's point of view we // always want the blockchain to move forward. By setting nLockTime this // way we're basically making the statement that we only want this // transaction to appear in the next block; we don't want to potentially // encourage reorgs by allowing transactions to appear at lower heights // than the next block in forks of the best chain. // // Of course, the subsidy is high enough, and transaction volume low // enough, that fee sniping isn't a problem yet, but by implementing a fix // now we ensure code won't be written that makes assumptions about // nLockTime that preclude a fix later. else if (!(request.DiscourageFeeSniping is false)) { if (waiter.State is BitcoinDWaiterState.Ready) { int blockHeight = ChainProvider.GetChain(network).Height; // Secondly occasionally randomly pick a nLockTime even further back, so // that transactions that are delayed after signing for whatever reason, // e.g. high-latency mix networks and some CoinJoin implementations, have // better privacy. if (txBuilder.ShuffleRandom.Next(0, 10) == 0) { blockHeight = Math.Max(0, blockHeight - txBuilder.ShuffleRandom.Next(0, 100)); } txBuilder.SetLockTime(new LockTime(blockHeight)); } else { txBuilder.SetLockTime(new LockTime(0)); } } var utxos = (await GetUTXOs(network.CryptoCode, strategy, null)).As <UTXOChanges>().GetUnspentUTXOs(request.MinConfirmations); var availableCoinsByOutpoint = utxos.ToDictionary(o => o.Outpoint); if (request.IncludeOnlyOutpoints != null) { var includeOnlyOutpoints = request.IncludeOnlyOutpoints.ToHashSet(); availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => includeOnlyOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value); } if (request.ExcludeOutpoints?.Any() is true) { var excludedOutpoints = request.ExcludeOutpoints.ToHashSet(); availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => !excludedOutpoints.Contains(c.Key)).ToDictionary(o => o.Key, o => o.Value); } if (request.MinValue != null) { availableCoinsByOutpoint = availableCoinsByOutpoint.Where(c => request.MinValue >= (Money)c.Value.Value).ToDictionary(o => o.Key, o => o.Value); } ICoin[] coins = null; if (strategy.GetDerivation().Redeem != null) { // We need to add the redeem script to the coins var hdKeys = strategy.AsHDRedeemScriptPubKey().AsHDKeyCache(); var arr = availableCoinsByOutpoint.Values.ToArray(); coins = new ICoin[arr.Length]; // Can be very intense CPU wise Parallel.For(0, coins.Length, i => { coins[i] = ((Coin)arr[i].AsCoin()).ToScriptCoin(hdKeys.Derive(arr[i].KeyPath).ScriptPubKey); }); } else { coins = availableCoinsByOutpoint.Values.Select(v => v.AsCoin()).ToArray(); } txBuilder.AddCoins(coins); foreach (var dest in request.Destinations) { if (dest.SweepAll) { try { txBuilder.SendAll(dest.Destination); } catch { throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "You can't sweep funds, because you don't have any.")); } } else { txBuilder.Send(dest.Destination, dest.Amount); if (dest.SubstractFees) { try { txBuilder.SubtractFees(); } catch { throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "You can't substract fee on this destination, because not enough money was sent to it")); } } } } (Script ScriptPubKey, KeyPath KeyPath)change = (null, null); bool hasChange = false; if (request.ExplicitChangeAddress == null) { var keyInfo = await repo.GetUnused(strategy, DerivationFeature.Change, 0, false); change = (keyInfo.ScriptPubKey, keyInfo.KeyPath); } else { // The provided explicit change might have a known keyPath, let's change for it KeyPath keyPath = null; var keyInfos = await repo.GetKeyInformations(new[] { request.ExplicitChangeAddress.ScriptPubKey }); if (keyInfos.TryGetValue(request.ExplicitChangeAddress.ScriptPubKey, out var kis)) { keyPath = kis.FirstOrDefault(k => k.DerivationStrategy == strategy)?.KeyPath; } change = (request.ExplicitChangeAddress.ScriptPubKey, keyPath); } txBuilder.SetChange(change.ScriptPubKey); PSBT psbt = null; try { if (request.FeePreference?.ExplicitFeeRate is FeeRate explicitFeeRate) { txBuilder.SendEstimatedFees(explicitFeeRate); } else if (request.FeePreference?.BlockTarget is int blockTarget) { try { var rate = await GetFeeRate(blockTarget, network.CryptoCode); txBuilder.SendEstimatedFees(rate.FeeRate); } catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate) { txBuilder.SendEstimatedFees(fallbackFeeRate); } } else if (request.FeePreference?.ExplicitFee is Money explicitFee) { txBuilder.SendFees(explicitFee); } else { try { var rate = await GetFeeRate(1, network.CryptoCode); txBuilder.SendEstimatedFees(rate.FeeRate); } catch (NBXplorerException e) when(e.Error.Code == "fee-estimation-unavailable" && request.FeePreference?.FallbackFeeRate is FeeRate fallbackFeeRate) { txBuilder.SendEstimatedFees(fallbackFeeRate); } } psbt = txBuilder.BuildPSBT(false); hasChange = psbt.Outputs.Any(o => o.ScriptPubKey == change.ScriptPubKey); } catch (NotEnoughFundsException) { throw new NBXplorerException(new NBXplorerError(400, "not-enough-funds", "Not enough funds for doing this transaction")); } // We made sure we can build the PSBT, so now we can reserve the change address if we need to if (hasChange && request.ExplicitChangeAddress == null && request.ReserveChangeAddress) { var derivation = await repo.GetUnused(strategy, DerivationFeature.Change, 0, true); // In most of the time, this is the same as previously, so no need to rebuild PSBT if (derivation.ScriptPubKey != change.ScriptPubKey) { change = (derivation.ScriptPubKey, derivation.KeyPath); txBuilder.SetChange(change.ScriptPubKey); psbt = txBuilder.BuildPSBT(false); } } var tx = psbt.GetOriginalTransaction(); if (request.Version is uint v) { tx.Version = v; } psbt = txBuilder.CreatePSBTFrom(tx, false, SigHash.All); var update = new UpdatePSBTRequest() { DerivationScheme = strategy, PSBT = psbt, RebaseKeyPaths = request.RebaseKeyPaths, AlwaysIncludeNonWitnessUTXO = request.AlwaysIncludeNonWitnessUTXO, IncludeGlobalXPub = request.IncludeGlobalXPub }; await UpdatePSBTCore(update, network); var resp = new CreatePSBTResponse() { PSBT = update.PSBT, ChangeAddress = hasChange ? change.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork) : null, Suggestions = suggestions }; return(Json(resp, network.JsonSerializerSettings)); }
public async Task <GetTransactionsResponse> GetTransactions( string cryptoCode, [ModelBinder(BinderType = typeof(DerivationStrategyModelBinder))] DerivationStrategyBase derivationScheme, [ModelBinder(BinderType = typeof(BitcoinAddressModelBinder))] BitcoinAddress address, [ModelBinder(BinderType = typeof(BookmarksModelBinding))] HashSet <Bookmark> unconfirmedBookmarks = null, [ModelBinder(BinderType = typeof(BookmarksModelBinding))] HashSet <Bookmark> confirmedBookmarks = null, [ModelBinder(BinderType = typeof(BookmarksModelBinding))] HashSet <Bookmark> replacedBookmarks = null, bool includeTransaction = true, bool longPolling = false) { var trackedSource = GetTrackedSource(derivationScheme, address); if (trackedSource == null) { throw new ArgumentNullException(nameof(trackedSource)); } GetTransactionsResponse response = null; using (CancellationTokenSource cts = new CancellationTokenSource()) { if (longPolling) { cts.CancelAfter(LongPollTimeout); } var network = GetNetwork(cryptoCode, false); var chain = ChainProvider.GetChain(network); var repo = RepositoryProvider.GetRepository(network); while (true) { response = new GetTransactionsResponse(); int currentHeight = chain.Height; response.Height = currentHeight; var txs = await GetAnnotatedTransactions(repo, chain, trackedSource); foreach (var item in new[] { new { TxSet = response.ConfirmedTransactions, KnownBookmarks = confirmedBookmarks ?? new HashSet <Bookmark>(), AnnotatedTx = txs.ConfirmedTransactions }, new { TxSet = response.UnconfirmedTransactions, KnownBookmarks = unconfirmedBookmarks ?? new HashSet <Bookmark>(), AnnotatedTx = txs.UnconfirmedTransactions }, new { TxSet = response.ReplacedTransactions, KnownBookmarks = replacedBookmarks ?? new HashSet <Bookmark>(), AnnotatedTx = txs.ReplacedTransactions }, }) { item.TxSet.Bookmark = Bookmark.Start; item.TxSet.KnownBookmark = item.KnownBookmarks.Contains(Bookmark.Start) ? Bookmark.Start : null; BookmarkProcessor processor = new BookmarkProcessor(32 + 32 + 25); foreach (var tx in item.AnnotatedTx) { processor.PushNew(); processor.AddData(tx.Record.TransactionHash); processor.AddData(tx.Record.BlockHash ?? uint256.Zero); processor.UpdateBookmark(); var txInfo = new TransactionInformation() { BlockHash = tx.Height.HasValue ? tx.Record.BlockHash : null, Height = tx.Height, TransactionId = tx.Record.TransactionHash, Transaction = includeTransaction ? tx.Record.Transaction : null, Confirmations = tx.Height.HasValue ? currentHeight - tx.Height.Value + 1 : 0, Timestamp = txs.GetByTxId(tx.Record.TransactionHash).Select(t => t.Record.FirstSeen).First(), Inputs = tx.Record.SpentOutpoints.Select(o => txs.GetUTXO(o)).Where(o => o != null).ToList(), Outputs = tx.Record.GetReceivedOutputs(trackedSource).ToList() }; item.TxSet.Transactions.Add(txInfo); txInfo.BalanceChange = txInfo.Outputs.Select(o => o.Value).Sum() - txInfo.Inputs.Select(o => o.Value).Sum(); item.TxSet.Bookmark = processor.CurrentBookmark; if (item.KnownBookmarks.Contains(processor.CurrentBookmark)) { item.TxSet.KnownBookmark = processor.CurrentBookmark; item.TxSet.Transactions.Clear(); } } } if (!longPolling || response.HasChanges()) { break; } if (!await WaitingTransaction(trackedSource, cts.Token)) { break; } } } return(response); }
public async Task <IActionResult> ConnectWebSocket( string cryptoCode, bool includeTransaction = true, CancellationToken cancellation = default) { if (!HttpContext.WebSockets.IsWebSocketRequest) { return(NotFound()); } GetNetwork(cryptoCode, false); // Internally check if cryptoCode is correct string listenAllDerivationSchemes = null; string listenAllTrackedSource = null; var listenedBlocks = new ConcurrentDictionary <string, string>(); var listenedDerivations = new ConcurrentDictionary <(Network, DerivationStrategyBase), DerivationStrategyBase>(); var listenedTrackedSource = new ConcurrentDictionary <(Network, TrackedSource), TrackedSource>(); WebsocketMessageListener server = new WebsocketMessageListener(await HttpContext.WebSockets.AcceptWebSocketAsync(), _SerializerSettings); CompositeDisposable subscriptions = new CompositeDisposable(); subscriptions.Add(_EventAggregator.Subscribe <Events.NewBlockEvent>(async o => { if (listenedBlocks.ContainsKey(o.CryptoCode)) { var chain = ChainProvider.GetChain(o.CryptoCode); if (chain == null) { return; } var block = chain.GetBlock(o.BlockId); if (block != null) { await server.Send(new Models.NewBlockEvent() { CryptoCode = o.CryptoCode, Hash = block.Hash, Height = block.Height, PreviousBlockHash = block?.Previous }); } } })); subscriptions.Add(_EventAggregator.Subscribe <Events.NewTransactionMatchEvent>(async o => { var network = Waiters.GetWaiter(o.CryptoCode); if (network == null) { return; } bool forward = false; var derivationScheme = (o.TrackedTransaction.TrackedSource as DerivationSchemeTrackedSource)?.DerivationStrategy; if (derivationScheme != null) { forward |= listenAllDerivationSchemes == "*" || listenAllDerivationSchemes == o.CryptoCode || listenedDerivations.ContainsKey((network.Network.NBitcoinNetwork, derivationScheme)); } forward |= listenAllTrackedSource == "*" || listenAllTrackedSource == o.CryptoCode || listenedTrackedSource.ContainsKey((network.Network.NBitcoinNetwork, o.TrackedTransaction.TrackedSource)); if (forward) { var chain = ChainProvider.GetChain(o.CryptoCode); if (chain == null) { return; } var blockHeader = o.BlockId == null ? null : chain.GetBlock(o.BlockId); var derivation = (o.TrackedTransaction.TrackedSource as DerivationSchemeTrackedSource)?.DerivationStrategy; await server.Send(new Models.NewTransactionEvent() { CryptoCode = o.CryptoCode, BlockId = blockHeader?.Hash, TransactionData = Utils.ToTransactionResult(includeTransaction, chain, new[] { o.SavedTransaction }), }.SetMatch(o.TrackedTransaction)); } })); try { while (server.Socket.State == WebSocketState.Open) { object message = await server.NextMessageAsync(cancellation); switch (message) { case Models.NewBlockEventRequest r: r.CryptoCode = r.CryptoCode ?? cryptoCode; listenedBlocks.TryAdd(r.CryptoCode, r.CryptoCode); break; case Models.NewTransactionEventRequest r: var network = Waiters.GetWaiter(r.CryptoCode)?.Network; if (r.DerivationSchemes != null) { r.CryptoCode = r.CryptoCode ?? cryptoCode; if (network != null) { foreach (var derivation in r.DerivationSchemes) { var parsed = new DerivationStrategyFactory(network.NBitcoinNetwork).Parse(derivation); listenedDerivations.TryAdd((network.NBitcoinNetwork, parsed), parsed); } } } else if ( // Back compat: If no derivation scheme precised and ListenAllDerivationSchemes not set, we listen all (r.TrackedSources == null && r.ListenAllDerivationSchemes == null) || (r.ListenAllDerivationSchemes != null && r.ListenAllDerivationSchemes.Value)) { listenAllDerivationSchemes = r.CryptoCode; } if (r.ListenAllTrackedSource != null && r.ListenAllTrackedSource.Value) { listenAllTrackedSource = r.CryptoCode; } else if (r.TrackedSources != null) { r.CryptoCode = r.CryptoCode ?? cryptoCode; if (network != null) { foreach (var trackedSource in r.TrackedSources) { if (TrackedSource.TryParse(trackedSource, out var parsed, network.NBitcoinNetwork)) { listenedTrackedSource.TryAdd((network.NBitcoinNetwork, parsed), parsed); } } } } break; default: break; } } } catch when(server.Socket.State != WebSocketState.Open) { } finally { subscriptions.Dispose(); await server.DisposeAsync(cancellation); } return(new EmptyResult()); }