Skip to content

Commit

Permalink
Simultaneous operations in channels with same source and destination (#…
Browse files Browse the repository at this point in the history
…240)

* Simultaneous operations in channels with same source and destination

* Update src/Data/Repositories/ChannelRepository.cs

Co-authored-by: Marcos <[email protected]>

---------

Co-authored-by: Marcos <[email protected]>
  • Loading branch information
RodriFS and markettes authored Jul 25, 2023
1 parent 4bcb6c2 commit 0b6c8ca
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 17 deletions.
36 changes: 20 additions & 16 deletions src/Data/Repositories/ChannelOperationRequestRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,31 +92,35 @@ public async Task<List<ChannelOperationRequest>> GetUnsignedPendingRequestsByUse
.ToListAsync();
}

public async Task<(bool, string?)> AddAsync(ChannelOperationRequest type)
public async Task<(bool, string?)> AddAsync(ChannelOperationRequest request)
{
await using var applicationDbContext = await _dbContextFactory.CreateDbContextAsync();

type.SetCreationDatetime();
type.SetUpdateDatetime();
request.SetCreationDatetime();
request.SetUpdateDatetime();

//Check for avoiding duplicate request
var existingRequest = await applicationDbContext.ChannelOperationRequests.Where(x =>
x.SourceNodeId == type.SourceNodeId
&& x.DestNodeId == type.DestNodeId && x.RequestType == type.RequestType).ToListAsync();

if (existingRequest.Any(x => x.Status == ChannelOperationRequestStatus.OnChainConfirmationPending ||
x.Status == ChannelOperationRequestStatus.Pending ||
x.Status == ChannelOperationRequestStatus.PSBTSignaturesPending
))
if (!Constants.ALLOW_SIMULTANEOUS_CHANNEL_OPENING_OPERATIONS && request.RequestType != OperationRequestType.Close)
{
return (false,
"Error, a channel operation request with the same source and destination node is in pending status, wait for that request to finalise before submitting a new request");
var all = applicationDbContext.ChannelOperationRequests.Count();
var existingRequest = await applicationDbContext.ChannelOperationRequests.Where(x =>
x.SourceNodeId == request.SourceNodeId
&& x.DestNodeId == request.DestNodeId && x.RequestType == request.RequestType).ToListAsync();

if (existingRequest.Any(x => x.Status == ChannelOperationRequestStatus.OnChainConfirmationPending ||
x.Status == ChannelOperationRequestStatus.Pending ||
x.Status == ChannelOperationRequestStatus.PSBTSignaturesPending
))
{
return (false,
"Error, a channel operation request with the same source and destination node is in pending status, wait for that request to finalise before submitting a new request");
}
}

var valueTuple = await _repository.AddAsync(type, applicationDbContext);
if (type.WalletId.HasValue)
var valueTuple = await _repository.AddAsync(request, applicationDbContext);
if (request.WalletId.HasValue)
{
await _notificationService.NotifyRequestSigners(type.WalletId.Value, "/channel-requests");
await _notificationService.NotifyRequestSigners(request.WalletId.Value, "/channel-requests");
}

return valueTuple;
Expand Down
2 changes: 1 addition & 1 deletion src/Data/Repositories/ChannelRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public async Task<List<Channel>> GetAll()

if (!closeRequestAddResult.Item1)
{
_logger.LogError("Error while saving close request for channel with id: {RequestId}", type.Id);
_logger.LogError("Error while saving close request for channel with id: {RequestId}: {error}", type.Id, closeRequestAddResult.Item2);
return (false, closeRequestAddResult.Item2);
}

Expand Down
7 changes: 7 additions & 0 deletions src/Helpers/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ public class Constants
public static readonly bool PUSH_NOTIFICATIONS_ONESIGNAL_ENABLED;
public static readonly bool ENABLE_HW_SUPPORT;
public static readonly bool NBXPLORER_ENABLE_CUSTOM_BACKEND = false;
/// <summary>
/// Allow simultaneous channel opening operations using the same source and destination nodes
/// </summary>
public static bool ALLOW_SIMULTANEOUS_CHANNEL_OPENING_OPERATIONS; // Not readonly so we can change it in tests

// Connections
public static readonly string POSTGRES_CONNECTIONSTRING = "Host=localhost;Port=5432;Database=fundsmanager;Username=rw_dev;Password=rw_dev";
Expand Down Expand Up @@ -105,6 +109,8 @@ static Constants()

NBXPLORER_ENABLE_CUSTOM_BACKEND = Environment.GetEnvironmentVariable("NBXPLORER_ENABLE_CUSTOM_BACKEND") == "true";

ALLOW_SIMULTANEOUS_CHANNEL_OPENING_OPERATIONS = Environment.GetEnvironmentVariable("ALLOW_SIMULTANEOUS_CHANNEL_OPENING_OPERATIONS") == "true";

// Connections
POSTGRES_CONNECTIONSTRING = Environment.GetEnvironmentVariable("POSTGRES_CONNECTIONSTRING") ?? POSTGRES_CONNECTIONSTRING;

Expand Down Expand Up @@ -212,6 +218,7 @@ static Constants()
MAX_SAT_PER_VB_RATIO = maxSatPerVbRatioEnv!= null ? decimal.Parse(maxSatPerVbRatioEnv) : MAX_SAT_PER_VB_RATIO;

}

}

