< Summary - Combined Code Coverage

Information
Class: NLightning.Daemon.Handlers.OpenChannelClientHandler
Assembly: NLightning.Daemon
File(s): /home/runner/work/NLightning/NLightning/src/NLightning.Daemon/Handlers/OpenChannelClientHandler.cs
Tag: 57_24045730253
Line coverage
0%
Covered lines: 0
Uncovered lines: 108
Coverable lines: 108
Total lines: 230
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 50
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/OpenChannelClientHandler.cs

#LineLine coverage
 1using Microsoft.Extensions.Logging;
 2
 3namespace NLightning.Daemon.Handlers;
 4
 5using Domain.Bitcoin.Interfaces;
 6using Domain.Channels.Events;
 7using Domain.Channels.Interfaces;
 8using Domain.Channels.ValueObjects;
 9using Domain.Client.Constants;
 10using Domain.Client.Enums;
 11using Domain.Client.Exceptions;
 12using Domain.Client.Requests;
 13using Domain.Client.Responses;
 14using Domain.Crypto.ValueObjects;
 15using Domain.Enums;
 16using Domain.Exceptions;
 17using Domain.Node;
 18using Domain.Node.Events;
 19using Domain.Node.Interfaces;
 20using Domain.Node.ValueObjects;
 21using Domain.Protocol.Interfaces;
 22using Domain.Protocol.Tlv;
 23using Infrastructure.Bitcoin.Wallet.Interfaces;
 24using Infrastructure.Protocol.Models;
 25using Interfaces;
 26
 27public sealed class OpenChannelClientHandler
 28    : IClientCommandHandler<OpenChannelClientRequest, OpenChannelClientResponse>
 29{
 30    private readonly IBlockchainMonitor _blockchainMonitor;
 31    private readonly IChannelMemoryRepository _channelMemoryRepository;
 32    private readonly IChannelFactory _channelFactory;
 33    private readonly ILogger<OpenChannelClientHandler> _logger;
 34    private readonly IMessageFactory _messageFactory;
 35    private readonly IPeerManager _peerManager;
 36    private readonly IUtxoMemoryRepository _utxoMemoryRepository;
 37
 038    private ChannelId _channelId = ChannelId.Zero;
 39    private IPeerService? _peerService;
 40
 41    /// <inheritdoc/>
 042    public ClientCommand Command => ClientCommand.OpenChannel;
 43
 044    public OpenChannelClientHandler(IBlockchainMonitor blockchainMonitor, IChannelFactory channelFactory,
 045                                    IChannelMemoryRepository channelMemoryRepository,
 046                                    ILogger<OpenChannelClientHandler> logger, IMessageFactory messageFactory,
 047                                    IPeerManager peerManager, IUtxoMemoryRepository utxoMemoryRepository)
 48    {
 049        _blockchainMonitor = blockchainMonitor;
 050        _channelFactory = channelFactory;
 051        _channelMemoryRepository = channelMemoryRepository;
 052        _logger = logger;
 053        _messageFactory = messageFactory;
 054        _peerManager = peerManager;
 055        _utxoMemoryRepository = utxoMemoryRepository;
 056    }
 57
 58    /// <inheritdoc/>
 59    public async Task<OpenChannelClientResponse> HandleAsync(OpenChannelClientRequest request, CancellationToken ct)
 60    {
 061        if (string.IsNullOrWhiteSpace(request.NodeInfo))
 062            throw new ClientException(ErrorCodes.InvalidAddress, "Address cannot be empty");
 63
 64        // Check if either a PeerAddressInfo or a CompactPubKey was provided
 065        var isPeerAddressInfo = request.NodeInfo.Contains('@') && request.NodeInfo.Contains(':');
 66        CompactPubKey peerId;
 67
 068        peerId = isPeerAddressInfo
 069                     ? new PeerAddress(request.NodeInfo).PubKey
 070                     : new CompactPubKey(Convert.FromHexString(request.NodeInfo)); // Parse as a hex public key
 71
 72        // Check if we're connected to the peer
 073        var peer = _peerManager.GetPeer(peerId)
 074                ?? await _peerManager.ConnectToPeerAsync(new PeerAddressInfo(request.NodeInfo));
 75
 76        // Let's check if we have enough funds to open this channel
 077        var currentHeight = _blockchainMonitor.LastProcessedBlockHeight;
 078        if (_utxoMemoryRepository.GetConfirmedBalance(currentHeight) < request.FundingAmount)
 079            throw new ClientException(ErrorCodes.NotEnoughBalance, "We don't have enough balance to open this channel");
 80
 81        // Since we're connected, let's open the channel
 082        var channel =
 083            await _channelFactory.CreateChannelV1AsInitiatorAsync(request, peer.NegotiatedFeatures, peerId);
 84
 85        // Save the channelId for later
 086        _channelId = channel.ChannelId;
 87
 088        if (_logger.IsEnabled(LogLevel.Trace))
 089            _logger.LogTrace("Created Temporary Channel {id} with fundingPubKey: {fundingPubKey}", channel.ChannelId,
 090                             channel.LocalKeySet.FundingCompactPubKey);
 91
 92        // Select UTXOs and mark them as toSpend for this channel
 093        _utxoMemoryRepository.LockUtxosToSpendOnChannel(request.FundingAmount, channel.ChannelId);
 94
 95        // Create a task completion source for the response
 096        var tsc = new TaskCompletionSource<OpenChannelClientResponse>(
 097            TaskCreationOptions.RunContinuationsAsynchronously);
 98
 99        try
 100        {
 101            // Add the channel to dictionaries
 0102            _channelMemoryRepository.AddTemporaryChannel(peerId, channel);
 103
 104            // Create the channel type Tlv
 0105            var channelTypeFeatureSet = FeatureSet.NewBasicChannelType();
 0106            if (peer.NegotiatedFeatures.OptionAnchors >= FeatureSupport.Optional)
 0107                channelTypeFeatureSet.SetFeature(Feature.OptionAnchors, true);
 108
 0109            if (channel.ChannelConfig.UseScidAlias >= FeatureSupport.Optional)
 0110                channelTypeFeatureSet.SetFeature(Feature.OptionScidAlias, true);
 111
 0112            if (channel.ChannelConfig.MinimumDepth == 0)
 0113                channelTypeFeatureSet.SetFeature(Feature.OptionZeroconf, true);
 114
 0115            var featureSetBytes = channelTypeFeatureSet.GetBytes() ?? throw new ClientException(
 0116                                      ErrorCodes.InvalidOperation,
 0117                                      $"Error creating {nameof(ChannelTypeTlv)}. This should never happen.");
 0118            var channelTypeTlv = new ChannelTypeTlv(featureSetBytes);
 119
 120            // Create UpfrontShutdownScriptTlv if needed
 0121            var upfrontShutdownScriptTlv = channel.LocalUpfrontShutdownScript is not null
 0122                                               ? new UpfrontShutdownScriptTlv(channel.LocalUpfrontShutdownScript.Value)
 0123                                               : new UpfrontShutdownScriptTlv(Array.Empty<byte>());
 124
 125            // Create the ChannelFlags
 0126            var channelFlags = new ChannelFlags(ChannelFlag.None);
 0127            if (peer.NegotiatedFeatures.ScidAlias == FeatureSupport.Compulsory)
 0128                channelFlags = new ChannelFlags(ChannelFlag.AnnounceChannel);
 129
 130            // Create the openChannel message
 0131            var openChannel1Message = _messageFactory.CreateOpenChannel1Message(
 0132                channel.ChannelId, channel.LocalBalance, channel.LocalKeySet.FundingCompactPubKey,
 0133                channel.RemoteBalance, channel.ChannelConfig.ChannelReserveAmount,
 0134                channel.ChannelConfig.FeeRateAmountPerKw,
 0135                channel.ChannelConfig.MaxAcceptedHtlcs, channel.LocalKeySet.RevocationCompactBasepoint,
 0136                channel.LocalKeySet.PaymentCompactBasepoint, channel.LocalKeySet.DelayedPaymentCompactBasepoint,
 0137                channel.LocalKeySet.HtlcCompactBasepoint, channel.LocalKeySet.CurrentPerCommitmentCompactPoint,
 0138                channelFlags, channelTypeTlv, upfrontShutdownScriptTlv);
 139
 0140            if (!peer.TryGetPeerService(out _peerService))
 0141                throw new ClientException(ErrorCodes.InvalidOperation, "Error getting peerService from peer");
 142
 143            // Subscribe to the events before sending the message
 0144            _peerService.OnAttentionMessageReceived += AttentionMessageHandlerEnvelope;
 0145            _peerService.OnDisconnect += PeerDisconnectionEnvelope;
 0146            _peerService.OnExceptionRaised += ExceptionRaisedEnvelope;
 0147            _channelMemoryRepository.OnChannelUpgraded += ChannelUpgradedHandlerEnvelope;
 148
 0149            if (_logger.IsEnabled(LogLevel.Information))
 0150                _logger.LogInformation("Sending OpenChannel message to peer {peerId} for channel {channelId}",
 0151                                       peerId,
 0152                                       channel.ChannelId);
 0153            await _peerService.SendMessageAsync(openChannel1Message);
 154
 0155            return await tsc.Task;
 156        }
 0157        catch
 158        {
 0159            _utxoMemoryRepository.ReturnUtxosNotSpentOnChannel(_channelId);
 160
 0161            throw;
 162        }
 163        finally
 164        {
 165            //Unsubscribe from the events so we don't have dangling memory
 0166            _peerService?.OnAttentionMessageReceived -= AttentionMessageHandlerEnvelope;
 0167            _peerService?.OnDisconnect -= PeerDisconnectionEnvelope;
 0168            _peerService?.OnExceptionRaised -= ExceptionRaisedEnvelope;
 0169            _channelMemoryRepository.OnChannelUpgraded -= ChannelUpgradedHandlerEnvelope;
 170        }
 171
 172        // Envelopes for the events
 173        void AttentionMessageHandlerEnvelope(object? _, AttentionMessageEventArgs args) =>
 0174            HandleAttentionMessage(args, tsc);
 175
 176        void PeerDisconnectionEnvelope(object? _, PeerDisconnectedEventArgs args) =>
 0177            HandlePeerDisconnection(args, channel.RemoteNodeId, tsc);
 178
 179        void ExceptionRaisedEnvelope(object? _, Exception e) =>
 0180            HandleExceptionRaised(e, tsc);
 181
 182        void ChannelUpgradedHandlerEnvelope(object? _, ChannelUpgradedEventArgs args) =>
 0183            HandleChannelUpgraded(args, tsc);
 0184    }
 185
 186    private void HandleChannelUpgraded(ChannelUpgradedEventArgs args,
 187                                       TaskCompletionSource<OpenChannelClientResponse> tsc)
 188    {
 0189        if (args.OldChannelId != _channelId)
 0190            return;
 191
 0192        tsc.TrySetResult(new OpenChannelClientResponse(args.NewChannelId));
 193
 0194        if (_logger.IsEnabled(LogLevel.Information))
 0195            _logger.LogInformation("Channel {oldChannelId} has been upgraded to {channelId}", args.OldChannelId,
 0196                                   args.NewChannelId);
 0197    }
 198
 199    private void HandleAttentionMessage(AttentionMessageEventArgs args,
 200                                        TaskCompletionSource<OpenChannelClientResponse> tsc)
 201    {
 0202        if (args.ChannelId != _channelId)
 0203            return;
 204
 0205        _logger.LogError(
 0206            "Received attention message from peer {peerId} for channel {channelId}: {message}",
 0207            args.PeerPubKey, args.ChannelId, args.Message);
 208
 0209        tsc.TrySetException(new ChannelErrorException($"Error opening channel: {args.Message}"));
 0210    }
 211
 212    private void HandlePeerDisconnection(PeerDisconnectedEventArgs args, CompactPubKey peerPubKey,
 213                                         TaskCompletionSource<OpenChannelClientResponse> tsc)
 214    {
 0215        if (args.PeerPubKey != peerPubKey)
 0216            return;
 217
 0218        _logger.LogError("Peer disconnected without notice");
 0219        tsc.TrySetException(new ConnectionException("Error opening channel: Peer disconnected"));
 0220    }
 221
 222    private void HandleExceptionRaised(Exception e, TaskCompletionSource<OpenChannelClientResponse> tsc)
 223    {
 0224        if (e is not ChannelErrorException ce || ce.ChannelId != _channelId)
 0225            return;
 226
 0227        _logger.LogError("Exception raised while opening channel: {message}", e.Message);
 0228        tsc.TrySetException(e);
 0229    }
 230}