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>(); var successTcs = new TaskCompletionSource <Event>(); _eventAggregator.Subscribe <Event>(e => { if (e is Event.PaymentFailed paymentFailed && paymentFailed.Item.PaymentHash.Equals(invoice.PaymentHash)) { _logger.LogError($"Payment for invoice ({invoice}) failed"); failureTcs.SetResult(paymentFailed); } if (e is Event.PaymentSent paymentSent && paymentSent.Item.Hash.Equals(invoice.PaymentHash)) { _logger.LogInformation($"Payment for invoice ({invoice}) succeed"); successTcs.SetResult(paymentSent); } }); peerMan.SendPayment(invoice.NodeIdValue.Item, invoice.PaymentHash, new List <RouteHint>(), LNMoney.MilliSatoshis(amount.Value), invoice.MinFinalCLTVExpiryDelta); peerMan.ProcessEvents(); var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); cts.CancelAfter(TimeSpan.FromSeconds(_config.Value.PaymentTimeoutSec)); var delay = Task.Delay(TimeSpan.FromMilliseconds(-1), cts.Token); var task = Task.WhenAny(failureTcs.Task, successTcs.Task); await Task.WhenAny(task, delay); if (cts.IsCancellationRequested) { throw new NRustLightningException($"Payment for {invoice} did not finish in {_config.Value.PaymentTimeoutSec} seconds"); } 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 _invoiceRepository.SetPreimage(paymentSent.Item); } }
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); } }