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!"); } }
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..."); }