< Summary - Combined Code Coverage

Information
Class: NLightning.Domain.Channels.Factories.ChannelFactory
Assembly: NLightning.Domain
File(s): /home/runner/work/NLightning/NLightning/src/NLightning.Domain/Channels/Factories/ChannelFactory.cs
Tag: 57_24045730253
Line coverage
0%
Covered lines: 0
Uncovered lines: 191
Coverable lines: 191
Total lines: 266
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 140
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%
.ctor(...)100%210%
CreateChannelV1AsNonInitiatorAsync()0%506220%
CreateChannelV1AsNonInitiatorAsync()0%342180%
CreateChannelV1AsInitiatorAsync()0%2756520%
PerformOptionalChecks(...)0%156120%
PerformMandatoryChecks(...)0%1980440%
GetOurChannelReserveFromFundingAmount(...)100%210%

File(s)

/home/runner/work/NLightning/NLightning/src/NLightning.Domain/Channels/Factories/ChannelFactory.cs

#LineLine coverage
 1namespace NLightning.Domain.Channels.Factories;
 2
 3using Bitcoin.Interfaces;
 4using Bitcoin.Transactions.Constants;
 5using Bitcoin.Transactions.Outputs;
 6using Bitcoin.ValueObjects;
 7using Client.Requests;
 8using Constants;
 9using Crypto.Hashes;
 10using Crypto.ValueObjects;
 11using Domain.Enums;
 12using Enums;
 13using Exceptions;
 14using Interfaces;
 15using Models;
 16using Money;
 17using Node.Options;
 18using Protocol.Interfaces;
 19using Protocol.Messages;
 20using Protocol.Models;
 21using Validators.Parameters;
 22using ValueObjects;
 23
 24public class ChannelFactory : IChannelFactory
 25{
 26    private readonly IChannelIdFactory _channelIdFactory;
 27    private readonly IChannelOpenValidator _channelOpenValidator;
 28    private readonly IFeeService _feeService;
 29    private readonly ILightningSigner _lightningSigner;
 30    private readonly NodeOptions _nodeOptions;
 031    private readonly ISha256 _sha256;
 032
 033    public ChannelFactory(IChannelIdFactory channelIdFactory, IChannelOpenValidator channelOpenValidator,
 034                          IFeeService feeService, ILightningSigner lightningSigner, NodeOptions nodeOptions,
 035                          ISha256 sha256)
 036    {
 037        _channelIdFactory = channelIdFactory;
 038        _channelOpenValidator = channelOpenValidator;
 039        _feeService = feeService;
 040        _lightningSigner = lightningSigner;
 041        _nodeOptions = nodeOptions;
 042        _sha256 = sha256;
 043    }
 044
 45    public async Task<ChannelModel> CreateChannelV1AsNonInitiatorAsync(OpenChannel1Message message,
 46                                                                       FeatureOptions negotiatedFeatures,
 047                                                                       CompactPubKey remoteNodeId)
 048    {
 049        var payload = message.Payload;
 50
 051        // If dual fund is negotiated fail the channel
 052        if (negotiatedFeatures.DualFund == FeatureSupport.Compulsory)
 053            throw new ChannelErrorException("We can only accept dual fund channels");
 54
 055        // Perform optional checks for the channel
 056        var ourChannelReserveAmount = GetOurChannelReserveFromFundingAmount(payload.FundingAmount);
 057        _channelOpenValidator.PerformOptionalChecks(
 058            ChannelOpenOptionalValidationParameters.FromOpenChannel1Payload(payload, ourChannelReserveAmount));
 059
 60        // Perform mandatory checks for the channel
 061        var currentFee = await _feeService.GetFeeRatePerKwAsync();
 062        _channelOpenValidator.PerformMandatoryChecks(
 063            ChannelOpenMandatoryValidationParameters.FromOpenChannel1Payload(
 064                message.ChannelTypeTlv, currentFee, negotiatedFeatures, payload), out var minimumDepth);
 65
 066        // Check for the upfront shutdown script
 067        if (message.UpfrontShutdownScriptTlv is null
 068         && (negotiatedFeatures.UpfrontShutdownScript > FeatureSupport.No || message.ChannelTypeTlv is not null))
 069            throw new ChannelErrorException("Upfront shutdown script is required but not provided");
 70
 071        BitcoinScript? remoteUpfrontShutdownScript = null;
 072        if (message.UpfrontShutdownScriptTlv is not null && message.UpfrontShutdownScriptTlv.Value.Length > 0)
 073            remoteUpfrontShutdownScript = message.UpfrontShutdownScriptTlv.Value;
 74
 075        // Calculate the amounts
 076        var toLocalAmount = payload.PushAmount;
 077        var toRemoteAmount = payload.FundingAmount - payload.PushAmount;
 078
 079        // Generate local keys through the signer
 080        var localKeyIndex = _lightningSigner.CreateNewChannel(out var localBasepoints, out var firstPerCommitmentPoint);
 081
 82        // Create the local key set
 083        var localKeySet = new ChannelKeySetModel(localKeyIndex, localBasepoints.FundingPubKey,
 084                                                 localBasepoints.RevocationBasepoint, localBasepoints.PaymentBasepoint,
 085                                                 localBasepoints.DelayedPaymentBasepoint, localBasepoints.HtlcBasepoint,
 086                                                 firstPerCommitmentPoint);
 087
 088        // Create the remote key set from the message
 089        var remoteKeySet = ChannelKeySetModel.CreateForRemote(message.Payload.FundingPubKey,
 090                                                              message.Payload.RevocationBasepoint,
 091                                                              message.Payload.PaymentBasepoint,
 092                                                              message.Payload.DelayedPaymentBasepoint,
 093                                                              message.Payload.HtlcBasepoint,
 094                                                              message.Payload.FirstPerCommitmentPoint);
 95
 096        BitcoinScript? localUpfrontShutdownScript = null;
 97        // Generate our upfront shutdown script
 098        if (_nodeOptions.Features.UpfrontShutdownScript > FeatureSupport.No)
 99        {
 100            // Generate our upfront shutdown script
 0101            // TODO: Generate a script from the local key set
 0102            // localUpfrontShutdownScript = ;
 103        }
 0104
 0105        // Generate the channel configuration
 0106        var useScidAlias = FeatureSupport.No;
 0107        if (negotiatedFeatures.ScidAlias > FeatureSupport.No)
 108        {
 0109            if (message.ChannelTypeTlv?.Features.IsFeatureSet(Feature.OptionScidAlias, true) ?? false)
 0110                useScidAlias = FeatureSupport.Compulsory;
 0111            else
 0112                useScidAlias = FeatureSupport.Optional;
 0113        }
 0114
 0115        var channelConfig = new ChannelConfig(payload.ChannelReserveAmount, payload.FeeRatePerKw,
 0116                                              payload.HtlcMinimumAmount, _nodeOptions.DustLimitAmount,
 0117                                              payload.MaxAcceptedHtlcs, payload.MaxHtlcValueInFlight, minimumDepth,
 0118                                              negotiatedFeatures.OptionAnchors != FeatureSupport.No,
 0119                                              payload.DustLimitAmount, payload.ToSelfDelay, useScidAlias,
 0120                                              localUpfrontShutdownScript, remoteUpfrontShutdownScript);
 121
 122        // Generate the commitment number
 0123        var commitmentNumber = new CommitmentNumber(remoteKeySet.PaymentCompactBasepoint,
 0124                                                    localKeySet.PaymentCompactBasepoint, _sha256);
 125
 126        try
 0127        {
 0128            var fundingOutput = new FundingOutputInfo(payload.FundingAmount, localKeySet.FundingCompactPubKey,
 0129                                                      remoteKeySet.FundingCompactPubKey);
 130
 0131            // Create the channel
 0132            return new ChannelModel(channelConfig, payload.ChannelId, commitmentNumber, fundingOutput, false, null,
 0133                                    null, toLocalAmount, localKeySet, 1, 0, toRemoteAmount, remoteKeySet, 1,
 0134                                    remoteNodeId, 0, ChannelState.V1Opening, ChannelVersion.V1);
 0135        }
 0136        catch (Exception e)
 137        {
 0138            throw new ChannelErrorException("Error creating commitment transaction", e);
 139        }
 0140    }
 141
 142    public async Task<ChannelModel> CreateChannelV1AsInitiatorAsync(OpenChannelClientRequest request,
 143                                                                    FeatureOptions negotiatedFeatures,
 144                                                                    CompactPubKey remoteNodeId)
 145    {
 146        // If dual fund is negotiated fail the channel
 0147        if (negotiatedFeatures.DualFund == FeatureSupport.Compulsory)
 0148            throw new ChannelErrorException("We can only open dual fund channels to this peer");
 149
 150        // Check if the FundingAmount is too small
 0151        if (request.FundingAmount < _nodeOptions.MinimumChannelSize)
 0152            throw new ChannelErrorException(
 0153                $"Funding amount is smaller than our MinimumChannelSize: {request.FundingAmount} < {_nodeOptions.Minimum
 154
 155        // Check if our fee is too big
 0156        if (request.FeeRatePerKw is not null && request.FeeRatePerKw > ChannelConstants.MaxFeePerKw)
 0157            throw new ChannelErrorException($"Fee rate per kw is too large: {request.FeeRatePerKw}");
 0158
 0159        // Check if our fee is too big
 0160        if (request.FeeRatePerKw is not null && request.FeeRatePerKw < ChannelConstants.MinFeePerKw)
 0161            throw new ChannelErrorException($"Fee rate per kw is too small: {request.FeeRatePerKw}");
 0162
 0163        // Check if the dust limit is greater than the channel reserve amount
 0164        var channelReserveAmount = GetOurChannelReserveFromFundingAmount(request.FundingAmount);
 0165        if (request.ChannelReserveAmount is not null && request.ChannelReserveAmount > channelReserveAmount)
 0166            channelReserveAmount = request.ChannelReserveAmount;
 0167
 0168        var dustLimitAmount = ChannelConstants.MinDustLimitAmount;
 0169        if (request.DustLimitAmount is not null)
 0170        {
 171            // Check if dust_limit_satoshis is too small
 0172            if (request.DustLimitAmount < ChannelConstants.MinDustLimitAmount)
 0173                throw new ChannelErrorException($"Dust limit amount is too small: {request.DustLimitAmount}");
 0174
 0175            dustLimitAmount = request.DustLimitAmount;
 176        }
 0177
 0178        if (dustLimitAmount > channelReserveAmount)
 0179            channelReserveAmount = dustLimitAmount;
 180
 0181        // Check if there are enough funds to pay for fees
 0182        var currentFeeRatePerKw = request.FeeRatePerKw ?? await _feeService.GetFeeRatePerKwAsync();
 0183        var expectedWeight = negotiatedFeatures.OptionAnchors > FeatureSupport.No
 0184                                 ? TransactionConstants.InitialCommitmentTransactionWeightNoAnchor
 0185                                 : TransactionConstants.InitialCommitmentTransactionWeightWithAnchor;
 0186        var expectedFee = LightningMoney.Satoshis(expectedWeight * currentFeeRatePerKw.Satoshi / 1000);
 0187        if (request.FundingAmount < expectedFee + channelReserveAmount)
 0188            throw new ChannelErrorException($"Funding amount is too small to cover fees: {request.FundingAmount}");
 189
 190        // Check if this is a large channel and if we support it
 0191        if (request.FundingAmount >= ChannelConstants.LargeChannelAmount &&
 0192            negotiatedFeatures.LargeChannels == FeatureSupport.No)
 0193            throw new ChannelErrorException("The peer doesn't support large channels");
 194
 195        // Check if we want zeroconf and if it's negotiated
 0196        var minimumDepth = _nodeOptions.MinimumDepth;
 0197        if (request.IsZeroConfChannel)
 198        {
 0199            if (_nodeOptions.Features.ZeroConf == FeatureSupport.No)
 0200                throw new ChannelErrorException(
 0201                    "ZeroConf feature not supported, change our configuration and try again");
 202
 0203            if (negotiatedFeatures.ZeroConf == FeatureSupport.No)
 0204                throw new ChannelErrorException("ZeroConf not supported by our peer");
 205
 0206            minimumDepth = 0U;
 207        }
 208
 209        // Calculate the amounts
 0210        var toRemoteAmount = request.PushAmount ?? LightningMoney.Zero;
 0211        var toLocalAmount = request.FundingAmount - toRemoteAmount;
 212
 213        // Generate our MaxHtlcValueInFlight if not provided
 0214        var maxHtlcValueInFlight = request.MaxHtlcValueInFlight
 0215                                ?? LightningMoney.Satoshis(_nodeOptions.AllowUpToPercentageOfChannelFundsInFlight *
 0216                                                           request.FundingAmount.Satoshi / 100M);
 217
 0218        // Generate local keys through the signer
 0219        var localKeyIndex = _lightningSigner.CreateNewChannel(out var localBasepoints, out var firstPerCommitmentPoint);
 220
 221        // Create the local key set
 0222        var localKeySet = new ChannelKeySetModel(localKeyIndex, localBasepoints.FundingPubKey,
 0223                                                 localBasepoints.RevocationBasepoint, localBasepoints.PaymentBasepoint,
 0224                                                 localBasepoints.DelayedPaymentBasepoint, localBasepoints.HtlcBasepoint,
 0225                                                 firstPerCommitmentPoint);
 0226
 0227        BitcoinScript? localUpfrontShutdownScript = null;
 228        // Generate our upfront shutdown script
 0229        if (negotiatedFeatures.UpfrontShutdownScript == FeatureSupport.Compulsory)
 0230            throw new ChannelErrorException("Upfront shutdown script is compulsory but we are not able to send it");
 0231
 0232        if (_nodeOptions.Features.UpfrontShutdownScript > FeatureSupport.No)
 233        {
 0234            // Generate our upfront shutdown script
 0235            // TODO: Generate a script from the local key set
 0236            // localUpfrontShutdownScript = ;
 237        }
 238
 0239        // Generate the channel configuration
 0240        var channelConfig = new ChannelConfig(channelReserveAmount, request.FeeRatePerKw ?? currentFeeRatePerKw,
 0241                                              request.HtlcMinimumAmount ?? _nodeOptions.HtlcMinimumAmount,
 0242                                              dustLimitAmount,
 0243                                              request.MaxAcceptedHtlcs ?? _nodeOptions.MaxAcceptedHtlcs,
 0244                                              maxHtlcValueInFlight, minimumDepth,
 0245                                              negotiatedFeatures.OptionAnchors != FeatureSupport.No,
 0246                                              LightningMoney.Zero, request.ToSelfDelay ?? _nodeOptions.ToSelfDelay,
 0247                                              negotiatedFeatures.ScidAlias, localUpfrontShutdownScript);
 0248
 0249        try
 0250        {
 0251            // Create the channel using only our data
 0252            return new ChannelModel(channelConfig, _channelIdFactory.CreateTemporaryChannelId(), null,
 0253                                    null, true, null, null, toLocalAmount, localKeySet, 1, 0, toRemoteAmount,
 0254                                    null, 1, remoteNodeId, 0, ChannelState.V1Opening, ChannelVersion.V1);
 255        }
 0256        catch (Exception e)
 0257        {
 0258            throw new ChannelErrorException("Error creating commitment transaction", e);
 259        }
 0260    }
 0261
 0262    private LightningMoney GetOurChannelReserveFromFundingAmount(LightningMoney fundingAmount)
 263    {
 0264        return fundingAmount * 0.01M;
 0265    }
 266}