public class EnvironmentalVariableMissingException: ArgumentNullException
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using FluentAssertions;
using FundsManager.Data.Models;
using FundsManager.Data.Repositories.Interfaces;
using Microsoft.EntityFrameworkCore;

namespace FundsManager.Data.Repositories;

public class ChannelOperationRequestRepositoryTests
{
private readonly Random _random = new();

private Mock<IDbContextFactory<ApplicationDbContext>> SetupDbContextFactory()
{
var dbContextFactory = new Mock<IDbContextFactory<ApplicationDbContext>>();
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: "ChannelOperationRequestRepositoryTests" + _random.Next())
.Options;
var context = ()=> new ApplicationDbContext(options);
dbContextFactory.Setup(x => x.CreateDbContext()).Returns(context);
dbContextFactory.Setup(x => x.CreateDbContextAsync(default)).ReturnsAsync(context);
return dbContextFactory;
}

[Fact]
public async Task AddAsync_ChannelCloseOperations()
{
// Arrange
var dbContextFactory = SetupDbContextFactory();
var repository = new Mock<IRepository<ChannelOperationRequest>>();

repository
.Setup(x => x.AddAsync(It.IsAny<ChannelOperationRequest>(), It.IsAny<ApplicationDbContext>()))
.ReturnsAsync((true, null));

await using var context = await dbContextFactory.Object.CreateDbContextAsync();

var request1 = new ChannelOperationRequest()
{
RequestType = OperationRequestType.Close,
Status = ChannelOperationRequestStatus.OnChainConfirmationPending
};
await context.ChannelOperationRequests.AddAsync(request1);

var channelOperationRequestRepository = new ChannelOperationRequestRepository(repository.Object, null, dbContextFactory.Object, null, null);

// Act
var result = await channelOperationRequestRepository.AddAsync(request1);

// Assert
result.Item1.Should().BeTrue();
result.Item2.Should().BeNull();
}

[Fact]
public async Task AddAsync_ChannelOpenOperations_SimultaneousOpsNotAllowed()
{
// Arrange
Constants.ALLOW_SIMULTANEOUS_CHANNEL_OPENING_OPERATIONS = false;

var dbContextFactory = SetupDbContextFactory();
var repository = new Mock<IRepository<ChannelOperationRequest>>();

repository
.Setup(x => x.AddAsync(It.IsAny<ChannelOperationRequest>(), It.IsAny<ApplicationDbContext>()))
.ReturnsAsync((true, null));

var dbContextFactoryObject = dbContextFactory.Object;
var context = await dbContextFactoryObject.CreateDbContextAsync();

var request1 = new ChannelOperationRequest()
{
RequestType = OperationRequestType.Open,
SourceNodeId = 1,
DestNodeId = 2,
Status = ChannelOperationRequestStatus.OnChainConfirmationPending
};

await context.ChannelOperationRequests.AddAsync(request1);
await context.SaveChangesAsync();

var channelOperationRequestRepository = new ChannelOperationRequestRepository(repository.Object, null, dbContextFactoryObject, null, null);

// Act
var result = await channelOperationRequestRepository.AddAsync(request1);

// Assert
result.Item1.Should().BeFalse();
result.Item2.Should().Be("Error, a channel operation request with the same source and destination node is in pending status, wait for that request to finalise before submitting a new request");
}

[Fact]
public async Task AddAsync_ChannelOpenOperations_SimultaneousOpsAllowed()
{
// Arrange
Constants.ALLOW_SIMULTANEOUS_CHANNEL_OPENING_OPERATIONS = true;

var dbContextFactory = SetupDbContextFactory();
var repository = new Mock<IRepository<ChannelOperationRequest>>();

repository
.Setup(x => x.AddAsync(It.IsAny<ChannelOperationRequest>(), It.IsAny<ApplicationDbContext>()))
.ReturnsAsync((true, null));

var dbContextFactoryObject = dbContextFactory.Object;
var context = await dbContextFactoryObject.CreateDbContextAsync();

var request1 = new ChannelOperationRequest()
{
RequestType = OperationRequestType.Open,
SourceNodeId = 1,
DestNodeId = 2,
Status = ChannelOperationRequestStatus.OnChainConfirmationPending
};

await context.ChannelOperationRequests.AddAsync(request1);
await context.SaveChangesAsync();

var channelOperationRequestRepository = new ChannelOperationRequestRepository(repository.Object, null, dbContextFactoryObject, null, null);

// Act
var result = await channelOperationRequestRepository.AddAsync(request1);

// Assert
result.Item1.Should().BeTrue();
result.Item2.Should().BeNull();
}
}

0 comments on commit 0b6c8ca

Please sign in to comment.