< Summary - Combined Code Coverage

Information
Class: NLightning.Infrastructure.Repositories.Database.Channel.ChannelDbRepository
Assembly: NLightning.Infrastructure.Repositories
File(s): /home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure.Repositories/Database/Channel/ChannelDbRepository.cs
Tag: 36_15743069263
Line coverage
0%
Covered lines: 0
Uncovered lines: 198
Coverable lines: 198
Total lines: 268
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 44
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(...)0%2040%
AddAsync()100%210%
AddAsync()100%210%
UpdateAsync()100%210%
UpdateAsync()100%210%
GetByIdAsync()0%620%
GetByIdAsync()0%620%
GetAllAsync()100%210%
GetAllAsync()100%210%
<GetAllAsync()100%210%
GetReadyChannelsAsync()100%210%
GetReadyChannelsAsync()100%210%
<GetReadyChannelsAsync()100%210%
GetByPeerIdAsync()100%210%
GetByPeerIdAsync()100%210%
<GetByPeerIdAsync()100%210%
MapDomainToEntity()0%210140%
MapDomainToEntity()0%210140%
MapEntityToDomain()0%1056320%
MapEntityToDomain()0%1056320%
GetHtlcsOrNull(...)0%2040%
GetHtlcsOrNull(...)0%2040%

File(s)

/home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure.Repositories/Database/Channel/ChannelDbRepository.cs

