Beispiel #1
0
        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);
        }
 public BrokerHostedService(BitcoinDWaiters waiters, ChainProvider chainProvider, EventAggregator eventAggregator, IOptions <ExplorerConfiguration> config, MvcNewtonsoftJsonOptions jsonOptions)
 {
     _EventAggregator    = eventAggregator;
     ChainProvider       = chainProvider;
     Waiters             = waiters;
     _config             = config.Value;
     _serializerSettings = jsonOptions.SerializerSettings;
 }
 public BrokerHostedService(BitcoinDWaitersAccessor waiters, ChainProvider chainProvider, EventAggregator eventAggregator, IOptions <ExplorerConfiguration> config, IOptions <MvcJsonOptions> jsonOptions)
 {
     _EventAggregator    = eventAggregator;
     ChainProvider       = chainProvider;
     Waiters             = waiters.Instance;
     _config             = config.Value;
     _serializerSettings = jsonOptions.Value.SerializerSettings;
 }
Beispiel #5
0
 public BrokerHostedService(BitcoinDWaiters waiters, ChainProvider chainProvider, EventAggregator eventAggregator, IOptions <ExplorerConfiguration> config, NBXplorerNetworkProvider networks)
 {
     _EventAggregator = eventAggregator;
     Networks         = networks;
     ChainProvider    = chainProvider;
     Waiters          = waiters;
     _config          = config.Value;
 }
        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;
            }
        }
Beispiel #7
0
 public MainController(
     RepositoryProvider repositoryProvider,
     ChainProvider chainProvider,
     EventAggregator eventAggregator,
     BitcoinDWaitersAccessor waiters,
     IOptions <MvcJsonOptions> jsonOptions)
 {
     RepositoryProvider  = repositoryProvider;
     ChainProvider       = chainProvider;
     _SerializerSettings = jsonOptions.Value.SerializerSettings;
     _EventAggregator    = eventAggregator;
     Waiters             = waiters.Instance;
 }
Beispiel #8
0
 public MainController(
     ExplorerConfiguration explorerConfiguration,
     RepositoryProvider repositoryProvider,
     ChainProvider chainProvider,
     EventAggregator eventAggregator,
     BitcoinDWaitersAccessor waiters,
     AddressPoolServiceAccessor addressPoolService,
     ScanUTXOSetServiceAccessor scanUTXOSetService,
     IOptions <MvcJsonOptions> jsonOptions)
 {
     ExplorerConfiguration = explorerConfiguration;
     RepositoryProvider    = repositoryProvider;
     ChainProvider         = chainProvider;
     _SerializerSettings   = jsonOptions.Value.SerializerSettings;
     _EventAggregator      = eventAggregator;
     ScanUTXOSetService    = scanUTXOSetService.Instance;
     Waiters            = waiters.Instance;
     AddressPoolService = addressPoolService.Instance;
 }
Beispiel #9
0
 public MainController(
     ExplorerConfiguration explorerConfiguration,
     RepositoryProvider repositoryProvider,
     ChainProvider chainProvider,
     EventAggregator eventAggregator,
     BitcoinDWaiters waiters,
     AddressPoolServiceAccessor addressPoolService,
     ScanUTXOSetServiceAccessor scanUTXOSetService,
     RebroadcasterHostedService rebroadcaster,
     KeyPathTemplates keyPathTemplates,
     IOptions <MvcJsonOptions> jsonOptions)
 {
     ExplorerConfiguration = explorerConfiguration;
     RepositoryProvider    = repositoryProvider;
     ChainProvider         = chainProvider;
     _SerializerSettings   = jsonOptions.Value.SerializerSettings;
     _EventAggregator      = eventAggregator;
     ScanUTXOSetService    = scanUTXOSetService.Instance;
     Waiters               = waiters;
     Rebroadcaster         = rebroadcaster;
     this.keyPathTemplates = keyPathTemplates;
     AddressPoolService    = addressPoolService.Instance;
 }
Beispiel #10
0
        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));
            }
        }
Beispiel #11
0
        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));
        }
Beispiel #12
0
        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));
        }
Beispiel #13
0
        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);
        }
Beispiel #14
0
        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());
        }