private async Task HandleOutboundConnectQueue()
        {
            var t             = _channelProvider.GetOutboundConnectionRequestQueue(_network.CryptoCode);
            var chanMan       = _peerManagerProvider.GetPeerManager(_network).ChannelManager;
            var knownChannels = chanMan.ListChannels(_pool);

            while (await t.Reader.WaitToReadAsync())
            {
                var req = await t.Reader.ReadAsync();

                try
                {
                    await _p2PConnectionHandler.NewOutbound(req.EndPoint, req.NodeId).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex.ToString());
                    _logger.LogError($"Failed to resume the connection for {req}. Force-closing the channel.");
                    var channelDetail = knownChannels.First(c => c.RemoteNetworkId.Equals(req.NodeId));
                    chanMan.ForceCloseChannel(channelDetail.ChannelId);
                }
            }
        }
        private async Task HandleEvent(Event e, CancellationToken cancellationToken)
        {
            _logger.LogDebug($"Handling event {e}");
            _eventAggregator.Publish(e);
            var peerManager = _peerManagerProvider.GetPeerManager(_network);
            var chanMan     = peerManager.ChannelManager;

            if (e is Event.FundingGenerationReady f)
            {
                var witScriptId   = PayToWitScriptHashTemplate.Instance.ExtractScriptPubKeyParameters(f.Item.OutputScript) ?? Infrastructure.Utils.Utils.Fail <WitScriptId>($"Failed to extract wit script from {f.Item.OutputScript.ToHex()}! this should never happen.");
                var outputAddress = witScriptId.GetAddress(_network.NBitcoinNetwork);
                var tx            = await _walletService.GetSendingTxAsync(outputAddress, f.Item.ChannelValueSatoshis, _network, cancellationToken);

                var nOut =
                    tx.Outputs.Select((txo, i) => (txo, i))
                    .First(item => item.txo.ScriptPubKey == outputAddress.ScriptPubKey).i;
                var fundingTxo = new OutPoint(tx.GetHash(), nOut);
                Debug.Assert(_pendingFundingTx.TryAdd(tx.GetHash(), tx));
                _logger.LogDebug($"Finished creating funding tx {tx.ToHex()}; id: {tx.GetHash()}");

                chanMan.FundingTransactionGenerated(f.Item.TemporaryChannelId.Value, fundingTxo);
                peerManager.ProcessEvents();
            }
            else if (e is Event.FundingBroadcastSafe fundingBroadcastSafe)
            {
                var h = fundingBroadcastSafe.Item.OutPoint.Item.Hash;
                if (!_pendingFundingTx.TryGetValue(h, out var fundingTx))
                {
                    _logger.LogCritical($"RL asked us to broadcast unknown funding tx for id: ({h})! this should never happen.");
                    return;
                }
                await _walletService.BroadcastAsync(fundingTx, _network).ConfigureAwait(false);
            }
            else if (e is Event.PaymentReceived paymentReceived)
            {
                var a      = paymentReceived.Item.Amount;
                var hash   = paymentReceived.Item.PaymentHash;
                var secret = paymentReceived.Item.PaymentSecret.GetOrDefault();
                var(result, intendedAmount) = await _invoiceService.PaymentReceived(hash, a, secret);

                _logger.LogDebug($"Received payment of type {result}. Payment hash {hash}. PaymentSecret: {secret}");
                if (result == PaymentReceivedType.UnknownPaymentHash)
                {
                    _logger.LogError($"Received payment for unknown payment_hash({hash}). ignoring.");
                    return;
                }
                var preImage = await _repository.GetPreimage(hash);

                _logger.LogDebug($"preimage {preImage.ToHex()}");
                if (result == PaymentReceivedType.Ok)
                {
                    if (chanMan.ClaimFunds(preImage, secret, (ulong)intendedAmount.MilliSatoshi))
                    {
                        peerManager.ProcessEvents();
                    }
                }
                else
                {
                    if (chanMan.FailHTLCBackwards(hash, secret))
                    {
                        peerManager.ProcessEvents();
                    }
                    else
                    {
                        _logger.LogCritical($"RL told us that preimage ({preImage}) and/or its hash ({hash}) is unknown to us. This should never happen");
                    }
                }
            }
            else if (e is Event.PaymentSent paymentSent)
            {
                await _repository.SetPreimage(paymentSent.Item);
            }
            else if (e is Event.PaymentFailed paymentFailed)
            {
                _logger.LogInformation($"payment failed: {paymentFailed.Item.PaymentHash}" + (paymentFailed.Item.RejectedByDest ? "rejected by destination" : ""));
            }
            else if (e is Event.PendingHTLCsForwardable pendingHtlCsForwardable)
            {
#if DEBUG
                var wait = TimeSpan.Zero;
#else
                var wait = pendingHtlCsForwardable.Item.TimeToWait(_random);
#endif
                await Task.Delay(wait, cancellationToken);

                peerManager.ChannelManager.ProcessPendingHTLCForwards();
                peerManager.ProcessEvents();
            }
            else if (e is Event.SpendableOutputs spendableOutputs)
            {
                var chan = _channelProvider.GetSpendableOutputDescriptorChannel(_network.CryptoCode);
                foreach (var o in spendableOutputs.Item)
                {
                    _logger.LogInformation($"New spendable on-chain funds txid: {o.OutPoint.Item.Hash}. vout: {o.OutPoint.Item.N}");
                    await chan.Writer.WriteAsync(o, cancellationToken);
                }
            }
            else
            {
                throw new Exception("Unreachable!");
            }
        }
