< Summary - Combined Code Coverage

Information
Class: NLightning.Infrastructure.Node.Services.PeerService
Assembly: NLightning.Infrastructure
File(s): /home/runner/work/NLightning/NLightning/src/NLightning.Infrastructure/Node/Services/PeerService.cs
Tag: 57_24045730253
Line coverage
0%
Covered lines: 0
Uncovered lines: 104
Coverable lines: 104
Total lines: 259
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 58
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_PeerPubKey()100%210%
get_PreferredHost()100%210%
get_PreferredPort()100%210%
get_Features()100%210%
.ctor(...)100%210%
Disconnect(...)100%210%
SendMessageAsync(...)100%210%
SendWarningAsync(...)100%210%
HandleMessage(...)0%1056320%
HandleException(...)0%620%
HandleDisconnection(...)0%620%
HandleInitialization(...)0%506220%
Dispose()100%210%

File(s)

/home/runner/work/NLightning/NLightning/src/NLightning.Infrastructure/Node/Services/PeerService.cs

#LineLine coverage
 1using System.Net;
 2using System.Text.Unicode;
 3using Microsoft.Extensions.Logging;
 4
 5namespace NLightning.Infrastructure.Node.Services;
 6
 7using Domain.Channels.ValueObjects;
 8using Domain.Crypto.ValueObjects;
 9using Domain.Exceptions;
 10using Domain.Node.Events;
 11using Domain.Node.Interfaces;
 12using Domain.Node.Options;
 13using Domain.Protocol.Constants;
 14using Domain.Protocol.Interfaces;
 15using Domain.Protocol.Messages;
 16
 17// TODO: Eventually move this to the Application layer
 18/// <summary>
 19/// Service for peer communication
 20/// </summary>
 21public sealed class PeerService : IPeerService
 22{
 23    private readonly IPeerCommunicationService _peerCommunicationService;
 24    private readonly ILogger<PeerService> _logger;
 25
 26    private bool _isInitialized;
 27
 28    /// <inheritdoc/>
 29    public event EventHandler<PeerDisconnectedEventArgs>? OnDisconnect;
 30
 31    /// <inheritdoc/>
 32    public event EventHandler<ChannelMessageEventArgs>? OnChannelMessageReceived;
 33
 34    /// <inheritdoc/>
 35    public event EventHandler<AttentionMessageEventArgs>? OnAttentionMessageReceived;
 36
 37    /// <inheritdoc/>
 38    public event EventHandler<Exception>? OnExceptionRaised;
 39
 40    /// <inheritdoc/>
 041    public CompactPubKey PeerPubKey => _peerCommunicationService.PeerCompactPubKey;
 42
 043    public string? PreferredHost { get; private set; }
 044    public ushort? PreferredPort { get; private set; }
 45
 046    public FeatureOptions Features { get; private set; }
 47
 48    /// <summary>
 49    /// Initializes a new instance of the <see cref="PeerService"/> class.
 50    /// </summary>
 51    /// <param name="peerCommunicationService">The peer communication service</param>
 52    /// <param name="features">The feature options</param>
 53    /// <param name="logger">A logger</param>
 54    /// <param name="networkTimeout">Network timeout</param>
 055    public PeerService(IPeerCommunicationService peerCommunicationService, FeatureOptions features,
 056                       ILogger<PeerService> logger, TimeSpan networkTimeout)
 57    {
 058        _peerCommunicationService = peerCommunicationService;
 059        Features = features;
 060        _logger = logger;
 61
 62        // Set up event handlers
 063        _peerCommunicationService.MessageReceived += HandleMessage;
 064        _peerCommunicationService.ExceptionRaised += HandleException;
 065        _peerCommunicationService.DisconnectEvent += HandleDisconnection;
 66
 67        // Initialize communication
 68        try
 69        {
 070            _peerCommunicationService.InitializeAsync(networkTimeout).GetAwaiter().GetResult();
 071        }
 072        catch (Exception e)
 73        {
 074            throw new ErrorException("Error initializing peer communication", e);
 75        }
 076    }
 77
 78    /// <summary>
 79    /// Disconnects from the peer.
 80    /// </summary>
 81    public void Disconnect(Exception? exception = null)
 82    {
 083        _logger.LogInformation("Disconnecting peer {peer}", PeerPubKey);
 084        _peerCommunicationService.Disconnect(exception);
 085    }
 86
 87    public Task SendMessageAsync(IChannelMessage replyMessage)
 88    {
 089        return _peerCommunicationService.SendMessageAsync(replyMessage);
 90    }
 91
 92    public Task SendWarningAsync(WarningException we)
 93    {
 094        return _peerCommunicationService.SendWarningAsync(we);
 95    }
 96
 97    /// <summary>
 98    /// Handles messages received from the peer.
 99    /// </summary>
 100    private void HandleMessage(object? sender, IMessage? message)
 101    {
 0102        if (message is null)
 0103            return;
 104
 0105        if (!_isInitialized)
 106        {
 0107            HandleInitialization(message);
 108        }
 0109        else if (message is IChannelMessage channelMessage)
 110        {
 0111            _logger.LogTrace("Received channel message ({messageType}) from peer {peer}",
 0112                             Enum.GetName(message.Type), PeerPubKey);
 113
 0114            OnChannelMessageReceived?.Invoke(this, new ChannelMessageEventArgs(channelMessage, PeerPubKey));
 115        }
 0116        else if (message is ErrorMessage errorMessage)
 117        {
 0118            var errorMessageString = string.Empty;
 0119            ChannelId? channelId = null;
 0120            if (errorMessage.Payload.ChannelId != ChannelId.Zero)
 0121                channelId = errorMessage.Payload.ChannelId;
 122
 0123            if (errorMessage.Payload.Data is not null)
 124            {
 125                // Try to get utf8 string from error data
 0126                errorMessageString = Utf8.IsValid(errorMessage.Payload.Data)
 0127                                         ? System.Text.Encoding.UTF8.GetString(errorMessage.Payload.Data)
 0128#if NET9_0_OR_GREATER
 0129                                         : Convert.ToHexStringLower(errorMessage.Payload.Data);
 130#else
 131                                         : Convert.ToHexString(errorMessage.Payload.Data).ToLowerInvariant();
 132#endif
 133
 0134                _logger.LogError(
 0135                    "Received error message from peer {peer} for channel {channelId}: {errorMessage}",
 0136                    PeerPubKey, channelId is null ? "" : channelId.ToString(), errorMessageString);
 137            }
 138
 0139            OnAttentionMessageReceived?.Invoke(
 0140                this, new AttentionMessageEventArgs(errorMessageString, PeerPubKey, channelId));
 141        }
 0142        else if (message is WarningMessage warningMessage)
 143        {
 0144            var warningMessageString = string.Empty;
 0145            ChannelId? channelId = null;
 0146            if (warningMessage.Payload.ChannelId != ChannelId.Zero)
 0147                channelId = warningMessage.Payload.ChannelId;
 148
 0149            if (warningMessage.Payload.Data is not null)
 150            {
 151                // Try to get utf8 string from error data
 0152                warningMessageString = Utf8.IsValid(warningMessage.Payload.Data)
 0153                                           ? System.Text.Encoding.UTF8.GetString(warningMessage.Payload.Data)
 0154#if NET9_0_OR_GREATER
 0155                                           : Convert.ToHexStringLower(warningMessage.Payload.Data);
 156#else
 157                                         : Convert.ToHexString(warningMessage.Payload.Data).ToLowerInvariant();
 158#endif
 159
 0160                _logger.LogError(
 0161                    "Received error message from peer {peer} for channel {channelId}: {errorMessage}",
 0162                    PeerPubKey, channelId is null ? "" : channelId.ToString(), warningMessageString);
 163            }
 164
 0165            OnAttentionMessageReceived?.Invoke(
 0166                this, new AttentionMessageEventArgs(warningMessageString, PeerPubKey, channelId));
 167        }
 0168    }
 169
 170    /// <summary>
 171    /// Handles exceptions raised by the communication service.
 172    /// </summary>
 173    private void HandleException(object? sender, Exception e)
 174    {
 0175        _logger.LogError(e, "Exception occurred with peer {peer}", PeerPubKey);
 0176        OnExceptionRaised?.Invoke(this, e);
 0177    }
 178
 179    private void HandleDisconnection(object? sender, Exception e)
 180    {
 0181        _logger.LogTrace(e, "Handling disconnection for peer {Peer}", PeerPubKey);
 0182        OnDisconnect?.Invoke(this, new PeerDisconnectedEventArgs(PeerPubKey, e));
 0183    }
 184
 185    /// <summary>
 186    /// Handles the initialization process when receiving the first message.
 187    /// </summary>
 188    private void HandleInitialization(IMessage message)
 189    {
 190        // Check if the first message is an init message
 0191        if (message.Type != MessageTypes.Init || message is not InitMessage initMessage)
 192        {
 0193            _logger.LogError("Failed to receive init message from peer {peer}", PeerPubKey);
 0194            Disconnect();
 0195            return;
 196        }
 197
 198        // Check if Features are compatible
 0199        if (!Features.GetNodeFeatures().IsCompatible(initMessage.Payload.FeatureSet, out var negotiatedFeatures)
 0200         || negotiatedFeatures is null)
 201        {
 0202            _logger.LogError("Peer {peer} is not compatible", PeerPubKey);
 0203            Disconnect();
 0204            return;
 205        }
 206
 207        // Check if ChainHash contained in networksTlv.ChainHashes exists in our ChainHashes
 0208        var networkChainHashes = initMessage.NetworksTlv?.ChainHashes;
 0209        if (networkChainHashes != null
 0210         && networkChainHashes.Any(chainHash => !Features.ChainHashes.Contains(chainHash)))
 211        {
 0212            _logger.LogError("Peer {peer} chain is not compatible", PeerPubKey);
 0213            Disconnect();
 0214            return;
 215        }
 216
 0217        if (initMessage.RemoteAddressTlv is not null)
 218        {
 0219            switch (initMessage.RemoteAddressTlv.AddressType)
 220            {
 221                case 1 or 2:
 222                    {
 0223                        if (!IPAddress.TryParse(initMessage.RemoteAddressTlv.Address, out var ipAddress))
 224                        {
 0225                            _logger.LogWarning("Peer {peer} has an invalid remote address: {address}",
 0226                                               PeerPubKey, initMessage.RemoteAddressTlv.Address);
 227                        }
 228                        else
 229                        {
 0230                            PreferredHost = ipAddress.ToString();
 0231                            PreferredPort = initMessage.RemoteAddressTlv.Port;
 232                        }
 233
 0234                        break;
 235                    }
 236                case 5:
 0237                    PreferredHost = initMessage.RemoteAddressTlv.Address;
 0238                    PreferredPort = initMessage.RemoteAddressTlv.Port;
 0239                    break;
 240                default:
 0241                    _logger.LogWarning("Peer {peer} has an unsupported remote address type: {addressType}",
 0242                                       PeerPubKey, initMessage.RemoteAddressTlv.AddressType);
 243                    break;
 244            }
 245        }
 246
 0247        Features = FeatureOptions.GetNodeOptions(negotiatedFeatures, initMessage.Extension);
 0248        _logger.LogTrace("Initialization from peer {peer} completed successfully", PeerPubKey);
 0249        _isInitialized = true;
 0250    }
 251
 252    public void Dispose()
 253    {
 0254        _peerCommunicationService.MessageReceived -= HandleMessage;
 0255        _peerCommunicationService.ExceptionRaised -= HandleException;
 0256        _peerCommunicationService.DisconnectEvent -= HandleDisconnection;
 0257        _peerCommunicationService.Dispose();
 0258    }
 259}