#LineLine coverage
 1using System.Collections.Immutable;
 2using Microsoft.EntityFrameworkCore;
 3using NLightning.Domain.Protocol.Models;
 4
 5namespace NLightning.Infrastructure.Repositories.Database.Channel;
 6
 7using Domain.Bitcoin.Transactions.Outputs;
 8using Domain.Channels.Enums;
 9using Domain.Channels.Interfaces;
 10using Domain.Channels.Models;
 11using Domain.Channels.ValueObjects;
 12using Domain.Crypto.Constants;
 13using Domain.Crypto.Hashes;
 14using Domain.Crypto.ValueObjects;
 15using Domain.Money;
 16using Domain.Serialization.Interfaces;
 17using Persistence.Contexts;
 18using Persistence.Entities.Channel;
 19
 20public class ChannelDbRepository : BaseDbRepository<ChannelEntity>, IChannelDbRepository
 21{
 22    private readonly IMessageSerializer _messageSerializer;
 23    private readonly ISha256 _sha256;
 24
 25    public ChannelDbRepository(NLightningDbContext context, IMessageSerializer messageSerializer, ISha256 sha256)
 026        : base(context)
 027    {
 028        _messageSerializer = messageSerializer ?? throw new ArgumentNullException(nameof(messageSerializer));
 029        _sha256 = sha256 ?? throw new ArgumentNullException(nameof(sha256));
 030    }
 31
 32    public async Task AddAsync(ChannelModel channelModel)
 033    {
 034        var channelEntity = await MapDomainToEntity(channelModel, _messageSerializer);
 035        Insert(channelEntity);
 036    }
 37
 38    public async Task UpdateAsync(ChannelModel channelModel)
 039    {
 040        var channelEntity = await MapDomainToEntity(channelModel, _messageSerializer);
 041        Update(channelEntity);
 042    }
 43
 44    public async Task<ChannelModel?> GetByIdAsync(ChannelId channelId)
 045    {
 046        var channelEntity = await DbSet
 047                                 .AsNoTracking()
 048                                 .Include(c => c.Config)
 049                                 .Include(c => c.KeySets)
 050                                 .Include(c => c.Htlcs)
 051                                 .FirstOrDefaultAsync(c => c.ChannelId == channelId);
 52
 053        if (channelEntity is null)
 054            return null;
 55
 056        return await MapEntityToDomain(channelEntity, _messageSerializer, _sha256);
 057    }
 58
 59    public async Task<IEnumerable<ChannelModel>> GetAllAsync()
 060    {
 061        var channelEntities = await DbSet
 062                                   .AsNoTracking()
 063                                   .Include(c => c.Config)
 064                                   .Include(c => c.KeySets)
 065                                   .Include(c => c.Htlcs)
 066                                   .ToListAsync();
 67
 068        return await Task.WhenAll(
 069                   channelEntities.Select(async entity =>
 070                                              await MapEntityToDomain(entity, _messageSerializer, _sha256)));
 071    }
 72
 73    public async Task<IEnumerable<ChannelModel>> GetReadyChannelsAsync()
 074    {
 075        byte[] readyStateList =
 076        [
 077            (byte)ChannelState.V1FundingCreated,
 078            (byte)ChannelState.V1FundingSigned,
 079            (byte)ChannelState.ReadyForThem,
 080            (byte)ChannelState.ReadyForUs,
 081            (byte)ChannelState.Open,
 082            (byte)ChannelState.Closing
 083        ];
 84
 085        var channelEntities = await DbSet
 086                                   .AsNoTracking()
 087                                   .Include(c => c.Config)
 088                                   .Include(c => c.KeySets)
 089                                   .Include(c => c.Htlcs)
 090                                   .Where(c => readyStateList.Contains(c.State))
 091                                   .ToListAsync();
 92
 093        return await Task.WhenAll(
 094                   channelEntities.Select(async entity =>
 095                                              await MapEntityToDomain(entity, _messageSerializer, _sha256)));
 096    }
 97
 98    public async Task<IEnumerable<ChannelModel?>> GetByPeerIdAsync(CompactPubKey peerNodeId)
 099    {
 0100        var channelEntities = await DbSet
 0101                                   .AsNoTracking()
 0102                                   .Include(c => c.Config)
 0103                                   .Include(c => c.KeySets)
 0104                                   .Include(c => c.Htlcs)
 0105                                   .Where(c => c.RemoteNodeId.Equals(peerNodeId))
 0106                                   .ToListAsync();
 107
 0108        return await Task.WhenAll(
 0109                   channelEntities.Select(async entity =>
 0110                                              await MapEntityToDomain(entity, _messageSerializer, _sha256)));
 0111    }
 112
 113    internal static async Task<ChannelEntity> MapDomainToEntity(ChannelModel channelModel,
 114                                                                IMessageSerializer messageSerializer)
 0115    {
 0116        var config = ChannelConfigDbRepository.MapDomainToEntity(channelModel.ChannelId, channelModel.ChannelConfig);
 0117        ImmutableArray<ChannelKeySetEntity> keySets =
 0118        [
 0119            ChannelKeySetDbRepository.MapDomainToEntity(channelModel.ChannelId, true, channelModel.LocalKeySet),
 0120            ChannelKeySetDbRepository.MapDomainToEntity(channelModel.ChannelId, false, channelModel.RemoteKeySet)
 0121        ];
 122
 0123        var htlcs = new List<Htlc>();
 0124        htlcs.AddRange(GetHtlcsOrNull(channelModel.LocalOfferedHtlcs));
 0125        htlcs.AddRange(GetHtlcsOrNull(channelModel.LocalFulfilledHtlcs));
 0126        htlcs.AddRange(GetHtlcsOrNull(channelModel.LocalOldHtlcs));
 0127        htlcs.AddRange(GetHtlcsOrNull(channelModel.RemoteOfferedHtlcs));
 0128        htlcs.AddRange(GetHtlcsOrNull(channelModel.RemoteFulfilledHtlcs));
 0129        htlcs.AddRange(GetHtlcsOrNull(channelModel.RemoteOldHtlcs));
 130
 0131        List<HtlcEntity>? htlcEntities = null;
 0132        if (htlcs.Count > 0)
 0133        {
 0134            htlcEntities = [];
 135
 0136            foreach (var htlc in htlcs)
 0137                htlcEntities.Add(
 0138                    await HtlcDbRepository.MapDomainToEntityAsync(channelModel.ChannelId, htlc, messageSerializer));
 0139        }
 140
 0141        return new ChannelEntity
 0142        {
 0143            ChannelId = channelModel.ChannelId,
 0144
 0145            FundingCreatedAtBlockHeight = channelModel.FundingCreatedAtBlockHeight,
 0146            FundingTxId = channelModel.FundingOutput.TransactionId ?? new byte[CryptoConstants.Sha256HashLen],
 0147            FundingOutputIndex = channelModel.FundingOutput.Index ?? 0,
 0148            FundingAmountSatoshis = channelModel.FundingOutput.Amount.Satoshi,
 0149
 0150            IsInitiator = channelModel.IsInitiator,
 0151            RemoteNodeId = channelModel.RemoteNodeId,
 0152            State = (byte)channelModel.State,
 0153            Version = (byte)channelModel.Version,
 0154
 0155            LocalBalanceSatoshis = channelModel.LocalBalance.Satoshi,
 0156            RemoteBalanceSatoshis = channelModel.RemoteBalance.Satoshi,
 0157
 0158            LocalNextHtlcId = channelModel.LocalNextHtlcId,
 0159            RemoteNextHtlcId = channelModel.RemoteNextHtlcId,
 0160            LocalRevocationNumber = channelModel.LocalRevocationNumber,
 0161            RemoteRevocationNumber = channelModel.RemoteRevocationNumber,
 0162            LastSentSignature = channelModel.LastSentSignature?.Value ?? null,
 0163            LastReceivedSignature = channelModel.LastReceivedSignature?.Value ?? null,
 0164
 0165            Config = config,
 0166            KeySets = keySets,
 0167            Htlcs = htlcEntities
 0168        };
 0169    }
 170
 171    internal static async Task<ChannelModel> MapEntityToDomain(ChannelEntity channelEntity,
 172                                                               IMessageSerializer messageSerializer, ISha256 sha256)
 0173    {
 0174        if (channelEntity.Config is null)
 0175            throw new InvalidOperationException(
 0176                "Channel config cannot be null when mapping channel entity to domain model.");
 177
 0178        if (channelEntity.KeySets is not { Count: 2 })
 0179            throw new InvalidOperationException(
 0180                "Channel key sets must contain exactly two entries when mapping channel entity to domain model.");
 181
 0182        var localKeySetEntity = channelEntity.KeySets.FirstOrDefault(k => k.IsLocal) ??
 0183                                throw new InvalidOperationException(
 0184                                    "Local key set cannot be null when mapping channel entity to domain model.");
 0185        var remoteKeySetEntity = channelEntity.KeySets.FirstOrDefault(k => !k.IsLocal) ??
 0186                                 throw new InvalidOperationException(
 0187                                     "Remote key set cannot be null when mapping channel entity to domain model.");
 0188        var config = ChannelConfigDbRepository.MapEntityToDomain(channelEntity.Config);
 0189        var localKeySet = ChannelKeySetDbRepository.MapEntityToDomain(localKeySetEntity);
 0190        var remoteKeySet = ChannelKeySetDbRepository.MapEntityToDomain(remoteKeySetEntity);
 191
 0192        var localOfferedHtlcs = new List<Htlc>();
 0193        var localFulfilledHtlcs = new List<Htlc>();
 0194        var localOldHtlcs = new List<Htlc>();
 0195        var remoteOfferedHtlcs = new List<Htlc>();
 0196        var remoteFulfilledHtlcs = new List<Htlc>();
 0197        var remoteOldHtlcs = new List<Htlc>();
 0198        if (channelEntity.Htlcs is { Count: > 0 })
 0199        {
 0200            foreach (var htlc in channelEntity.Htlcs.Where(h => h.State.Equals(HtlcState.Offered)))
 0201            {
 0202                var domainHtlc = await HtlcDbRepository.MapEntityToDomainAsync(htlc, messageSerializer);
 0203                if (htlc.Direction.Equals(HtlcDirection.Outgoing))
 0204                    localOfferedHtlcs.Add(domainHtlc);
 205                else
 0206                    remoteOfferedHtlcs.Add(domainHtlc);
 0207            }
 208
 0209            foreach (var htlc in channelEntity.Htlcs.Where(h => h.State.Equals(HtlcState.Fulfilled)))
 0210            {
 0211                var domainHtlc = await HtlcDbRepository.MapEntityToDomainAsync(htlc, messageSerializer);
 0212                if (htlc.Direction.Equals(HtlcDirection.Outgoing))
 0213                    localFulfilledHtlcs.Add(domainHtlc);
 214                else
 0215                    remoteFulfilledHtlcs.Add(domainHtlc);
 0216            }
 217
 0218            byte[] oldStates = [(byte)HtlcState.Expired, (byte)HtlcState.Failed];
 0219            foreach (var htlc in channelEntity.Htlcs.Where(h => oldStates.Contains(h.State)))
 0220            {
 0221                var domainHtlc = await HtlcDbRepository.MapEntityToDomainAsync(htlc, messageSerializer);
 0222                if (htlc.Direction.Equals(HtlcDirection.Outgoing))
 0223                    localOldHtlcs.Add(domainHtlc);
 224                else
 0225                    remoteOldHtlcs.Add(domainHtlc);
 0226            }
 0227        }
 228
 0229        var fundingOutput = new FundingOutputInfo(LightningMoney.Satoshis(channelEntity.FundingAmountSatoshis),
 0230                                                  localKeySet.FundingCompactPubKey, localKeySet.FundingCompactPubKey)
 0231        {
 0232            Index = channelEntity.FundingOutputIndex,
 0233            TransactionId = channelEntity.FundingTxId
 0234        };
 235
 0236        var commitmentNumber =
 0237            new CommitmentNumber(localKeySet.PaymentCompactBasepoint, remoteKeySet.PaymentCompactBasepoint, sha256,
 0238                                 channelEntity.LocalRevocationNumber + 1);
 239
 0240        var remoteNodeId = channelEntity.RemoteNodeId;
 241
 0242        CompactSignature? lastSentSig = null;
 0243        if (channelEntity.LastSentSignature != null)
 0244            lastSentSig = new CompactSignature(channelEntity.LastSentSignature);
 245
 0246        CompactSignature? lastReceivedSig = null;
 0247        if (channelEntity.LastReceivedSignature != null)
 0248            lastReceivedSig = new CompactSignature(channelEntity.LastReceivedSignature);
 249
 0250        return new ChannelModel(config, channelEntity.ChannelId, commitmentNumber, fundingOutput,
 0251                                channelEntity.IsInitiator, lastSentSig, lastReceivedSig,
 0252                                LightningMoney.Satoshis(channelEntity.LocalBalanceSatoshis), localKeySet,
 0253                                channelEntity.LocalNextHtlcId, channelEntity.LocalRevocationNumber,
 0254                                LightningMoney.Satoshis(channelEntity.RemoteBalanceSatoshis), remoteKeySet,
 0255                                channelEntity.RemoteNextHtlcId, remoteNodeId, channelEntity.RemoteRevocationNumber,
 0256                                (ChannelState)channelEntity.State, (ChannelVersion)channelEntity.Version,
 0257                                localOfferedHtlcs, localFulfilledHtlcs, localOldHtlcs, null, remoteOfferedHtlcs,
 0258                                remoteFulfilledHtlcs, remoteOldHtlcs)
 0259        {
 0260            FundingCreatedAtBlockHeight = channelEntity.FundingCreatedAtBlockHeight
 0261        };
 0262    }
 263
 264    private static ICollection<Htlc> GetHtlcsOrNull(ICollection<Htlc>? htlcs)
 0265    {
 0266        return htlcs is { Count: > 0 } ? htlcs : [];
 0267    }
 268}