< Summary - Combined Code Coverage

Information
Class: NLightning.Daemon.Handlers.OpenChannelClientSubscriptionHandler
Assembly: NLightning.Daemon
File(s): /home/runner/work/NLightning/NLightning/src/NLightning.Daemon/Handlers/OpenChannelClientSubscriptionHandler.cs
Tag: 57_24045730253
Line coverage
0%
Covered lines: 0
Uncovered lines: 93
Coverable lines: 93
Total lines: 199
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 72
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/NLightning/NLightning/src/NLightning.Daemon/Handlers/OpenChannelClientSubscriptionHandler.cs

#LineLine coverage
 1using Microsoft.Extensions.Logging;
 2
 3namespace NLightning.Daemon.Handlers;
 4
 5using Domain.Bitcoin.Interfaces;
 6using Domain.Channels.Enums;
 7using Domain.Channels.Events;
 8using Domain.Channels.Interfaces;
 9using Domain.Channels.ValueObjects;
 10using Domain.Client.Constants;
 11using Domain.Client.Enums;
 12using Domain.Client.Exceptions;
 13using Domain.Client.Requests;
 14using Domain.Client.Responses;
 15using Domain.Crypto.ValueObjects;
 16using Domain.Exceptions;
 17using Domain.Node.Events;
 18using Domain.Node.Interfaces;
 19using Interfaces;
 20
 21public class OpenChannelClientSubscriptionHandler :
 22    IClientCommandHandler<OpenChannelClientSubscriptionRequest, OpenChannelClientSubscriptionResponse>
 23{
 24    private readonly IChannelMemoryRepository _channelMemoryRepository;
 25    private readonly ILogger<OpenChannelClientSubscriptionHandler> _logger;
 26    private readonly IPeerManager _peerManager;
 27    private readonly IUtxoMemoryRepository _utxoMemoryRepository;
 28
 29    private ChannelId _channelId;
 30    private IPeerService? _peerService;
 31
 32    /// <inheritdoc/>
 033    public ClientCommand Command => ClientCommand.OpenChannelSubscription;
 34
 035    public OpenChannelClientSubscriptionHandler(IChannelMemoryRepository channelMemoryRepository,
 036                                                ILogger<OpenChannelClientSubscriptionHandler> logger,
 037                                                IPeerManager peerManager, IUtxoMemoryRepository utxoMemoryRepository)
 38    {
 039        _channelMemoryRepository = channelMemoryRepository;
 040        _logger = logger;
 041        _peerManager = peerManager;
 042        _utxoMemoryRepository = utxoMemoryRepository;
 043    }
 44
 45    /// <inheritdoc/>
 46    public async Task<OpenChannelClientSubscriptionResponse> HandleAsync(OpenChannelClientSubscriptionRequest request,
 47                                                                         CancellationToken ct)
 48    {
 049        if (request.ChannelId == ChannelId.Zero)
 050            throw new ClientException(ErrorCodes.InvalidChannel, "ChannelId cannot be empty");
 51
 052        _channelId = request.ChannelId;
 53
 054        if (!_channelMemoryRepository.TryGetChannel(_channelId, out var channel))
 055            throw new ClientException(ErrorCodes.InvalidChannel, $"Channel with Id {_channelId} not found");
 56
 057        var peer = _peerManager.GetPeer(channel.RemoteNodeId) ?? throw new ClientException(ErrorCodes.InvalidOperation,
 058                       $"Peer with NodeId {channel.RemoteNodeId} is not connected");
 59
 60        // Create a task completion source for the response
 061        var tsc = new TaskCompletionSource<OpenChannelClientSubscriptionResponse>(
 062            TaskCreationOptions.RunContinuationsAsynchronously);
 63
 64        // If it's in a state we consider Open, return immediately
 065        if (channel.State is ChannelState.ReadyForUs or ChannelState.ReadyForThem or ChannelState.Open)
 66        {
 067            return new OpenChannelClientSubscriptionResponse(channel.ChannelId)
 068            {
 069                ChannelState = ChannelState.ReadyForUs,
 070                TxId = channel.FundingOutput?.TransactionId,
 071                Index = channel.FundingOutput?.Index
 072            };
 73        }
 74
 75        // Check if the channel is already in a state we care about
 076        var lockedUtxos = _utxoMemoryRepository.GetLockedUtxosForChannel(_channelId);
 077        if (channel.State is not ChannelState.V1FundingSigned && lockedUtxos.Count == 0)
 078            throw new ClientException(ErrorCodes.InvalidOperation, $"No locked UTXOs found for channel {_channelId}");
 79
 80        try
 81        {
 082            if (!peer.TryGetPeerService(out _peerService))
 083                throw new ClientException(ErrorCodes.InvalidOperation, "Error getting peerService from peer");
 84
 85            // Subscribe to the events
 086            _peerService.OnAttentionMessageReceived += AttentionMessageHandlerEnvelope;
 087            _peerService.OnDisconnect += PeerDisconnectionEnvelope;
 088            _peerService.OnExceptionRaised += ExceptionRaisedEnvelope;
 089            _channelMemoryRepository.OnChannelUpdated += ChannelUpdatedHandlerEnvelope;
 90
 091            return await tsc.Task;
 92        }
 093        catch
 94        {
 095            if (!_channelMemoryRepository.TryGetChannel(_channelId, out channel)
 096             || channel.State is ChannelState.ReadyForUs
 097                              or ChannelState.ReadyForThem
 098                              or ChannelState.Open
 099                              or ChannelState.V1FundingSigned)
 0100                throw;
 101
 0102            _utxoMemoryRepository.ReturnUtxosNotSpentOnChannel(request.ChannelId);
 103
 0104            throw;
 105        }
 106        finally
 107        {
 108            //Unsubscribe from the events so we don't have dangling memory
 0109            _peerService?.OnAttentionMessageReceived -= AttentionMessageHandlerEnvelope;
 0110            _peerService?.OnDisconnect -= PeerDisconnectionEnvelope;
 0111            _peerService?.OnExceptionRaised -= ExceptionRaisedEnvelope;
 0112            _channelMemoryRepository.OnChannelUpdated -= ChannelUpdatedHandlerEnvelope;
 113        }
 114
 115        // Envelopes for the events
 116        void AttentionMessageHandlerEnvelope(object? _, AttentionMessageEventArgs args) =>
 0117            HandleAttentionMessage(args, tsc);
 118
 119        void PeerDisconnectionEnvelope(object? _, PeerDisconnectedEventArgs args) =>
 0120            HandlePeerDisconnection(args, channel.RemoteNodeId, tsc);
 121
 122        void ExceptionRaisedEnvelope(object? _, Exception e) =>
 0123            HandleExceptionRaised(e, tsc);
 124
 125        void ChannelUpdatedHandlerEnvelope(object? _, ChannelUpdatedEventArgs args) =>
 0126            HandleChannelUpdated(args, tsc);
 0127    }
 128
 129    private void HandleAttentionMessage(AttentionMessageEventArgs args,
 130                                        TaskCompletionSource<OpenChannelClientSubscriptionResponse> tsc)
 131    {
 0132        if (args.ChannelId != _channelId)
 0133            return;
 134
 0135        _logger.LogError(
 0136            "Received attention message from peer {peerId} for channel {channelId}: {message}",
 0137            args.PeerPubKey, args.ChannelId, args.Message);
 138
 0139        tsc.TrySetException(new ChannelErrorException($"Error opening channel: {args.Message}"));
 0140    }
 141
 142    private void HandlePeerDisconnection(PeerDisconnectedEventArgs args, CompactPubKey peerPubKey,
 143                                         TaskCompletionSource<OpenChannelClientSubscriptionResponse> tsc)
 144    {
 0145        if (args.PeerPubKey != peerPubKey)
 0146            return;
 147
 0148        if (args.Exception is null)
 149        {
 0150            _logger.LogError("Peer disconnected without notice");
 0151            tsc.TrySetException(new ConnectionException("Error opening channel: Peer disconnected"));
 152        }
 153        else
 154        {
 155            // Get to the bottom of the inner exceptions to fetch the real reason for the disconnection
 0156            var exception = args.Exception;
 0157            while (exception.InnerException is not null)
 0158                exception = exception.InnerException;
 159
 0160            _logger.LogError(args.Exception, "Error opening channel. Error: {message}", exception.Message);
 0161            tsc.TrySetException(new ChannelErrorException($"Error opening channel: {exception.Message}", exception));
 162        }
 0163    }
 164
 165    private void HandleExceptionRaised(Exception e, TaskCompletionSource<OpenChannelClientSubscriptionResponse> tsc)
 166    {
 0167        if (e is not ChannelErrorException ce || ce.ChannelId != _channelId)
 0168            return;
 169
 0170        _logger.LogError("Exception raised while opening channel: {message}", e.Message);
 0171        tsc.TrySetException(e);
 0172    }
 173
 174    private void HandleChannelUpdated(ChannelUpdatedEventArgs args,
 175                                      TaskCompletionSource<OpenChannelClientSubscriptionResponse> tsc)
 176    {
 0177        if (args.Channel.ChannelId != _channelId)
 0178            return;
 179
 0180        if (args.Channel.State == ChannelState.V1FundingSigned)
 181        {
 0182            tsc.TrySetResult(new OpenChannelClientSubscriptionResponse(args.Channel.ChannelId)
 0183            {
 0184                ChannelState = ChannelState.V1FundingSigned,
 0185                TxId = args.Channel.FundingOutput?.TransactionId,
 0186                Index = args.Channel.FundingOutput?.Index
 0187            });
 188        }
 0189        else if (args.Channel.State is ChannelState.ReadyForUs or ChannelState.ReadyForThem)
 190        {
 0191            tsc.TrySetResult(new OpenChannelClientSubscriptionResponse(args.Channel.ChannelId)
 0192            {
 0193                ChannelState = ChannelState.ReadyForUs,
 0194                TxId = args.Channel.FundingOutput?.TransactionId,
 0195                Index = args.Channel.FundingOutput?.Index
 0196            });
 197        }
 0198    }
 199}