Skip to content

Commit

Permalink
xunit style Postgres backing
Browse files Browse the repository at this point in the history
  • Loading branch information
rsafier committed Mar 25, 2024
1 parent 43b0466 commit c9f9311
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 4 deletions.
224 changes: 224 additions & 0 deletions LNUnit.Tests/Fixture/PostgresLightningFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
using System.Net;
using System.Net.Sockets;
using Docker.DotNet;
using Docker.DotNet.Models;
using LNUnit.Setup;
using Npgsql;
using ServiceStack;
using Xunit;
using Assert = NUnit.Framework.Assert;
using HostConfig = Docker.DotNet.Models.HostConfig;

namespace LNUnit.Fixtures;

// ReSharper disable once ClassNeverInstantiated.Global
public class PostgresLightningFixture : IDisposable
{
public string DbContainerName { get; set; }= "postgres";
private readonly DockerClient _client = new DockerClientConfiguration().CreateClient();
private string _containerId;
private string _ip;

public PostgresLightningFixture()
{
StartPostgres().Wait();
AddDb("alice");
AddDb("bob");
AddDb("carol");
SetupNetwork().Wait();
}

private void AddDb(string dbName)
{
using (NpgsqlConnection connection = new(DbConnectionString))
{
connection.Open();
using var checkIfExistsCommand = new NpgsqlCommand($"SELECT 1 FROM pg_catalog.pg_database WHERE datname = '{dbName}'", connection);
var result = checkIfExistsCommand.ExecuteScalar();

if (result == null)
{

using var command = new NpgsqlCommand($"CREATE DATABASE \"{dbName}\"", connection);
command.ExecuteNonQuery();
}
}
LNDConnectionStrings.Add(dbName,
$"postgresql://superuser:superuser@{_ip}:5432/{dbName}?sslmode=disable");
}

public string DbConnectionStringLND { get; private set; }
public string DbConnectionString { get; private set; }

public Dictionary<string, string> LNDConnectionStrings = new();
public void Dispose()
{
GC.SuppressFinalize(this);

// Remove containers
RemoveContainer(DbContainerName).Wait();
RemoveContainer("miner").Wait();
RemoveContainer("alice").Wait();
RemoveContainer("bob").Wait();
RemoveContainer("carol").Wait();

Builder?.Destroy();
_client.Dispose();
}

public LNUnitBuilder? Builder { get; private set; }


public async Task SetupNetwork()
{
await RemoveContainer("miner");
await RemoveContainer("alice");
await RemoveContainer("bob");
await RemoveContainer("carol");

await _client.CreateDockerImageFromPath("../../../../Docker/lnd", ["custom_lnd", "custom_lnd:latest"]);
Builder = new LNUnitBuilder();

Builder.AddBitcoinCoreNode();

Builder.AddPolarLNDNode("alice",
[
new()
{
ChannelSize = 10_000_000, //10MSat
RemoteName = "bob"
}
], imageName: "custom_lnd", tagName: "latest", pullImage: false,postgresDSN: LNDConnectionStrings["alice"]);

Builder.AddPolarLNDNode("bob",
[
new()
{
ChannelSize = 10_000_000, //10MSat
RemotePushOnStart = 1_000_000, // 1MSat
RemoteName = "alice"
}
], imageName: "custom_lnd", tagName: "latest", pullImage: false,postgresDSN: LNDConnectionStrings["bob"]);

Builder.AddPolarLNDNode("carol",
[
new()
{
ChannelSize = 10_000_000, //10MSat
RemotePushOnStart = 1_000_000, // 1MSat
RemoteName = "alice"
},
new()
{
ChannelSize = 10_000_000, //10MSat
RemotePushOnStart = 1_000_000, // 1MSat
RemoteName = "bob"
}
], imageName: "custom_lnd", tagName: "latest", pullImage: false,postgresDSN: LNDConnectionStrings["carol"]);

await Builder.Build();
}

public async Task StartPostgres()
{
await _client.PullImageAndWaitForCompleted("postgres", "16.2-alpine");
await RemoveContainer(DbContainerName);
var nodeContainer = await _client.Containers.CreateContainerAsync(new CreateContainerParameters
{
Image = "postgres:16.2-alpine",
HostConfig = new HostConfig
{
NetworkMode = "bridge"
},
Name = $"{DbContainerName}",
Hostname = $"{DbContainerName}",
Env =
[
"POSTGRES_PASSWORD=superuser",
"POSTGRES_USER=superuser",
"POSTGRES_DB=postgres"
]
});
Assert.NotNull(nodeContainer);
_containerId = nodeContainer.ID;
var started = await _client.Containers.StartContainerAsync(_containerId, new ContainerStartParameters());

//Build connection string
var ipAddressReady = false;
while (!ipAddressReady)
{
var listContainers = await _client.Containers.ListContainersAsync(new ContainersListParameters());

var db = listContainers.FirstOrDefault(x => x.ID == nodeContainer.ID);
if (db != null)
{
_ip = db.NetworkSettings.Networks.First().Value.IPAddress;
DbConnectionString = $"Host={_ip};Database=postgres;Username=superuser;Password=superuser";
ipAddressReady = true;
}
else
{
await Task.Delay(100);
}

}
//wait for TCP socket to open
var tcpConnectable = false;
while (!tcpConnectable)
{
try
{
TcpClient c = new()
{
ReceiveTimeout = 1,
SendTimeout = 1
};
await c.ConnectAsync(new IPEndPoint(IPAddress.Parse(_ip), 5432));
if (c.Connected)
{
tcpConnectable = true;
}
}
catch (Exception e)
{
await Task.Delay(50);
}
}
}

private async Task RemoveContainer(string name)
{
try
{
await _client.Containers.RemoveContainerAsync(name,
new ContainerRemoveParameters { Force = true, RemoveVolumes = true });
}
catch
{
// ignored
}
}

public async Task<bool> IsRunning()
{
try
{
var inspect = await _client.Containers.InspectContainerAsync(DbContainerName);
return inspect.State.Running;
}
catch
{
// ignored
}

return false;
}
}

