< Summary - Combined Code Coverage

Information
Class: NLightning.Domain.Bitcoin.Transactions.Factories.CommitmentTransactionModelFactory
Assembly: NLightning.Domain
File(s): /home/runner/work/NLightning/NLightning/src/NLightning.Domain/Bitcoin/Transactions/Factories/CommitmentTransactionModelFactory.cs
Tag: 57_24045730253
Line coverage
60%
Covered lines: 112
Uncovered lines: 74
Coverable lines: 186
Total lines: 256
Line coverage: 60.2%
Branch coverage
34%
Covered branches: 51
Total branches: 150
Branch coverage: 34%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
CreateCommitmentTransactionModel(...)68.06%93.236684%
GetFeePayerAmount(...)0%4260%
AdjustForAnchorOutputs(...)0%2040%
GetFeePayerAmount(...)33.33%7.33666.67%
AdjustForAnchorOutputs(...)0%2040%

File(s)

/home/runner/work/NLightning/NLightning/src/NLightning.Domain/Bitcoin/Transactions/Factories/CommitmentTransactionModelFactory.cs

#LineLine coverage
 1using NLightning.Domain.Bitcoin.Interfaces;
 2using NLightning.Domain.Bitcoin.Transactions.Constants;
 3using NLightning.Domain.Bitcoin.Transactions.Enums;
 4using NLightning.Domain.Bitcoin.Transactions.Interfaces;
 5using NLightning.Domain.Bitcoin.Transactions.Models;
 6using NLightning.Domain.Bitcoin.Transactions.Outputs;
 7using NLightning.Domain.Channels.Enums;
 8using NLightning.Domain.Channels.Models;
 9using NLightning.Domain.Channels.ValueObjects;
 10using NLightning.Domain.Exceptions;
 11using NLightning.Domain.Money;
 12using NLightning.Domain.Protocol.Interfaces;
 13
 14namespace NLightning.Domain.Bitcoin.Transactions.Factories;
 15
 16public class CommitmentTransactionModelFactory : ICommitmentTransactionModelFactory
 17{
 18    private readonly ICommitmentKeyDerivationService _commitmentKeyDerivationService;
 19    private readonly ILightningSigner _lightningSigner;
 20
 6821    public CommitmentTransactionModelFactory(ICommitmentKeyDerivationService commitmentKeyDerivationService,
 6822                                             ILightningSigner lightningSigner)
 23    {
 6824        _commitmentKeyDerivationService = commitmentKeyDerivationService;
 6825        _lightningSigner = lightningSigner;
 6826    }
 27
 28    public CommitmentTransactionModel CreateCommitmentTransactionModel(ChannelModel channel, CommitmentSide side)
 29    {
 30        // Guarantee we have a RemoteKeySet
 6831        if (channel.RemoteKeySet is null)
 032            throw new InvalidOperationException(
 033                "Channel must have a RemoteKeySet to create a commitment transaction model");
 034
 035        // Guarantee we have a CommitmentNumber
 6836        if (channel.CommitmentNumber is null)
 037            throw new InvalidOperationException(
 038                "Channel must have a CommitmentNumber to create a commitment transaction model");
 039
 040        // Guarantee we have a FundingOutput
 6841        if (channel.FundingOutput is null)
 042            throw new InvalidOperationException(
 043                "Channel must have a FundingOutput to create a commitment transaction model");
 044
 045        // Create base output information
 6846        ToLocalOutputInfo? toLocalOutput = null;
 6847        ToRemoteOutputInfo? toRemoteOutput = null;
 6848        AnchorOutputInfo? localAnchorOutput = null;
 6849        AnchorOutputInfo? remoteAnchorOutput = null;
 6850        var offeredHtlcOutputs = new List<OfferedHtlcOutputInfo>();
 6851        var receivedHtlcOutputs = new List<ReceivedHtlcOutputInfo>();
 052
 053        // Get the HTLCs based on the commitment side
 6854        var htlcs = new List<Htlc>();
 6855        htlcs.AddRange(channel.LocalOfferedHtlcs?.ToList() ?? []);
 6856        htlcs.AddRange(channel.RemoteOfferedHtlcs?.ToList() ?? []);
 057
 058        // Get basepoints from the signer instead of the old key set model
 6859        var localBasepoints = _lightningSigner.GetChannelBasepoints(channel.LocalKeySet.KeyIndex);
 6860        var remoteBasepoints = new ChannelBasepoints(channel.RemoteKeySet!.FundingCompactPubKey,
 6861                                                     channel.RemoteKeySet.RevocationCompactBasepoint,
 6862                                                     channel.RemoteKeySet.PaymentCompactBasepoint,
 6863                                                     channel.RemoteKeySet.DelayedPaymentCompactBasepoint,
 6864                                                     channel.RemoteKeySet.HtlcCompactBasepoint);
 65
 66        // Derive the commitment keys from the appropriate perspective
 6867        var commitmentKeys = side switch
 6868        {
 6869            CommitmentSide.Local => _commitmentKeyDerivationService.DeriveLocalCommitmentKeys(
 6870                channel.LocalKeySet.KeyIndex, localBasepoints, remoteBasepoints,
 6871                channel.LocalKeySet.CurrentPerCommitmentIndex),
 6872
 073            CommitmentSide.Remote => _commitmentKeyDerivationService.DeriveRemoteCommitmentKeys(
 074                localBasepoints, remoteBasepoints, channel.RemoteKeySet.CurrentPerCommitmentCompactPoint),
 6875
 076            _ => throw new ArgumentOutOfRangeException(nameof(side), side,
 077                                                       "You should use either Local or Remote commitment side.")
 6878        };
 079
 80        // Calculate base weight
 6881        var weight = WeightConstants.TransactionBaseWeight
 6882                   + TransactionConstants.CommitmentTransactionInputWeight
 6883                   // + htlcs.Count * WeightConstants.HtlcOutputWeight
 6884                   + WeightConstants.P2WshOutputWeight; // To Local Output
 085
 086        // Set initial amounts for to_local and to_remote outputs
 6887        var toLocalAmount = side == CommitmentSide.Local
 6888                                ? channel.LocalBalance
 6889                                : channel.RemoteBalance;
 90
 6891        var toRemoteAmount = side == CommitmentSide.Local
 6892                                 ? channel.RemoteBalance
 6893                                 : channel.LocalBalance;
 094
 6895        var localDustLimitAmount = side == CommitmentSide.Local
 6896                                       ? channel.ChannelConfig.LocalDustLimitAmount
 6897                                       : channel.ChannelConfig.RemoteDustLimitAmount;
 098
 6899        var remoteDustLimitAmount = side == CommitmentSide.Local
 68100                                        ? channel.ChannelConfig.RemoteDustLimitAmount
 68101                                        : channel.ChannelConfig.LocalDustLimitAmount;
 0102
 68103        if (htlcs is { Count: > 0 })
 0104        {
 105            // Calculate htlc weight and fee
 60106            var offeredHtlcWeight = channel.ChannelConfig.OptionAnchorOutputs
 60107                                        ? WeightConstants.HtlcTimeoutWeightAnchors
 60108                                        : WeightConstants.HtlcTimeoutWeightNoAnchors;
 60109            var offeredHtlcFee =
 60110                LightningMoney.MilliSatoshis(offeredHtlcWeight * channel.ChannelConfig.FeeRateAmountPerKw.Satoshi);
 111
 60112            var receivedHtlcWeight = channel.ChannelConfig.OptionAnchorOutputs
 60113                                         ? WeightConstants.HtlcSuccessWeightAnchors
 60114                                         : WeightConstants.HtlcSuccessWeightNoAnchors;
 60115            var receivedHtlcFee =
 60116                LightningMoney.MilliSatoshis(receivedHtlcWeight * channel.ChannelConfig.FeeRateAmountPerKw.Satoshi);
 117
 704118            foreach (var htlc in htlcs)
 0119            {
 0120                // Determine if this is an offered or received HTLC from the perspective of the commitment holder
 292121                var isOffered = side == CommitmentSide.Local
 292122                                    ? htlc.Direction == HtlcDirection.Outgoing
 292123                                    : htlc.Direction == HtlcDirection.Incoming;
 124
 125                // Calculate the amounts after subtracting fees
 292126                var htlcFee = isOffered ? offeredHtlcFee : receivedHtlcFee;
 292127                var htlcAmount = htlc.Amount.Satoshi > htlcFee.Satoshi
 292128                                     ? LightningMoney.Satoshis(htlc.Amount.Satoshi - htlcFee.Satoshi)
 292129                                     : LightningMoney.Zero;
 0130
 0131                // Always subtract the full HTLC amount from to_local
 292132                toLocalAmount = toLocalAmount > htlc.Amount
 292133                                    ? toLocalAmount - htlc.Amount
 292134                                    : LightningMoney.Zero; // If not enough, set to zero
 135
 136                // Offered or received depends on dust check
 292137                if (htlcAmount.Satoshi < localDustLimitAmount.Satoshi)
 0138                    continue;
 0139
 132140                weight += WeightConstants.HtlcOutputWeight;
 132141                if (isOffered)
 142                {
 64143                    offeredHtlcOutputs.Add(new OfferedHtlcOutputInfo(
 64144                                               htlc,
 64145                                               commitmentKeys.LocalHtlcPubKey,
 64146                                               commitmentKeys.RemoteHtlcPubKey,
 64147                                               commitmentKeys.RevocationPubKey));
 0148                }
 149                else
 0150                {
 68151                    receivedHtlcOutputs.Add(new ReceivedHtlcOutputInfo(
 68152                                                htlc,
 68153                                                commitmentKeys.LocalHtlcPubKey,
 68154                                                commitmentKeys.RemoteHtlcPubKey,
 68155                                                commitmentKeys.RevocationPubKey));
 156                }
 0157            }
 0158        }
 0159
 160        LightningMoney fee;
 161        // Create anchor outputs if option_anchors is negotiated
 68162        if (channel.ChannelConfig.OptionAnchorOutputs)
 0163        {
 0164            localAnchorOutput = new AnchorOutputInfo(channel.LocalKeySet.FundingCompactPubKey, true);
 0165            remoteAnchorOutput = new AnchorOutputInfo(channel.RemoteKeySet.FundingCompactPubKey, false);
 0166
 0167            weight += WeightConstants.AnchorOutputWeight * 2
 0168                    + WeightConstants.P2WshOutputWeight; // Add ToRemote Output weight
 0169            fee = LightningMoney.MilliSatoshis(weight * channel.ChannelConfig.FeeRateAmountPerKw.Satoshi);
 0170
 0171            ref var feePayerAmount =
 0172                ref GetFeePayerAmount(side, channel.IsInitiator, ref toLocalAmount, ref toRemoteAmount);
 0173            AdjustForAnchorOutputs(ref feePayerAmount, fee, TransactionConstants.AnchorOutputAmount);
 174        }
 175        else
 0176        {
 68177            weight += WeightConstants.P2WpkhOutputWeight; // Add ToRemote Output weight
 68178            fee = LightningMoney.MilliSatoshis(weight * channel.ChannelConfig.FeeRateAmountPerKw.Satoshi);
 0179
 68180            ref var feePayerAmount =
 68181                ref GetFeePayerAmount(side, channel.IsInitiator, ref toLocalAmount, ref toRemoteAmount);
 0182
 183            // Simple fee deduction when no anchors
 68184            feePayerAmount = feePayerAmount.Satoshi > fee.Satoshi
 68185                                 ? LightningMoney.Satoshis(feePayerAmount.Satoshi - fee.Satoshi)
 68186                                 : LightningMoney.Zero;
 187        }
 188
 0189        // Fail if both amounts are below ChannelReserve
 68190        if (channel.ChannelConfig.ChannelReserveAmount is not null
 68191         && toLocalAmount.Satoshi < channel.ChannelConfig.ChannelReserveAmount.Satoshi
 68192         && toRemoteAmount.Satoshi < channel.ChannelConfig.ChannelReserveAmount.Satoshi)
 0193            throw new ChannelErrorException("Both to_local and to_remote amounts are below the reserve limits.");
 194
 0195        // Only create output if the amount is above the dust limit
 68196        if (toLocalAmount.Satoshi >= localDustLimitAmount.Satoshi)
 197        {
 60198            toLocalOutput = new ToLocalOutputInfo(toLocalAmount, commitmentKeys.LocalDelayedPubKey,
 60199                                                  commitmentKeys.RevocationPubKey,
 60200                                                  channel.ChannelConfig.ToSelfDelay);
 201        }
 0202
 68203        if (toRemoteAmount.Satoshi >= remoteDustLimitAmount.Satoshi)
 204        {
 68205            var remotePubKey = side == CommitmentSide.Local
 68206                                   ? channel.RemoteKeySet.PaymentCompactBasepoint
 68207                                   : channel.LocalKeySet.PaymentCompactBasepoint;
 208
 68209            toRemoteOutput =
 68210                new ToRemoteOutputInfo(toRemoteAmount, remotePubKey, channel.ChannelConfig.OptionAnchorOutputs);
 0211        }
 0212
 68213        if (offeredHtlcOutputs.Count == 0 && receivedHtlcOutputs.Count == 0)
 214        {
 215            // If no HTLCs and no to_local, we can remove our anchor output
 24216            if (toLocalOutput is null)
 8217                localAnchorOutput = null;
 218
 219            // If no HTLCs and no to_remote, we can remove their anchor output
 24220            if (toRemoteOutput is null)
 0221                remoteAnchorOutput = null;
 222        }
 0223
 0224        // Create and return the commitment transaction model
 68225        return new CommitmentTransactionModel(channel.CommitmentNumber!, fee, channel.FundingOutput!,
 68226                                              localAnchorOutput, remoteAnchorOutput, toLocalOutput, toRemoteOutput,
 68227                                              offeredHtlcOutputs, receivedHtlcOutputs);
 228    }
 229
 230    private static ref LightningMoney GetFeePayerAmount(CommitmentSide side, bool isInitiator,
 231                                                        ref LightningMoney toLocal,
 0232                                                        ref LightningMoney toRemote)
 233    {
 0234        // If we're the initiator, and it's our tx, deduct from toLocal
 0235        // If not initiator and our tx, deduct from toRemote
 0236        // For remote tx, logic is reversed
 68237        if ((side == CommitmentSide.Local && isInitiator) || (side == CommitmentSide.Remote && !isInitiator))
 68238            return ref toLocal;
 239
 0240        return ref toRemote;
 0241    }
 242
 243    private static void AdjustForAnchorOutputs(ref LightningMoney amount, LightningMoney fee,
 244                                               LightningMoney anchorAmount)
 245    {
 0246        if (amount > fee)
 247        {
 0248            amount -= fee;
 0249            amount = amount > anchorAmount
 0250                         ? amount - anchorAmount
 0251                         : LightningMoney.Zero;
 252        }
 253        else
 0254            amount = LightningMoney.Zero;
 0255    }
 256}