< Summary - Combined Code Coverage

Information
Class: NLightning.Infrastructure.Node.Models.Peer
Assembly: NLightning.Infrastructure
File(s): /home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure/Node/Models/Peer.cs
Tag: 30_15166811759
Line coverage
82%
Covered lines: 82
Uncovered lines: 18
Coverable lines: 100
Total lines: 220
Line coverage: 82%
Branch coverage
84%
Covered branches: 27
Total branches: 32
Branch coverage: 84.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%22100%
get_PeerAddress()100%11100%
DisconnectWithException(...)100%11100%
DisconnectWithException(...)50%22100%
HandleMessage(...)75%8.04891.67%
HandleException(...)100%210%
HandleInitialization(...)100%1414100%
StartPingPongService()100%1.06160%
HandlePingAsync()100%11100%
Disconnect()0%620%

File(s)

/home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure/Node/Models/Peer.cs

#LineLine coverage
 1using Microsoft.Extensions.Logging;
 2
 3namespace NLightning.Infrastructure.Node.Models;
 4
 5using Domain.Exceptions;
 6using Domain.Factories;
 7using Domain.Node.Options;
 8using Domain.Protocol.Constants;
 9using Domain.Protocol.Messages;
 10using Domain.Protocol.Messages.Interfaces;
 11using Domain.Protocol.Services;
 12using Domain.Protocol.Tlv;
 13using Interfaces;
 14
 15/// <summary>
 16/// Represents a peer in the network.
 17/// </summary>
 18/// <remarks>
 19/// This class is used to communicate with a peer in the network.
 20/// </remarks>
 21public sealed class Peer : IPeer
 22{
 4423    private readonly CancellationTokenSource _cancellationTokenSource = new();
 24    private readonly FeatureOptions _features;
 25    private readonly ILogger<Peer> _logger;
 26    private readonly IMessageFactory _messageFactory;
 27    private readonly IMessageService _messageService;
 28    private readonly IPingPongService _pingPongService;
 29
 30    private bool _isInitialized;
 31
 32    /// <summary>
 33    /// Event raised when the peer is disconnected.
 34    /// </summary>
 35    public event EventHandler? DisconnectEvent;
 36
 14037    public Protocol.Models.PeerAddress PeerAddress { get; }
 38
 39    /// <summary>
 40    /// Initializes a new instance of the <see cref="Peer"/> class.
 41    /// </summary>
 42    /// <param name="features">The feature options</param>
 43    /// <param name="logger">A logger</param>
 44    /// <param name="messageFactory">The message factory</param>
 45    /// <param name="messageService">The message service.</param>
 46    /// <param name="networkTimeout">Network timeout</param>
 47    /// <param name="peerAddress">Peer address</param>
 48    /// <param name="pingPongService">The ping pong service.</param>
 49    /// <exception cref="ConnectionException">Thrown when the connection to the peer fails.</exception>
 4450    internal Peer(FeatureOptions features, ILogger<Peer> logger, IMessageFactory messageFactory,
 4451                  IMessageService messageService, TimeSpan networkTimeout, Protocol.Models.PeerAddress peerAddress,
 4452                  IPingPongService pingPongService)
 53    {
 4454        _features = features;
 4455        _logger = logger;
 4456        _messageFactory = messageFactory;
 4457        _messageService = messageService;
 4458        _pingPongService = pingPongService;
 59
 4460        PeerAddress = peerAddress;
 61
 4462        _messageService.MessageReceived += HandleMessage;
 4463        _messageService.ExceptionRaised += HandleException;
 4464        _pingPongService.DisconnectEvent += HandleException;
 65
 66        // Always send an init message upon connection
 4467        logger.LogTrace("[Peer] Sending init message to peer {peer}", PeerAddress.PubKey);
 4468        var initMessage = _messageFactory.CreateInitMessage();
 4469        _messageService.SendMessageAsync(initMessage, _cancellationTokenSource.Token).Wait();
 70
 71        // Wait for an init message
 4472        logger.LogTrace("[Peer] Waiting for init message from peer {peer}", PeerAddress.PubKey);
 73        // Set timeout to close connection if the other peer doesn't send an init message
 4474        Task.Delay(networkTimeout, _cancellationTokenSource.Token).ContinueWith(task =>
 4475        {
 1676            if (!task.IsCanceled && !_isInitialized)
 4477            {
 478                DisconnectWithException(new ConnectionException("Peer did not send init message after timeout"));
 4479            }
 6080        });
 81
 4482        if (!_messageService.IsConnected)
 83        {
 484            throw new ConnectionException("Failed to connect to peer");
 85        }
 4086    }
 87
 88    private void DisconnectWithException(Exception e)
 89    {
 1690        DisconnectWithException(this, e);
 1691    }
 92    private void DisconnectWithException(object? sender, Exception? e)
 93    {
 1694        _logger.LogError(e, "Disconnecting peer {peer}", PeerAddress.PubKey);
 1695        _cancellationTokenSource.Cancel();
 1696        _messageService.Dispose();
 97
 1698        DisconnectEvent?.Invoke(sender, EventArgs.Empty);
 1699    }
 100
 101    private void HandleMessage(object? sender, IMessage? message)
 102    {
 24103        if (message is null)
 104        {
 0105            return;
 106        }
 107
 24108        if (!_isInitialized)
 109        {
 16110            _logger.LogTrace("[Peer] Received message from peer {peer} but was not initialized", PeerAddress.PubKey);
 16111            HandleInitialization(message);
 112        }
 113        else
 114        {
 8115            switch (message.Type)
 116            {
 117                case MessageTypes.PING:
 4118                    _logger.LogTrace("[Peer] Received ping message from peer {peer}", PeerAddress.PubKey);
 4119                    _ = HandlePingAsync(message);
 4120                    break;
 121                case MessageTypes.PONG:
 4122                    _logger.LogTrace("[Peer] Received pong message from peer {peer}", PeerAddress.PubKey);
 4123                    _pingPongService.HandlePong(message);
 124                    break;
 125            }
 126        }
 4127    }
 128
 129    private void HandleException(object? sender, Exception e)
 130    {
 0131        DisconnectWithException(sender, e);
 0132    }
 133
 134    private void HandleInitialization(IMessage message)
 135    {
 136        // Check if first message is an init message
 16137        if (message.Type != MessageTypes.INIT || message is not InitMessage initMessage)
 138        {
 4139            DisconnectWithException(new ConnectionException("Failed to receive init message"));
 4140            return;
 141        }
 142
 143        // Check if Features are compatible
 12144        if (!_features.GetNodeFeatures().IsCompatible(initMessage.Payload.FeatureSet))
 145        {
 4146            DisconnectWithException(new ConnectionException("Peer is not compatible"));
 4147            return;
 148        }
 149
 150        // Check if Chains are compatible
 8151        if (initMessage.Extension != null
 8152            && initMessage.Extension.TryGetTlv(TlvConstants.NETWORKS, out var networksTlv))
 153        {
 154            // Check if ChainHash contained in networksTlv.ChainHashes exists in our ChainHashes
 8155            var networkChainHashes = ((NetworksTlv)networksTlv!).ChainHashes;
 8156            if (networkChainHashes != null)
 157            {
 16158                if (networkChainHashes.Any(chainHash => !_features.ChainHashes.Contains(chainHash)))
 159                {
 4160                    DisconnectWithException(new ConnectionException("Peer chain is not compatible"));
 4161                    return;
 162                }
 163            }
 164        }
 165
 4166        FeatureOptions.GetNodeOptions(initMessage.Payload.FeatureSet, initMessage.Extension);
 167
 4168        _logger.LogTrace("[Peer] Message from peer {peer} is correct (init)", PeerAddress.PubKey);
 169
 4170        StartPingPongService();
 171
 4172        _isInitialized = true;
 4173    }
 174
 175    private void StartPingPongService()
 176    {
 4177        _pingPongService.PingMessageReadyEvent += (sender, pingMessage) =>
 4178        {
 4179            // We can only send ping messages if the peer is initialized
 0180            if (!_isInitialized)
 4181            {
 0182                return;
 4183            }
 4184
 0185            _ = _messageService.SendMessageAsync(pingMessage, _cancellationTokenSource.Token).ContinueWith(task =>
 0186            {
 0187                if (task.IsFaulted)
 0188                {
 0189                    DisconnectWithException(new ConnectionException("Failed to send ping message", task.Exception));
 0190                }
 0191            });
 4192        };
 193
 194        // Setup Ping to keep connection alive
 4195        _ = _pingPongService.StartPingAsync(_cancellationTokenSource.Token).ContinueWith(task =>
 4196        {
 4197            if (task.IsFaulted)
 4198            {
 0199                DisconnectWithException(new ConnectionException("Failed to start ping service", task.Exception));
 4200            }
 8201        });
 202
 4203        _logger.LogInformation("[Peer] Ping service started for peer {peer}", PeerAddress.PubKey);
 4204    }
 205
 206    private async Task HandlePingAsync(IMessage pingMessage)
 207    {
 4208        var pongMessage = _messageFactory.CreatePongMessage(pingMessage);
 4209        await _messageService.SendMessageAsync(pongMessage);
 4210    }
 211
 212    public void Disconnect()
 213    {
 0214        _logger.LogInformation("Disconnecting peer {peer}", PeerAddress.PubKey);
 0215        _cancellationTokenSource.Cancel();
 0216        _messageService.Dispose();
 217
 0218        DisconnectEvent?.Invoke(this, EventArgs.Empty);
 0219    }
 220}