Listen / WaitInvoice for Lnd

This commit is contained in:
rockstardev 2018-05-31 15:08:22 -05:00
parent 431147784e
commit ce9189caf8
3 changed files with 95 additions and 19 deletions

View File

@ -58,8 +58,26 @@ namespace BTCPayServer.Tests.Lnd
Assert.Equal(createInvoice.BOLT11, getInvoice.BOLT11);
}
// integration tests
[Fact]
public async Task TestWaitListenInvoice()
{
var merchantInvoice = await InvoiceClient.CreateInvoice(10000, "Hello world", TimeSpan.FromSeconds(3600));
//integration tests
var waitToken = default(CancellationToken);
var listener = await InvoiceClient.Listen(waitToken);
var waitTask = listener.WaitInvoice(waitToken);
await EnsureLightningChannelAsync();
var payResponse = await CustomerLnd.SendPaymentSyncAsync(new LnrpcSendRequest
{
Payment_request = merchantInvoice.BOLT11
});
var invoice = await waitTask;
Assert.True(invoice.PaidAt.HasValue);
}
[Fact]
public async Task CreateLndInvoiceAndPay()

View File

@ -70,21 +70,18 @@ namespace BTCPayServer.Payments.Lightning.Lnd
var resp = await _rpcClient.LookupInvoiceAsync(invoiceId, null, cancellation);
return ConvertLndInvoice(resp);
}
// TODO: These two methods where you wait on invoice are still work in progress
public Task<ILightningListenInvoiceSession> Listen(CancellationToken cancellation = default(CancellationToken))
{
throw new NotImplementedException();
//return Task.FromResult<ILightningListenInvoiceSession>(this);
Task.Run(_rpcClient.StartSubscribeInvoiceThread);
return Task.FromResult<ILightningListenInvoiceSession>(this);
}
async Task<LightningInvoice> ILightningListenInvoiceSession.WaitInvoice(CancellationToken cancellation)
{
throw new NotImplementedException();
//var resp = await _rpcClient.SubscribeInvoicesAsync(cancellation);
//return ConvertLndInvoice(resp);
var resp = await _rpcClient.InvoiceResponse.Task;
return ConvertLndInvoice(resp);
}
// Eof work in progress
// utility static methods... maybe move to separate class

View File

@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NBitcoin;
@ -12,7 +16,7 @@ namespace BTCPayServer.Payments.Lightning.Lnd
{
public class LndSwaggerClientCustomHttp : LndSwaggerClient, IDisposable
{
public LndSwaggerClientCustomHttp(string baseUrl, HttpClient httpClient)
protected LndSwaggerClientCustomHttp(string baseUrl, HttpClient httpClient)
: base(baseUrl, httpClient)
{
_HttpClient = httpClient;
@ -28,23 +32,36 @@ namespace BTCPayServer.Payments.Lightning.Lnd
//
public static LndSwaggerClientCustomHttp Create(Uri uri, Network network, byte[] tlsCertificate = null, byte[] grpcMacaroon = null)
{
// for development we are working with custom build of lnd that allows no macaroons and http
var clientWithNoMacaroonsTls = tlsCertificate == null || grpcMacaroon == null;
var factory = new HttpClientFactoryForLnd(tlsCertificate, grpcMacaroon);
var httpClient = factory.Generate();
var httpClient = clientWithNoMacaroonsTls ? new HttpClient() :
HttpClientFactoryForLnd.Generate(tlsCertificate, grpcMacaroon);
var swagger = new LndSwaggerClientCustomHttp(uri.ToString().TrimEnd('/'), httpClient);
swagger.HttpClientFactory = factory;
return new LndSwaggerClientCustomHttp(uri.ToString().TrimEnd('/'), httpClient);
return swagger;
}
}
internal class HttpClientFactoryForLnd
{
internal static HttpClient Generate(byte[] tlsCertificate, byte[] grpcMacaroon)
public HttpClientFactoryForLnd(byte[] tlsCertificate = null, byte[] grpcMacaroon = null)
{
var httpClient = new HttpClient(GetCertificate(tlsCertificate));
var macaroonHex = BitConverter.ToString(grpcMacaroon).Replace("-", "", StringComparison.InvariantCulture);
httpClient.DefaultRequestHeaders.Add("Grpc-Metadata-macaroon", macaroonHex);
TlsCertificate = tlsCertificate;
GrpcMacaroon = grpcMacaroon;
}
public byte[] TlsCertificate { get; set; }
public byte[] GrpcMacaroon { get; set; }
public HttpClient Generate()
{
var httpClient = new HttpClient(GetCertificate(TlsCertificate));
if (GrpcMacaroon != null)
{
var macaroonHex = BitConverter.ToString(GrpcMacaroon).Replace("-", "", StringComparison.InvariantCulture);
httpClient.DefaultRequestHeaders.Add("Grpc-Metadata-macaroon", macaroonHex);
}
return httpClient;
}
@ -92,5 +109,49 @@ namespace BTCPayServer.Payments.Lightning.Lnd
public partial class LndSwaggerClient
{
internal HttpClientFactoryForLnd HttpClientFactory { get; set; }
public TaskCompletionSource<LnrpcInvoice> InvoiceResponse = new TaskCompletionSource<LnrpcInvoice>();
public TaskCompletionSource<LndSwaggerClient> SubscribeLost = new TaskCompletionSource<LndSwaggerClient>();
// TODO: Refactor swagger generated wrapper to include this method directly
public async Task StartSubscribeInvoiceThread()
{
var urlBuilder = new StringBuilder();
urlBuilder.Append(BaseUrl).Append("/v1/invoices/subscribe");
using (var client = HttpClientFactory.Generate())
{
client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);
var request = new HttpRequestMessage(HttpMethod.Get, urlBuilder.ToString());
using (var response = await client.SendAsync(
request,
HttpCompletionOption.ResponseHeadersRead))
{
using (var body = await response.Content.ReadAsStreamAsync())
using (var reader = new StreamReader(body))
{
try
{
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
LnrpcInvoice parsedInvoice = Newtonsoft.Json.JsonConvert.DeserializeObject<LnrpcInvoice>(line, _settings.Value);
InvoiceResponse?.SetResult(parsedInvoice);
}
}
catch (Exception e)
{
// TODO: check that the exception type is actually from a closed stream.
Debug.WriteLine(e.Message);
SubscribeLost?.SetResult(this);
}
}
}
}
}
}
}