[CollectionDefinition("postgres")]
public class PostgresLightningFixtureCollection : ICollectionFixture<PostgresLightningFixture>
{
// This class has no code, and is never created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
}
2 changes: 2 additions & 0 deletions LNUnit.Tests/LNUnit.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<PackageReference Include="JunitXml.TestLogger" Version="3.0.134" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.0"/>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.NetTopologySuite" Version="8.0.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NBitcoin" Version="7.0.31"/>
Expand All @@ -35,6 +36,7 @@
<!-- <PackageReference Include="Serilog.Sinks.AwsCloudWatch" Version="4.0.182"/>-->
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0"/>
<PackageReference Include="xunit" Version="2.7.0" />
</ItemGroup>

<ItemGroup>
Expand Down
53 changes: 53 additions & 0 deletions LNUnit.Tests/PostgresLightningScenario.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using Lnrpc;
using LNUnit.Fixtures;
using ServiceStack;
using ServiceStack.Text;
using Xunit;
using Xunit.Abstractions;
using Assert = NUnit.Framework.Assert;

namespace NLightning.Bolts.Tests.Docker;

#pragma warning disable xUnit1033 // Test classes decorated with 'Xunit.IClassFixture<TFixture>' or 'Xunit.ICollectionFixture<TFixture>' should add a constructor argument of type TFixture
[Collection("postgres")]
public class PostgresXUnitTest
{
private readonly PostgresLightningFixture _lightningRegtestNetworkFixture;

public PostgresXUnitTest(PostgresLightningFixture fixture, ITestOutputHelper output)
{
_lightningRegtestNetworkFixture = fixture;
Console.SetOut(new TestOutputWriter(output));
}



[Fact]
public async Task Verify_Alice_Bob_Carol_Setup()
{
var readyNodes = _lightningRegtestNetworkFixture.Builder!.LNDNodePool!.ReadyNodes.ToImmutableList();
var nodeCount = readyNodes.Count;
Assert.AreEqual(3, nodeCount);

Check warning on line 34 in LNUnit.Tests/PostgresLightningScenario.cs

View workflow job for this annotation

GitHub Actions / build

Consider using the constraint model, Assert.That(actual, Is.EqualTo(expected)), instead of the classic model, Assert.AreEqual(expected, actual)
$"LND Nodes in Ready State: {nodeCount}".Print();
foreach (var node in readyNodes)
{
var walletBalanceResponse = await node.LightningClient.WalletBalanceAsync(new WalletBalanceRequest());
var channels = await node.LightningClient.ListChannelsAsync(new ListChannelsRequest());
$"Node {node.LocalAlias} ({node.LocalNodePubKey})".Print();
walletBalanceResponse.PrintDump();
channels.PrintDump();
}
$"Bitcoin Node Balance: {(await _lightningRegtestNetworkFixture.Builder!.BitcoinRpcClient!.GetBalanceAsync()).Satoshi / 1e8}".Print();
}

// [Fact]
// public async Task KeepRunning5Min()
// {
// await Task.Delay(5 * 60 * 1000);
// }
}
#pragma warning restore xUnit1033 // Test classes decorated with 'Xunit.IClassFixture<TFixture>' or 'Xunit.ICollectionFixture<TFixture>' should add a constructor argument of type TFixture
21 changes: 21 additions & 0 deletions LNUnit.Tests/TestOutputWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Text;
using Xunit.Abstractions;

