< 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: 36_15743069263
Line coverage
0%
Covered lines: 0
Uncovered lines: 82
Coverable lines: 82
Total lines: 175
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 34
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%930300%
PersistChannelAsync()0%620%
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        _logger.LogTrace("Processing ChannelReadyMessage with ChannelId: {ChannelId} from Peer: {PeerPubKey}",
 036                         message.Payload.ChannelId, peerPubKey);
 37
 038        var payload = message.Payload;
 39
 040        if (currentState is not (ChannelState.V1FundingSigned
 041                              or ChannelState.ReadyForThem
 042                              or ChannelState.ReadyForUs
 043                              or ChannelState.Open))
 044            throw new ChannelErrorException("Channel had the wrong state", payload.ChannelId,
 045                                            "This channel is not ready to be opened");
 46
 47        // Check if there's a channel for this peer
 048        if (!_channelMemoryRepository.TryGetChannel(payload.ChannelId, out var channel))
 049            throw new ChannelErrorException("Channel not found", payload.ChannelId,
 050                                            "This channel is not ready to be opened");
 51
 052        var mustUseScidAlias = channel.ChannelConfig.UseScidAlias > FeatureSupport.No;
 053        if (mustUseScidAlias && message.ShortChannelIdTlv is null)
 054            throw new ChannelWarningException("No ShortChannelIdTlv provided",
 055                                              payload.ChannelId,
 056                                              "This channel requires a ShortChannelIdTlv to be provided");
 57
 58        // Store their new per-commitment point
 059        if (channel.RemoteKeySet.CurrentPerCommitmentIndex == 0)
 060            channel.RemoteKeySet.UpdatePerCommitmentPoint(payload.SecondPerCommitmentPoint);
 61
 62        // Handle ScidAlias
 063        if (currentState is ChannelState.Open or ChannelState.ReadyForThem)
 64        {
 065            if (mustUseScidAlias)
 66            {
 067                if (ShouldReplaceAlias())
 68                {
 069                    var oldAlias = channel.RemoteAlias;
 070                    channel.RemoteAlias = message.ShortChannelIdTlv!.ShortChannelId;
 71
 072                    _logger.LogDebug("Updated remote alias for channel {ChannelId} from {OldAlias} to {NewAlias}",
 073                                     payload.ChannelId, oldAlias, channel.RemoteAlias);
 74
 075                    await PersistChannelAsync(channel);
 76                }
 77                else
 78                {
 079                    _logger.LogDebug(
 080                        "Keeping existing remote alias {ExistingAlias} for channel {ChannelId}", channel.RemoteAlias,
 081                        payload.ChannelId);
 82                }
 83            }
 84            else
 085                _logger.LogDebug("Received duplicate ChannelReady message for channel {ChannelId} in Open state",
 086                                 payload.ChannelId);
 87
 088            return null; // No further action needed, we are already open
 89        }
 90
 091        if (channel.IsInitiator) // Handle state transitions based on whether we are the initiator
 92        {
 93            // We already sent our ChannelReady, now they sent theirs
 094            if (currentState == ChannelState.ReadyForUs)
 95            {
 96                // Valid transition: ReadyForUs -> Open
 097                channel.UpdateState(ChannelState.Open);
 098                await PersistChannelAsync(channel);
 99
 0100                _logger.LogInformation("Channel {ChannelId} is now open (we are initiator)", payload.ChannelId);
 101
 102                // TODO: Notify application layer that channel is fully open
 103                // TODO: Update routing tables
 104
 0105                return null;
 106            }
 107
 108            // Invalid state for initiator receiving ChannelReady
 0109            _logger.LogError(
 0110                "Received ChannelReady message for channel {ChannelId} in invalid state {CurrentState} (we are initiator
 0111                payload.ChannelId, currentState);
 112
 0113            throw new ChannelErrorException($"Unexpected ChannelReady message in state {Enum.GetName(currentState)}",
 0114                                            payload.ChannelId,
 0115                                            "Protocol violation: unexpected ChannelReady message");
 116        }
 117
 0118        if (currentState == ChannelState.V1FundingSigned) // We are not the initiator
 119        {
 120            // First ChannelReady from initiator
 121            // Valid transition: V1FundingSigned -> ReadyForThem
 0122            channel.UpdateState(ChannelState.ReadyForThem);
 0123            await PersistChannelAsync(channel);
 124
 0125            _logger.LogInformation(
 0126                "Received ChannelReady from initiator for channel {ChannelId}, waiting for funding confirmation",
 0127                payload.ChannelId);
 128
 0129            return null;
 130        }
 131
 132        // Invalid state for non-initiator receiving ChannelReady
 0133        _logger.LogError(
 0134            "Received ChannelReady message for channel {ChannelId} in invalid state {CurrentState} (we are not initiator
 0135            payload.ChannelId, currentState);
 136
 0137        throw new ChannelErrorException($"Unexpected ChannelReady message in state {Enum.GetName(currentState)}",
 0138                                        payload.ChannelId,
 0139                                        "Protocol violation: unexpected ChannelReady message");
 0140    }
 141
 142    /// <summary>
 143    /// Persists a channel to the database using a scoped Unit of Work
 144    /// </summary>
 145    private async Task PersistChannelAsync(ChannelModel channel)
 146    {
 147        try
 148        {
 149            // Check if the channel already exists
 0150            _ = await _unitOfWork.ChannelDbRepository.GetByIdAsync(channel.ChannelId)
 0151             ?? throw new ChannelWarningException("Channel not found in database", channel.ChannelId,
 0152                                                  "Sorry, we had an internal error");
 0153            await _unitOfWork.ChannelDbRepository.UpdateAsync(channel);
 0154            await _unitOfWork.SaveChangesAsync();
 155
 0156            _channelMemoryRepository.UpdateChannel(channel);
 157
 0158            _logger.LogDebug("Successfully persisted channel {ChannelId} to database", channel.ChannelId);
 0159        }
 0160        catch (Exception ex)
 161        {
 0162            _logger.LogError(ex, "Failed to persist channel {ChannelId} to database", channel.ChannelId);
 0163            throw;
 164        }
 0165    }
 166
 167    private static bool ShouldReplaceAlias()
 168    {
 0169        return RandomNumberGenerator.GetInt32(0, 2) switch
 0170        {
 0171            0 => true,
 0172            _ => false
 0173        };
 174    }
 175}