< 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: 57_24045730253
Line coverage
0%
Covered lines: 0
Uncovered lines: 198
Coverable lines: 198
Total lines: 269
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);
 35
 036        Insert(channelEntity);
 037    }
 38
 39    public async Task UpdateAsync(ChannelModel channelModel)
 040    {
 041        var channelEntity = await MapDomainToEntity(channelModel, _messageSerializer);
 042        Update(channelEntity);
 043    }
 44
 45    public async Task<ChannelModel?> GetByIdAsync(ChannelId channelId)
 046    {
 047        var channelEntity = await DbSet
 048                                 .AsNoTracking()
 049                                 .Include(c => c.Config)
 050                                 .Include(c => c.KeySets)
 051                                 .Include(c => c.Htlcs)
 052                                 .FirstOrDefaultAsync(c => c.ChannelId == channelId);
 53
 054        if (channelEntity is null)
 055            return null;
 56
 057        return await MapEntityToDomain(channelEntity, _messageSerializer, _sha256);
 058    }
 59
 60    public async Task<IEnumerable<ChannelModel>> GetAllAsync()
 061    {
 062        var channelEntities = await DbSet
 063                                   .AsNoTracking()
 064                                   .Include(c => c.Config)
 065                                   .Include(c => c.KeySets)
 066                                   .Include(c => c.Htlcs)
 067                                   .ToListAsync();
 68
 069        return await Task.WhenAll(
 070                   channelEntities.Select(async entity =>
 071                                              await MapEntityToDomain(entity, _messageSerializer, _sha256)));
 072    }
 73
 74    public async Task<IEnumerable<ChannelModel>> GetReadyChannelsAsync()
 075    {
 076        byte[] readyStateList =
 077        [
 078            (byte)ChannelState.V1FundingCreated,
 079            (byte)ChannelState.V1FundingSigned,
 080            (byte)ChannelState.ReadyForThem,
 081            (byte)ChannelState.ReadyForUs,
 082            (byte)ChannelState.Open,
 083            (byte)ChannelState.Closing
 084        ];
 85
 086        var channelEntities = await DbSet
 087                                   .AsNoTracking()
 088                                   .Include(c => c.Config)
 089                                   .Include(c => c.KeySets)
 090                                   .Include(c => c.Htlcs)
 091                                   .Where(c => readyStateList.Contains(c.State))
 092                                   .ToListAsync();
 93
 094        return await Task.WhenAll(
 095                   channelEntities.Select(async entity =>
 096                                              await MapEntityToDomain(entity, _messageSerializer, _sha256)));
 097    }
 98
 99    public async Task<IEnumerable<ChannelModel?>> GetByPeerIdAsync(CompactPubKey peerNodeId)
 0100    {
 0101        var channelEntities = await DbSet
 0102                                   .AsNoTracking()
 0103                                   .Include(c => c.Config)
 0104                                   .Include(c => c.KeySets)
 0105                                   .Include(c => c.Htlcs)
 0106                                   .Where(c => c.RemoteNodeId.Equals(peerNodeId))
 0107                                   .ToListAsync();
 108
 0109        return await Task.WhenAll(
 0110                   channelEntities.Select(async entity =>
 0111                                              await MapEntityToDomain(entity, _messageSerializer, _sha256)));
 0112    }
 113
 114    internal static async Task<ChannelEntity> MapDomainToEntity(ChannelModel channelModel,
 115                                                                IMessageSerializer messageSerializer)
 0116    {
 0117        var config = ChannelConfigDbRepository.MapDomainToEntity(channelModel.ChannelId, channelModel.ChannelConfig);
 0118        ImmutableArray<ChannelKeySetEntity> keySets =
 0119        [
 0120            ChannelKeySetDbRepository.MapDomainToEntity(channelModel.ChannelId, true, channelModel.LocalKeySet),
 0121            ChannelKeySetDbRepository.MapDomainToEntity(channelModel.ChannelId, false, channelModel.RemoteKeySet)
 0122        ];
 123
 0124        var htlcs = new List<Htlc>();
 0125        htlcs.AddRange(GetHtlcsOrNull(channelModel.LocalOfferedHtlcs));
 0126        htlcs.AddRange(GetHtlcsOrNull(channelModel.LocalFulfilledHtlcs));
 0127        htlcs.AddRange(GetHtlcsOrNull(channelModel.LocalOldHtlcs));
 0128        htlcs.AddRange(GetHtlcsOrNull(channelModel.RemoteOfferedHtlcs));
 0129        htlcs.AddRange(GetHtlcsOrNull(channelModel.RemoteFulfilledHtlcs));
 0130        htlcs.AddRange(GetHtlcsOrNull(channelModel.RemoteOldHtlcs));
 131
 0132        List<HtlcEntity>? htlcEntities = null;
 0133        if (htlcs.Count > 0)
 0134        {
 0135            htlcEntities = [];
 136
 0137            foreach (var htlc in htlcs)
 0138                htlcEntities.Add(
 0139                    await HtlcDbRepository.MapDomainToEntityAsync(channelModel.ChannelId, htlc, messageSerializer));
 0140        }
 141
 0142        return new ChannelEntity
 0143        {
 0144            ChannelId = channelModel.ChannelId,
 0145
 0146            FundingCreatedAtBlockHeight = channelModel.FundingCreatedAtBlockHeight,
 0147            FundingTxId = channelModel.FundingOutput.TransactionId ?? new byte[CryptoConstants.Sha256HashLen],
 0148            FundingOutputIndex = channelModel.FundingOutput.Index ?? 0,
 0149            FundingAmountSatoshis = channelModel.FundingOutput.Amount.Satoshi,
 0150
 0151            IsInitiator = channelModel.IsInitiator,
 0152            RemoteNodeId = channelModel.RemoteNodeId,
 0153            State = (byte)channelModel.State,
 0154            Version = (byte)channelModel.Version,
 0155
 0156            LocalBalanceSatoshis = channelModel.LocalBalance.Satoshi,
 0157            RemoteBalanceSatoshis = channelModel.RemoteBalance.Satoshi,
 0158
 0159            LocalNextHtlcId = channelModel.LocalNextHtlcId,
 0160            RemoteNextHtlcId = channelModel.RemoteNextHtlcId,
 0161            LocalRevocationNumber = channelModel.LocalRevocationNumber,
 0162            RemoteRevocationNumber = channelModel.RemoteRevocationNumber,
 0163            LastSentSignature = channelModel.LastSentSignature?.Value ?? null,
 0164            LastReceivedSignature = channelModel.LastReceivedSignature?.Value ?? null,
 0165
 0166            Config = config,
 0167            KeySets = keySets,
 0168            Htlcs = htlcEntities
 0169        };
 0170    }
 171
 172    internal static async Task<ChannelModel> MapEntityToDomain(ChannelEntity channelEntity,
 173                                                               IMessageSerializer messageSerializer, ISha256 sha256)
 0174    {
 0175        if (channelEntity.Config is null)
 0176            throw new InvalidOperationException(
 0177                "Channel config cannot be null when mapping channel entity to domain model.");
 178
 0179        if (channelEntity.KeySets is not { Count: 2 })
 0180            throw new InvalidOperationException(
 0181                "Channel key sets must contain exactly two entries when mapping channel entity to domain model.");
 182
 0183        var localKeySetEntity = channelEntity.KeySets.FirstOrDefault(k => k.IsLocal) ??
 0184                                throw new InvalidOperationException(
 0185                                    "Local key set cannot be null when mapping channel entity to domain model.");
 0186        var remoteKeySetEntity = channelEntity.KeySets.FirstOrDefault(k => !k.IsLocal) ??
 0187                                 throw new InvalidOperationException(
 0188                                     "Remote key set cannot be null when mapping channel entity to domain model.");
 0189        var config = ChannelConfigDbRepository.MapEntityToDomain(channelEntity.Config);
 0190        var localKeySet = ChannelKeySetDbRepository.MapEntityToDomain(localKeySetEntity);
 0191        var remoteKeySet = ChannelKeySetDbRepository.MapEntityToDomain(remoteKeySetEntity);
 192
 0193        var localOfferedHtlcs = new List<Htlc>();
 0194        var localFulfilledHtlcs = new List<Htlc>();
 0195        var localOldHtlcs = new List<Htlc>();
 0196        var remoteOfferedHtlcs = new List<Htlc>();
 0197        var remoteFulfilledHtlcs = new List<Htlc>();
 0198        var remoteOldHtlcs = new List<Htlc>();
 0199        if (channelEntity.Htlcs is { Count: > 0 })
 0200        {
 0201            foreach (var htlc in channelEntity.Htlcs.Where(h => h.State.Equals(HtlcState.Offered)))
 0202            {
 0203                var domainHtlc = await HtlcDbRepository.MapEntityToDomainAsync(htlc, messageSerializer);
 0204                if (htlc.Direction.Equals(HtlcDirection.Outgoing))
 0205                    localOfferedHtlcs.Add(domainHtlc);
 206                else
 0207                    remoteOfferedHtlcs.Add(domainHtlc);
 0208            }
 209
 0210            foreach (var htlc in channelEntity.Htlcs.Where(h => h.State.Equals(HtlcState.Fulfilled)))
 0211            {
 0212                var domainHtlc = await HtlcDbRepository.MapEntityToDomainAsync(htlc, messageSerializer);
 0213                if (htlc.Direction.Equals(HtlcDirection.Outgoing))
 0214                    localFulfilledHtlcs.Add(domainHtlc);
 215                else
 0216                    remoteFulfilledHtlcs.Add(domainHtlc);
 0217            }
 218
 0219            byte[] oldStates = [(byte)HtlcState.Expired, (byte)HtlcState.Failed];
 0220            foreach (var htlc in channelEntity.Htlcs.Where(h => oldStates.Contains(h.State)))
 0221            {
 0222                var domainHtlc = await HtlcDbRepository.MapEntityToDomainAsync(htlc, messageSerializer);
 0223                if (htlc.Direction.Equals(HtlcDirection.Outgoing))
 0224                    localOldHtlcs.Add(domainHtlc);
 225                else
 0226                    remoteOldHtlcs.Add(domainHtlc);
 0227            }
 0228        }
 229
 0230        var fundingOutput = new FundingOutputInfo(LightningMoney.Satoshis(channelEntity.FundingAmountSatoshis),
 0231                                                  localKeySet.FundingCompactPubKey, localKeySet.FundingCompactPubKey)
 0232        {
 0233            Index = channelEntity.FundingOutputIndex,
 0234            TransactionId = channelEntity.FundingTxId
 0235        };
 236
 0237        var commitmentNumber =
 0238            new CommitmentNumber(localKeySet.PaymentCompactBasepoint, remoteKeySet.PaymentCompactBasepoint, sha256,
 0239                                 channelEntity.LocalRevocationNumber + 1);
 240
 0241        var remoteNodeId = channelEntity.RemoteNodeId;
 242
 0243        CompactSignature? lastSentSig = null;
 0244        if (channelEntity.LastSentSignature != null)
 0245            lastSentSig = new CompactSignature(channelEntity.LastSentSignature);
 246
 0247        CompactSignature? lastReceivedSig = null;
 0248        if (channelEntity.LastReceivedSignature != null)
 0249            lastReceivedSig = new CompactSignature(channelEntity.LastReceivedSignature);
 250
 0251        return new ChannelModel(config, channelEntity.ChannelId, commitmentNumber, fundingOutput,
 0252                                channelEntity.IsInitiator, lastSentSig, lastReceivedSig,
 0253                                LightningMoney.Satoshis(channelEntity.LocalBalanceSatoshis), localKeySet,
 0254                                channelEntity.LocalNextHtlcId, channelEntity.LocalRevocationNumber,
 0255                                LightningMoney.Satoshis(channelEntity.RemoteBalanceSatoshis), remoteKeySet,
 0256                                channelEntity.RemoteNextHtlcId, remoteNodeId, channelEntity.RemoteRevocationNumber,
 0257                                (ChannelState)channelEntity.State, (ChannelVersion)channelEntity.Version,
 0258                                localOfferedHtlcs, localFulfilledHtlcs, localOldHtlcs, null, remoteOfferedHtlcs,
 0259                                remoteFulfilledHtlcs, remoteOldHtlcs)
 0260        {
 0261            FundingCreatedAtBlockHeight = channelEntity.FundingCreatedAtBlockHeight
 0262        };
 0263    }
 264
 265    private static ICollection<Htlc> GetHtlcsOrNull(ICollection<Htlc>? htlcs)
 0266    {
 0267        return htlcs is { Count: > 0 } ? htlcs : [];
 0268    }
 269}