Skip to content

Commit

Permalink
GRPC api call for creating changeless channels (#271)
Browse files Browse the repository at this point in the history
* feat: proto files ready to implement method

* chore: add null coinselecitonservice dependency to tests that dont need it

* feat: Api call to open changeless channels completed

* fix: merge OpenChannel and OpenChangelessChannel functions into same one

* test: fix tests

* test: testing changless opening of channel

* test: fix and add tests
  • Loading branch information
markettes authored Aug 18, 2023
1 parent 1590fc8 commit 454a8e1
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 42 deletions.
7 changes: 5 additions & 2 deletions src/Proto/nodeguard.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ option go_package = "./nodeguard";

service NodeGuardService {
/*
Returns the liquidity rules associated to a node and its channels
*/
rpc GetLiquidityRules(GetLiquidityRulesRequest) returns (GetLiquidityRulesResponse);
Expand Down Expand Up @@ -162,7 +162,10 @@ message OpenChannelRequest {
int64 sats_amount = 3;
// Whether the channel should be private
bool private = 4;

// Whether the channel should be created in a changeless way
bool changeless = 6;
// Outpoints for the UTXOs to use for the channel
repeated string utxos_outpoints = 7;
}

// A successful response is an empty message and does NOT indicate that the channel has been open, external monitoring is required
Expand Down
67 changes: 52 additions & 15 deletions src/Rpc/NodeGuardService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Grpc.Core;
using NBitcoin;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
using Nodeguard;
using Quartz;
using LiquidityRule = Nodeguard.LiquidityRule;
Expand Down Expand Up @@ -54,6 +55,7 @@ public class NodeGuardService : Nodeguard.NodeGuardService.NodeGuardServiceBase,
private readonly INodeRepository _nodeRepository;
private readonly IChannelOperationRequestRepository _channelOperationRequestRepository;
private readonly IChannelRepository _channelRepository;
private readonly ICoinSelectionService _coinSelectionService;
private readonly IScheduler _scheduler;

public NodeGuardService(ILogger<NodeGuardService> logger,
Expand All @@ -66,7 +68,8 @@ public NodeGuardService(ILogger<NodeGuardService> logger,
ISchedulerFactory schedulerFactory,
INodeRepository nodeRepository,
IChannelOperationRequestRepository channelOperationRequestRepository,
IChannelRepository channelRepository
IChannelRepository channelRepository,
ICoinSelectionService coinSelectionService
)
{
_logger = logger;
Expand All @@ -80,6 +83,7 @@ IChannelRepository channelRepository
_nodeRepository = nodeRepository;
_channelOperationRequestRepository = channelOperationRequestRepository;
_channelRepository = channelRepository;
_coinSelectionService = coinSelectionService;
_scheduler = Task.Run(() => _schedulerFactory.GetScheduler()).Result;
}

Expand Down Expand Up @@ -348,8 +352,33 @@ public override async Task<OpenChannelResponse> OpenChannel(OpenChannelRequest r
throw new RpcException(new Status(StatusCode.NotFound, "Destination node not found"));
}

var wallet = await _walletRepository.GetById(request.WalletId);
if (wallet == null)
{
throw new RpcException(new Status(StatusCode.NotFound, "Wallet not found"));
}

if (request.Changeless && request.UtxosOutpoints.Count == 0)
{
throw new RpcException(new Status(StatusCode.InvalidArgument, "Changeless channel open requires utxos"));
}

try
{
var outpoints = new List<OutPoint>();
var utxos = new List<UTXO>();

if (request.Changeless)
{
foreach (var outpoint in request.UtxosOutpoints)
{
outpoints.Add(OutPoint.Parse(outpoint));
}

// Search the utxos and lock them
utxos = await _coinSelectionService.GetUTXOsByOutpointAsync(wallet.GetDerivationStrategy(), outpoints);
}

var channelOperationRequest = new ChannelOperationRequest
{
SatsAmount = request.SatsAmount,
Expand All @@ -360,9 +389,10 @@ public override async Task<OpenChannelResponse> OpenChannel(OpenChannelRequest r
WalletId = request.WalletId,
SourceNodeId = sourceNode.Id,
DestNodeId = destNode.Id,
/*UserId = null, //TODO User & Auth
User = null,*/
/*UserId = null, //TODO User & Auth
User = null,*/
IsChannelPrivate = request.Private,
Changeless = request.Changeless,
};

//Persist request
Expand All @@ -373,6 +403,13 @@ public override async Task<OpenChannelResponse> OpenChannel(OpenChannelRequest r
throw new RpcException(new Status(StatusCode.Internal, "Error adding channel operation request"));
}

if (request.Changeless)
{
// Lock the utxos
await _coinSelectionService.LockUTXOs(utxos, channelOperationRequest,
BitcoinRequestType.ChannelOperation);
}

//Fire Open Channel Job
var scheduler = await _schedulerFactory.GetScheduler();

Expand Down Expand Up @@ -407,7 +444,7 @@ public override async Task<CloseChannelResponse> CloseChannel(CloseChannelReques
{
//Get channel by its chan_id (id of the ln implementation)
var channel = await _channelRepository.GetByChanId(request.ChannelId);

if (channel == null)
{
throw new RpcException(new Status(StatusCode.NotFound, "Channel not found"));
Expand All @@ -416,10 +453,10 @@ public override async Task<CloseChannelResponse> CloseChannel(CloseChannelReques
try
{
//Create channel operation request

var channelOperationRequest = new ChannelOperationRequest
{
Description = "Channel close (API)",
Description = "Channel close (API)",
Status = ChannelOperationRequestStatus.Pending,
RequestType = OperationRequestType.Close,
SourceNodeId = channel.SourceNodeId,
Expand All @@ -428,32 +465,32 @@ public override async Task<CloseChannelResponse> CloseChannel(CloseChannelReques
/*UserId = null, //TODO User & Auth
*/
};

//Persist request

var result = await _channelOperationRequestRepository.AddAsync(channelOperationRequest);

if (!result.Item1)
{
_logger?.LogError("Error adding channel operation request, error: {error}", result.Item2);
throw new RpcException(new Status(StatusCode.Internal, "Error adding channel operation request"));
}

//Fire Close Channel Job
var scheduler = await _schedulerFactory.GetScheduler();

var map = new JobDataMap();
map.Put("closeRequestId", channelOperationRequest.Id);
map.Put("forceClose", request.Force);

var retryList = RetriableJob.ParseRetryListFromString(Constants.JOB_RETRY_INTERVAL_LIST_IN_MINUTES);
var job = RetriableJob.Create<ChannelCloseJob>(map, channelOperationRequest.Id.ToString(), retryList);
await scheduler.ScheduleJob(job.Job, job.Trigger);

channelOperationRequest.JobId = job.Job.Key.ToString();

var jobUpdateResult = _channelOperationRequestRepository.Update(channelOperationRequest);

if (!jobUpdateResult.Item1)
{
_logger?.LogError("Error updating channel operation request, error: {error}", jobUpdateResult.Item2);
Expand Down
13 changes: 13 additions & 0 deletions src/Services/CoinSelectionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ public interface ICoinSelectionService
/// <param name="closestTo"></param>
public Task<List<UTXO>> GetAvailableUTXOsAsync(DerivationStrategyBase derivationStrategy, CoinSelectionStrategy strategy, int limit, long amount, long closestTo);

/// <summary>
/// Gets the UTXOs that are not locked in other transactions related to the outpoints
/// </summary>
/// <param name="derivationStrategy"></param>
/// <param name="outPoints"></param>
public Task<List<UTXO>> GetUTXOsByOutpointAsync(DerivationStrategyBase derivationStrategy, List<OutPoint> outPoints);

/// <summary>
/// Locks the UTXOs for using in a specific transaction
/// </summary>
Expand Down Expand Up @@ -194,4 +201,10 @@ public async Task<List<UTXO>> GetAvailableUTXOsAsync(DerivationStrategyBase deri

return (coins, selectedUTXOs);
}

public async Task<List<UTXO>> GetUTXOsByOutpointAsync(DerivationStrategyBase derivationStrategy, List<OutPoint> outPoints)
{
var utxos = await _nbXplorerService.GetUTXOsAsync(derivationStrategy);
return utxos.Confirmed.UTXOs.Where(utxo => outPoints.Contains(utxo.Outpoint)).ToList();
}
}
Loading

0 comments on commit 454a8e1

Please sign in to comment.