diff --git a/BTCPayServer.Tests/UnitTests/LndTest.cs b/BTCPayServer.Tests/UnitTests/LndTest.cs index 2433c39f9..cae4f3216 100644 --- a/BTCPayServer.Tests/UnitTests/LndTest.cs +++ b/BTCPayServer.Tests/UnitTests/LndTest.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; +using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning.Lnd; using NBitcoin; +using NBitcoin.RPC; using Xunit; using Xunit.Abstractions; @@ -16,31 +18,96 @@ namespace BTCPayServer.Tests.UnitTests public LndTest(ITestOutputHelper output) { this.output = output; + initializeEnvironment(); + + LndRpc = LndSwaggerClientCustomHttp.Create(new Uri("http://localhost:53280"), Network.RegTest); + InvoiceClient = new LndClient(LndRpc); } - private LndClient Client - { - get - { - var lnd = new LndClient(new Uri("http://localhost:53280"), Network.RegTest); - return lnd; - } - } + private LndSwaggerClientCustomHttp LndRpc { get; set; } + private LndClient InvoiceClient { get; set; } [Fact] public async Task GetInfo() { - var res = await Client.GetInfo(); - + var res = await InvoiceClient.GetInfo(); output.WriteLine("Result: " + res.ToJson()); } [Fact] public async Task CreateInvoice() { - var res = await Client.CreateInvoice(10000, "Hello world", TimeSpan.FromSeconds(3600)); - + var res = await InvoiceClient.CreateInvoice(10000, "Hello world", TimeSpan.FromSeconds(3600)); output.WriteLine("Result: " + res.ToJson()); } + + [Fact] + public async Task GetInvoice() + { + var createInvoice = await InvoiceClient.CreateInvoice(10000, "Hello world", TimeSpan.FromSeconds(3600)); + var getInvoice = await InvoiceClient.GetInvoice(createInvoice.Id); + + Assert.Equal(createInvoice.BOLT11, getInvoice.BOLT11); + } + + + + [Fact] + public async Task SetupWalletForPayment() + { + var nodeInfo = GetInfo(); + var addressResponse = await LndRpc.NewWitnessAddressAsync(); + var address = BitcoinAddress.Create(addressResponse.Address, Network.RegTest); + await ExplorerNode.SendToAddressAsync(address, Money.Coins(0.2m)); + ExplorerNode.Generate(1); + await WaitLNSynched(); + await Task.Delay(1000); + + // We need two instances of lnd... one for merchant, one for buyer + // prepare that in next commit + //var channelReq = new LnrpcOpenChannelRequest + //{ + // Local_funding_amount = 16777215.ToString() + //}; + //var channelResp = await LndRpc.OpenChannelSyncAsync(channelReq); + + output.WriteLine("Wallet Address: " + address); + } + + private async Task WaitLNSynched() + { + while (true) + { + var merchantInfo = await InvoiceClient.GetInfo(); + var blockCount = await ExplorerNode.GetBlockCountAsync(); + if (merchantInfo.BlockHeight != blockCount) + { + await Task.Delay(1000); + } + else + { + return merchantInfo; + } + } + } + + + + + // + private void initializeEnvironment() + { + NetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest); + ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_BTCRPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork("BTC").NBitcoinNetwork); + } + + public BTCPayNetworkProvider NetworkProvider { get; private set; } + public RPCClient ExplorerNode { get; set; } + + internal string GetEnvironment(string variable, string defaultValue) + { + var var = Environment.GetEnvironmentVariable(variable); + return String.IsNullOrEmpty(var) ? defaultValue : var; + } } } diff --git a/BTCPayServer/Payments/Lightning/Lnd/LndClient.cs b/BTCPayServer/Payments/Lightning/Lnd/LndClient.cs index 0358a4007..0b9e6207d 100644 --- a/BTCPayServer/Payments/Lightning/Lnd/LndClient.cs +++ b/BTCPayServer/Payments/Lightning/Lnd/LndClient.cs @@ -17,18 +17,13 @@ namespace BTCPayServer.Payments.Lightning.Lnd { public class LndClient : ILightningInvoiceClient, ILightningListenInvoiceSession { - public LndClient(Uri uri, Network network, byte[] tlsCertificate = null, byte[] grpcMacaroon = null) + public LndSwaggerClient _Decorator; + + public LndClient(LndSwaggerClient decorator) { - // for now working with custom build of lnd that has no macaroons and is on http - //_HttpClient = HttpClientFactoryForLnd.Generate(tlsCertificate, grpcMacaroon); - - _HttpClient = new HttpClient(); - _Decorator = new LndSwaggerClient(uri.ToString().TrimEnd('/'), _HttpClient); + _Decorator = decorator; } - private HttpClient _HttpClient; - private LndSwaggerClient _Decorator; - public async Task CreateInvoice(LightMoney amount, string description, TimeSpan expiry, CancellationToken cancellation = default(CancellationToken)) { @@ -84,11 +79,8 @@ namespace BTCPayServer.Payments.Lightning.Lnd } - public void Dispose() - { - _HttpClient?.Dispose(); - } + // utility static methods... maybe move to separate class private static string BitString(byte[] bytes) { return BitConverter.ToString(bytes) @@ -123,6 +115,11 @@ namespace BTCPayServer.Payments.Lightning.Lnd return invoice; } + public void Dispose() + { + throw new NotImplementedException(); + } + // Invariant culture conversion public static class ConvertInv { @@ -143,53 +140,7 @@ namespace BTCPayServer.Payments.Lightning.Lnd } } - internal class HttpClientFactoryForLnd - { - internal static HttpClient Generate(byte[] tlsCertificate, byte[] grpcMacaroon) - { - var httpClient = new HttpClient(GetCertificate(tlsCertificate)); - var macaroonHex = BitConverter.ToString(grpcMacaroon).Replace("-", "", StringComparison.InvariantCulture); - httpClient.DefaultRequestHeaders.Add("Grpc-Metadata-macaroon", macaroonHex); - - return httpClient; - } - - private static HttpClientHandler GetCertificate(byte[] certFile) - { - X509Certificate2 clientCertificate = null; - if (certFile != null) - clientCertificate = new X509Certificate2(certFile); - - var handler = new HttpClientHandler - { - SslProtocols = SslProtocols.Tls12 - }; - - handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => - { - const SslPolicyErrors unforgivableErrors = - SslPolicyErrors.RemoteCertificateNotAvailable | - SslPolicyErrors.RemoteCertificateNameMismatch; - - if ((errors & unforgivableErrors) != 0) - { - return false; - } - - if (clientCertificate == null) - return true; - - X509Certificate2 remoteRoot = chain.ChainElements[chain.ChainElements.Count - 1].Certificate; - var res = clientCertificate.RawData.SequenceEqual(remoteRoot.RawData); - - return res; - }; - - return handler; - } - } - - partial class LndSwaggerClient + public partial class LndSwaggerClient { } } diff --git a/BTCPayServer/Payments/Lightning/Lnd/LndSwaggerClientCustomHttp.cs b/BTCPayServer/Payments/Lightning/Lnd/LndSwaggerClientCustomHttp.cs new file mode 100644 index 000000000..ed5cee152 --- /dev/null +++ b/BTCPayServer/Payments/Lightning/Lnd/LndSwaggerClientCustomHttp.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Security; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using NBitcoin; + +namespace BTCPayServer.Payments.Lightning.Lnd +{ + public class LndSwaggerClientCustomHttp : LndSwaggerClient, IDisposable + { + public LndSwaggerClientCustomHttp(string baseUrl, HttpClient httpClient) + : base(baseUrl, httpClient) + { + _HttpClient = httpClient; + } + + private HttpClient _HttpClient; + + public void Dispose() + { + _HttpClient.Dispose(); + } + + // + public static LndSwaggerClientCustomHttp Create(Uri uri, Network network, byte[] tlsCertificate = null, byte[] grpcMacaroon = null) + { + // for now working with custom build of lnd that has no macaroons and is on http + //_HttpClient = HttpClientFactoryForLnd.Generate(tlsCertificate, grpcMacaroon); + var httpClient = new HttpClient(); + return new LndSwaggerClientCustomHttp(uri.ToString().TrimEnd('/'), httpClient); + } + } + + internal class HttpClientFactoryForLnd + { + internal static HttpClient Generate(byte[] tlsCertificate, byte[] grpcMacaroon) + { + var httpClient = new HttpClient(GetCertificate(tlsCertificate)); + var macaroonHex = BitConverter.ToString(grpcMacaroon).Replace("-", "", StringComparison.InvariantCulture); + httpClient.DefaultRequestHeaders.Add("Grpc-Metadata-macaroon", macaroonHex); + + return httpClient; + } + + private static HttpClientHandler GetCertificate(byte[] certFile) + { + X509Certificate2 clientCertificate = null; + if (certFile != null) + clientCertificate = new X509Certificate2(certFile); + + var handler = new HttpClientHandler + { + SslProtocols = SslProtocols.Tls12 + }; + + handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => + { + const SslPolicyErrors unforgivableErrors = + SslPolicyErrors.RemoteCertificateNotAvailable | + SslPolicyErrors.RemoteCertificateNameMismatch; + + if ((errors & unforgivableErrors) != 0) + { + return false; + } + + if (clientCertificate == null) + return true; + + X509Certificate2 remoteRoot = chain.ChainElements[chain.ChainElements.Count - 1].Certificate; + var res = clientCertificate.RawData.SequenceEqual(remoteRoot.RawData); + + return res; + }; + + return handler; + } + } +}