< Summary - Combined Code Coverage

Information
Class: NLightning.Application.Channels.Handlers.ChannelReadyMessageHandler
Assembly: NLightning.Application
File(s): /home/runner/work/NLightning/NLightning/src/NLightning.Application/Channels/Handlers/ChannelReadyMessageHandler.cs
Tag: 57_24045730253
Line coverage
0%
Covered lines: 0
Uncovered lines: 77
Coverable lines: 77
Total lines: 163
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 42
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
HandleAsync()0%1332360%
PersistChannelAsync()0%2040%
ShouldReplaceAlias()0%620%

File(s)

/home/runner/work/NLightning/NLightning/src/NLightning.Application/Channels/Handlers/ChannelReadyMessageHandler.cs

#LineLine coverage
 1using System.Security.Cryptography;
 2using Microsoft.Extensions.Logging;
 3
 4namespace NLightning.Application.Channels.Handlers;
 5
 6using Domain.Channels.Enums;
 7using Domain.Channels.Interfaces;
 8using Domain.Channels.Models;
 9using Domain.Crypto.ValueObjects;
 10using Domain.Enums;
 11using Domain.Exceptions;
 12using Domain.Node.Options;
 13using Domain.Persistence.Interfaces;
 14using Domain.Protocol.Interfaces;
 15using Domain.Protocol.Messages;
 16using Interfaces;
 17
 18public class ChannelReadyMessageHandler : IChannelMessageHandler<ChannelReadyMessage>
 19{
 20    private readonly IChannelMemoryRepository _channelMemoryRepository;
 21    private readonly ILogger<ChannelReadyMessageHandler> _logger;
 22    private readonly IUnitOfWork _unitOfWork;
 23
 024    public ChannelReadyMessageHandler(IChannelMemoryRepository channelMemoryRepository,
 025                                      ILogger<ChannelReadyMessageHandler> logger, IUnitOfWork unitOfWork)
 26    {
 027        _channelMemoryRepository = channelMemoryRepository;
 028        _logger = logger;
 029        _unitOfWork = unitOfWork;
 030    }
 31
 32    public async Task<IChannelMessage?> HandleAsync(ChannelReadyMessage message, ChannelState currentState,
 33                                                    FeatureOptions negotiatedFeatures, CompactPubKey peerPubKey)
 34    {
 035        if (_logger.IsEnabled(LogLevel.Trace))
 036            _logger.LogTrace("Processing ChannelReadyMessage with ChannelId: {ChannelId} from Peer: {PeerPubKey}",
 037                             message.Payload.ChannelId, peerPubKey);
 38
 039        var payload = message.Payload;
 40
 041        if (currentState is not (ChannelState.V1FundingSigned
 042                              or ChannelState.ReadyForThem
 043                              or ChannelState.ReadyForUs
 044                              or ChannelState.Open))
 045            throw new ChannelErrorException(
 046                $"Unexpected ChannelReady message in state {Enum.GetName(currentState)}",
 047                payload.ChannelId,
 048                "Protocol violation: unexpected ChannelReady message");
 49
 50        // Check if there's a channel for this peer
 051        if (!_channelMemoryRepository.TryGetChannel(payload.ChannelId, out var channel))
 052            throw new ChannelErrorException("Channel not found", payload.ChannelId,
 053                                            "This channel is not ready to be opened");
 54
 055        var mustUseScidAlias = channel.ChannelConfig.UseScidAlias > FeatureSupport.No;
 056        if (mustUseScidAlias && message.ShortChannelIdTlv is null)
 057            throw new ChannelWarningException("No ShortChannelIdTlv provided",
 058                                              payload.ChannelId,
 059                                              "This channel requires a ShortChannelIdTlv to be provided");
 60
 61        // Store their new per-commitment point
 062        if (channel.RemoteKeySet!.CurrentPerCommitmentIndex == 0)
 063            channel.RemoteKeySet.UpdatePerCommitmentPoint(payload.SecondPerCommitmentPoint);
 64
 65        switch (currentState)
 66        {
 67            case ChannelState.Open or ChannelState.ReadyForThem: // Handle ScidAlias
 68                {
 069                    if (mustUseScidAlias)
 70                    {
 071                        if (ShouldReplaceAlias())
 72                        {
 073                            var oldAlias = channel.RemoteAlias;
 074                            channel.RemoteAlias = message.ShortChannelIdTlv!.ShortChannelId;
 75
 076                            if (_logger.IsEnabled(LogLevel.Debug))
 077                                _logger.LogDebug(
 078                                    "Updated remote alias for channel {ChannelId} from {OldAlias} to {NewAlias}",
 079                                    payload.ChannelId, oldAlias, channel.RemoteAlias);
 80
 081                            await PersistChannelAsync(channel);
 82                        }
 083                        else if (_logger.IsEnabled(LogLevel.Debug))
 84                        {
 085                            _logger.LogDebug(
 086                                "Keeping existing remote alias {ExistingAlias} for channel {ChannelId}",
 087                                channel.RemoteAlias,
 088                                payload.ChannelId);
 89                        }
 90                    }
 091                    else if (_logger.IsEnabled(LogLevel.Debug))
 092                        _logger.LogDebug("Received duplicate ChannelReady message for channel {ChannelId} in Open state"
 093                                         payload.ChannelId);
 94
 095                    break;
 96                }
 97            case ChannelState.ReadyForUs: // We already sent our ChannelReady, now they sent theirs
 98                {
 99                    // Valid transition: ReadyForUs -> Open
 0100                    channel.UpdateState(ChannelState.Open);
 0101                    await PersistChannelAsync(channel);
 102
 0103                    if (_logger.IsEnabled(LogLevel.Information))
 0104                        _logger.LogInformation("Channel {ChannelId} is now open", payload.ChannelId);
 105
 106                    // TODO: Notify application layer that channel is fully open
 107                    // TODO: Update routing tables
 108
 0109                    break;
 110                }
 111            case ChannelState.V1FundingSigned: // First ChannelReady
 112                {
 113                    // Valid transition: V1FundingSigned -> ReadyForThem
 0114                    channel.UpdateState(ChannelState.ReadyForThem);
 0115                    await PersistChannelAsync(channel);
 116
 0117                    if (_logger.IsEnabled(LogLevel.Information))
 0118                        _logger.LogInformation(
 0119                            "Received ChannelReady from peer for channel {ChannelId}, waiting for funding confirmation",
 0120                            payload.ChannelId);
 121
 122                    break;
 123                }
 124        }
 125
 0126        return null; // No further action needed
 0127    }
 128
 129    /// <summary>
 130    /// Persists a channel to the database using a scoped Unit of Work
 131    /// </summary>
 132    private async Task PersistChannelAsync(ChannelModel channel)
 133    {
 134        try
 135        {
 136            // Check if the channel already exists
 0137            _ = await _unitOfWork.ChannelDbRepository.GetByIdAsync(channel.ChannelId)
 0138             ?? throw new ChannelWarningException("Channel not found in database", channel.ChannelId,
 0139                                                  "Sorry, we had an internal error");
 0140            await _unitOfWork.ChannelDbRepository.UpdateAsync(channel);
 0141            await _unitOfWork.SaveChangesAsync();
 142
 0143            _channelMemoryRepository.UpdateChannel(channel);
 144
 0145            if (_logger.IsEnabled(LogLevel.Debug))
 0146                _logger.LogDebug("Successfully persisted channel {ChannelId} to database", channel.ChannelId);
 0147        }
 0148        catch (Exception ex)
 149        {
 0150            _logger.LogError(ex, "Failed to persist channel {ChannelId} to database", channel.ChannelId);
 0151            throw;
 152        }
 0153    }
 154
 155    private static bool ShouldReplaceAlias()
 156    {
 0157        return RandomNumberGenerator.GetInt32(0, 2) switch
 0158        {
 0159            0 => true,
 0160            _ => false
 0161        };
 162    }
 163}