예제 #3
0
        public async Task PayInvoice(PaymentRequest invoice, long?amountMSat = null, CancellationToken ct = default)
        {
            var amount = invoice.AmountValue?.Value.MilliSatoshi ?? amountMSat;

            if (amount is null)
            {
                throw new NRustLightningException($"You must specify payment amount if it is not included in invoice");
            }

            var n = _networkProvider.TryGetByInvoice(invoice);

            if (n is null)
            {
                throw new NRustLightningException($"Unknown invoice prefix {invoice.PrefixValue}");
            }
            var peerMan    = _peerManagerProvider.GetPeerManager(n);
            var failureTcs = new TaskCompletionSource <Event>(TaskCreationOptions.RunContinuationsAsynchronously);
            var successTcs = new TaskCompletionSource <Event>(TaskCreationOptions.RunContinuationsAsynchronously);

            _eventAggregator.Subscribe <Event>(e =>
            {
                if (e is Event.PaymentFailed paymentF && paymentF.Item.PaymentHash.Equals(invoice.PaymentHash))
                {
                    _logger.LogError($"Payment for invoice ({invoice}) failed");
                    failureTcs.SetResult(paymentF);
                }

                if (e is Event.PaymentSent paymentS && paymentS.Item.Hash.Equals(invoice.PaymentHash))
                {
                    _logger.LogInformation($"Payment for invoice ({invoice}) succeed");
                    successTcs.SetResult(paymentS);
                }
            });
            peerMan.SendPayment(invoice.NodeIdValue.Item, invoice.PaymentHash, new List <RouteHint>(), LNMoney.MilliSatoshis(amount.Value), invoice.MinFinalCLTVExpiryDelta, _pool, invoice.PaymentSecret.Value);
            peerMan.ProcessEvents();

            // case 1: canceled by user.
            var cancelTcs = new TaskCompletionSource <object>(TaskCreationOptions.RunContinuationsAsynchronously);

            await using var _ = (ct.Register(state => { ((TaskCompletionSource <object>)state).TrySetResult(null); },
                                             cancelTcs));
            var cancelTask = cancelTcs.Task;

            // case 2: timeout.
            using var cts = new CancellationTokenSource();
            var timeoutTask = Task.Delay(TimeSpan.FromSeconds(_config.Value.PaymentTimeoutSec), cts.Token);

            // case 3: finishes
            var task = Task.WhenAny(failureTcs.Task, successTcs.Task);

            var resultTask = await Task.WhenAny(cancelTask, timeoutTask, task);

            if (resultTask == cancelTask)
            {
                throw new NRustLightningException($"Payment canceled.");
            }
            if (resultTask == timeoutTask)
            {
                throw new NRustLightningException($"Payment for {invoice} did not finish in {_config.Value.PaymentTimeoutSec} seconds");
            }
            cts.Cancel(); // cancel the timer task so it does not fire.

            var resultEvent = await await task;

            if (resultEvent is Event.PaymentFailed paymentFailed)
            {
                if (paymentFailed.Item.RejectedByDest)
                {
                    throw new NRustLightningException($"Failed to pay! rejected by destination");
                }
                throw new NRustLightningException($"Failed to pay!");
            }

            if (resultEvent is Event.PaymentSent paymentSent)
            {
                await _repository.SetPreimage(paymentSent.Item, ct);
            }
        }
        private async Task ListenToSessionLoop(LongPollingNotificationSession session, CancellationToken stoppingToken)
        {
            var client  = session.Client;
            var peerMan = _peerManagerProvider.GetPeerManager(_network);

            Debug.Assert(_peerManagerProvider.CurrentHeightsInStartup.TryGetValue(_network, out var startupHeight));
            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    var events =
                        (await session.GetEventsAsync(lastEventId, 10000, true, stoppingToken))
                        .Where(e => e.EventType == "newblock")
                        .Select(e => (NewBlockEvent)e)
                        .Where(e => e.Height > startupHeight - 10) // for the sake of speed.
                        .OrderBy(e => e.Height);

                    foreach (var e in events)
                    {
                        var newBlock = await client.RPCClient.GetBlockAsync(e.Hash).ConfigureAwait(false);

                        peerMan.BlockNotifier.BlockConnected(newBlock, (uint)e.Height);
                        lastEventId = e.EventId > lastEventId ? e.EventId : lastEventId;
                    }

                    try
                    {
                        var h = await _explorerClient.GetFeeRateAsync(FeeRateSet.HighPriorityBlockCount, stoppingToken);

                        var n = await _explorerClient.GetFeeRateAsync(FeeRateSet.NormalBlockCount, stoppingToken);

                        var b = await _explorerClient.GetFeeRateAsync(FeeRateSet.BackgroundBlockCount, stoppingToken);

                        _feeRateWriter.TryWrite(new FeeRateSet()
                        {
                            HighPriority = h.FeeRate, Normal = n.FeeRate, Background = b.FeeRate
                        });
                    }
                    catch (NBXplorerException ex)
                    {
                        _logger.LogError($"Failed to estimate fee by nbxplorer: \"{ex.Message}\"");
                    }

                    // nbx does not return blocks aligned with eventId(i.e. sometimes block height will decrease when
                    // eventId increase. event if there are no forks. This is especially true in regtest, where many blocks
                    // are generated at once.)
                    // so to get the blocks in batch and to sort it by its height in our side, we will limit our query
                    // frequency by waiting in here.
                    await Task.Delay(6000, stoppingToken);
                }
                catch (HttpRequestException ex) when(ex.InnerException is TimeoutException)
                {
                }
                catch (OperationCanceledException ex)
                {
                    if (stoppingToken.IsCancellationRequested)
                    {
                        break;
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError($"Failed to listen on nbx {ex}");
                    break;
                }
            }

            _logger.LogInformation($"Shutting down nbx listener session loop...");
        }