namespace NLightning.Bolts.Tests.Docker;

public class TestOutputWriter : TextWriter
{
private readonly ITestOutputHelper _output;

public TestOutputWriter(ITestOutputHelper output)
{
_output = output;
}

public override Encoding Encoding => Encoding.UTF8;

public override void Write(char[] buffer, int index, int count)
{
_output.WriteLine(new string(buffer, index, count));
}
}
1 change: 1 addition & 0 deletions LNUnit.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<s:Boolean x:Key="/Default/Environment/Filtering/ExcludeCoverageFilters/=LNUnit_002ELND_003B_002A_003BWatchtowerrpc_002E_002A_003B_002A/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/Filtering/ExcludeCoverageFilters/=LNUnit_002ELND_003B_002A_003BWtclientrpc_002E_002A_003B_002A/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/Filtering/ExcludeCoverageFilters/=LNUnit_002ETests_003B_002A_003B_002A_003B_002A/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/Filtering/ExcludeCoverageFilters/=LNUnit_003B_002A_003BProgram_003B_002A/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=maxpendingchannels/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=noseedbackup/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=restlisten/@EntryIndexedValue">True</s:Boolean>
Expand Down
23 changes: 19 additions & 4 deletions LNUnit/Setup/LNUnitBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,15 @@ await _dockerClient.Containers.StartContainerAsync(loopServer?.ID,
n.DockerContainerId = nodeContainer.ID;
var success =
await _dockerClient.Containers.StartContainerAsync(nodeContainer.ID, new ContainerStartParameters());
var inspectionResponse = await _dockerClient.Containers.InspectContainerAsync(n.DockerContainerId);
var ipAddress = inspectionResponse.NetworkSettings.Networks.First().Value.IPAddress;
//Not always having IP yet.
var ipAddress = string.Empty;
ContainerInspectResponse? inspectionResponse = null;
while (ipAddress.IsEmpty())
{
inspectionResponse = await _dockerClient.Containers.InspectContainerAsync(n.DockerContainerId);
ipAddress = inspectionResponse.NetworkSettings.Networks.First().Value.IPAddress;
}

var basePath = !n.Image.Contains("lightning-terminal") ? "/home/lnd/.lnd" : "/root/lnd/.lnd";
if (n.Image.Contains("lightning-terminal")) await Task.Delay(2000);
var txt = await GetStringFromFS(n.DockerContainerId, $"{basePath}/tls.cert");
Expand Down Expand Up @@ -826,8 +833,8 @@ public static LNUnitBuilder AddBitcoinCoreNode(this LNUnitBuilder b, LNUnitNetwo
public static LNUnitBuilder AddPolarLNDNode(this LNUnitBuilder b, string aliasHostname,
List<LNUnitNetworkDefinition.Channel>? channels = null, string bitcoinMinerHost = "miner",
string rpcUser = "bitcoin", string rpcPass = "bitcoin", string imageName = "polarlightning/lnd",
string tagName = "0.16.2-beta", bool acceptKeysend = true, bool pullImage = true, bool mapTotmp = false,
bool gcInvoiceOnStartup = false, bool gcInvoiceOnFly = false)
string tagName = "0.17.4-beta", bool acceptKeysend = true, bool pullImage = true, bool mapTotmp = false,
bool gcInvoiceOnStartup = false, bool gcInvoiceOnFly = false, string postgresDSN = null)
{
var cmd = new List<string>
{
Expand Down Expand Up @@ -855,6 +862,14 @@ public static LNUnitBuilder AddPolarLNDNode(this LNUnitBuilder b, string aliasHo
"--gossip.max-channel-update-burst=100",
"--gossip.channel-update-interval=1s"
};

if (!postgresDSN.IsEmpty())
{
cmd.Add("--db.backend=postgres");
cmd.Add($"--db.postgres.dsn={postgresDSN}");
cmd.Add($"--db.postgres.timeout=300s");
cmd.Add($"--db.postgres.maxconnections=16");
}
if (gcInvoiceOnStartup) cmd.Add("--gc-canceled-invoices-on-startup");
if (gcInvoiceOnFly) cmd.Add("--gc-canceled-invoices-on-the-fly");

Expand Down

0 comments on commit c9f9311

Please sign in to comment.