< 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: 36_15743069263
Line coverage
82%
Covered lines: 109
Uncovered lines: 23
Coverable lines: 132
Total lines: 242
Line coverage: 82.5%
Branch coverage
63%
Covered branches: 48
Total branches: 76
Branch coverage: 63.1%
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(...)69.7%75.186687.18%
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        // Create base output information
 6831        ToLocalOutputInfo? toLocalOutput = null;
 6832        ToRemoteOutputInfo? toRemoteOutput = null;
 6833        AnchorOutputInfo? localAnchorOutput = null;
 6834        AnchorOutputInfo? remoteAnchorOutput = null;
 6835        var offeredHtlcOutputs = new List<OfferedHtlcOutputInfo>();
 6836        var receivedHtlcOutputs = new List<ReceivedHtlcOutputInfo>();
 37
 38        // Get the HTLCs based on the commitment side
 6839        var htlcs = new List<Htlc>();
 6840        htlcs.AddRange(channel.LocalOfferedHtlcs?.ToList() ?? []);
 6841        htlcs.AddRange(channel.RemoteOfferedHtlcs?.ToList() ?? []);
 42
 43        // Get basepoints from the signer instead of the old key set model
 6844        var localBasepoints = _lightningSigner.GetChannelBasepoints(channel.LocalKeySet.KeyIndex);
 6845        var remoteBasepoints = new ChannelBasepoints(channel.RemoteKeySet.FundingCompactPubKey,
 6846                                                     channel.RemoteKeySet.RevocationCompactBasepoint,
 6847                                                     channel.RemoteKeySet.PaymentCompactBasepoint,
 6848                                                     channel.RemoteKeySet.DelayedPaymentCompactBasepoint,
 6849                                                     channel.RemoteKeySet.HtlcCompactBasepoint);
 50
 51        // Derive the commitment keys from the appropriate perspective
 6852        var commitmentKeys = side switch
 6853        {
 6854            CommitmentSide.Local => _commitmentKeyDerivationService.DeriveLocalCommitmentKeys(
 6855                channel.LocalKeySet.KeyIndex, localBasepoints, remoteBasepoints,
 6856                channel.LocalKeySet.CurrentPerCommitmentIndex),
 6857
 058            CommitmentSide.Remote => _commitmentKeyDerivationService.DeriveRemoteCommitmentKeys(
 059                channel.LocalKeySet.KeyIndex, localBasepoints, remoteBasepoints,
 060                channel.RemoteKeySet.CurrentPerCommitmentCompactPoint, channel.RemoteKeySet.CurrentPerCommitmentIndex),
 6861
 062            _ => throw new ArgumentOutOfRangeException(nameof(side), side,
 063                                                       "You should use either Local or Remote commitment side.")
 6864        };
 65
 66        // Calculate base weight
 6867        var weight = WeightConstants.TransactionBaseWeight
 6868                   + TransactionConstants.CommitmentTransactionInputWeight
 6869                   // + htlcs.Count * WeightConstants.HtlcOutputWeight
 6870                   + WeightConstants.P2WshOutputWeight; // To Local Output
 71
 72        // Set initial amounts for to_local and to_remote outputs
 6873        var toLocalAmount = side == CommitmentSide.Local
 6874                                ? channel.LocalBalance
 6875                                : channel.RemoteBalance;
 76
 6877        var toRemoteAmount = side == CommitmentSide.Local
 6878                                 ? channel.RemoteBalance
 6879                                 : channel.LocalBalance;
 80
 6881        var localDustLimitAmount = side == CommitmentSide.Local
 6882                                       ? channel.ChannelConfig.LocalDustLimitAmount
 6883                                       : channel.ChannelConfig.RemoteDustLimitAmount;
 84
 6885        var remoteDustLimitAmount = side == CommitmentSide.Local
 6886                                        ? channel.ChannelConfig.RemoteDustLimitAmount
 6887                                        : channel.ChannelConfig.LocalDustLimitAmount;
 88
 6889        if (htlcs is { Count: > 0 })
 90        {
 91            // Calculate htlc weight and fee
 6092            var offeredHtlcWeight = channel.ChannelConfig.OptionAnchorOutputs
 6093                                        ? WeightConstants.HtlcTimeoutWeightAnchors
 6094                                        : WeightConstants.HtlcTimeoutWeightNoAnchors;
 6095            var offeredHtlcFee =
 6096                LightningMoney.MilliSatoshis(offeredHtlcWeight * channel.ChannelConfig.FeeRateAmountPerKw.Satoshi);
 97
 6098            var receivedHtlcWeight = channel.ChannelConfig.OptionAnchorOutputs
 6099                                         ? WeightConstants.HtlcSuccessWeightAnchors
 60100                                         : WeightConstants.HtlcSuccessWeightNoAnchors;
 60101            var receivedHtlcFee =
 60102                LightningMoney.MilliSatoshis(receivedHtlcWeight * channel.ChannelConfig.FeeRateAmountPerKw.Satoshi);
 103
 704104            foreach (var htlc in htlcs)
 105            {
 106                // Determine if this is an offered or received HTLC from the perspective of the commitment holder
 292107                var isOffered = side == CommitmentSide.Local
 292108                                    ? htlc.Direction == HtlcDirection.Outgoing
 292109                                    : htlc.Direction == HtlcDirection.Incoming;
 110
 111                // Calculate the amounts after subtracting fees
 292112                var htlcFee = isOffered ? offeredHtlcFee : receivedHtlcFee;
 292113                var htlcAmount = htlc.Amount.Satoshi > htlcFee.Satoshi
 292114                                     ? LightningMoney.Satoshis(htlc.Amount.Satoshi - htlcFee.Satoshi)
 292115                                     : LightningMoney.Zero;
 116
 117                // Always subtract the full HTLC amount from to_local
 292118                toLocalAmount = toLocalAmount > htlc.Amount
 292119                                    ? toLocalAmount - htlc.Amount
 292120                                    : LightningMoney.Zero; // If not enough, set to zero
 121
 122                // Offered or received depends on dust check
 292123                if (htlcAmount.Satoshi < localDustLimitAmount.Satoshi)
 124                    continue;
 125
 132126                weight += WeightConstants.HtlcOutputWeight;
 132127                if (isOffered)
 128                {
 64129                    offeredHtlcOutputs.Add(new OfferedHtlcOutputInfo(
 64130                                               htlc,
 64131                                               commitmentKeys.LocalHtlcPubKey,
 64132                                               commitmentKeys.RemoteHtlcPubKey,
 64133                                               commitmentKeys.RevocationPubKey));
 134                }
 135                else
 136                {
 68137                    receivedHtlcOutputs.Add(new ReceivedHtlcOutputInfo(
 68138                                                htlc,
 68139                                                commitmentKeys.LocalHtlcPubKey,
 68140                                                commitmentKeys.RemoteHtlcPubKey,
 68141                                                commitmentKeys.RevocationPubKey));
 142                }
 143            }
 144        }
 145
 146        LightningMoney fee;
 147        // Create anchor outputs if option_anchors is negotiated
 68148        if (channel.ChannelConfig.OptionAnchorOutputs)
 149        {
 0150            localAnchorOutput = new AnchorOutputInfo(channel.LocalKeySet.FundingCompactPubKey, true);
 0151            remoteAnchorOutput = new AnchorOutputInfo(channel.RemoteKeySet.FundingCompactPubKey, false);
 152
 0153            weight += WeightConstants.AnchorOutputWeight * 2
 0154                    + WeightConstants.P2WshOutputWeight; // Add ToRemote Output weight
 0155            fee = LightningMoney.MilliSatoshis(weight * channel.ChannelConfig.FeeRateAmountPerKw.Satoshi);
 156
 0157            ref var feePayerAmount =
 0158                ref GetFeePayerAmount(side, channel.IsInitiator, ref toLocalAmount, ref toRemoteAmount);
 0159            AdjustForAnchorOutputs(ref feePayerAmount, fee, TransactionConstants.AnchorOutputAmount);
 160        }
 161        else
 162        {
 68163            weight += WeightConstants.P2WpkhOutputWeight; // Add ToRemote Output weight
 68164            fee = LightningMoney.MilliSatoshis(weight * channel.ChannelConfig.FeeRateAmountPerKw.Satoshi);
 165
 68166            ref var feePayerAmount =
 68167                ref GetFeePayerAmount(side, channel.IsInitiator, ref toLocalAmount, ref toRemoteAmount);
 168
 169            // Simple fee deduction when no anchors
 68170            feePayerAmount = feePayerAmount.Satoshi > fee.Satoshi
 68171                                 ? LightningMoney.Satoshis(feePayerAmount.Satoshi - fee.Satoshi)
 68172                                 : LightningMoney.Zero;
 173        }
 174
 175        // Fail if both amounts are below ChannelReserve
 68176        if (channel.ChannelConfig.ChannelReserveAmount is not null
 68177         && toLocalAmount.Satoshi < channel.ChannelConfig.ChannelReserveAmount.Satoshi
 68178         && toRemoteAmount.Satoshi < channel.ChannelConfig.ChannelReserveAmount.Satoshi)
 0179            throw new ChannelErrorException("Both to_local and to_remote amounts are below the reserve limits.");
 180
 181        // Only create output if the amount is above the dust limit
 68182        if (toLocalAmount.Satoshi >= localDustLimitAmount.Satoshi)
 183        {
 60184            toLocalOutput = new ToLocalOutputInfo(toLocalAmount, commitmentKeys.LocalDelayedPubKey,
 60185                                                  commitmentKeys.RevocationPubKey,
 60186                                                  channel.ChannelConfig.ToSelfDelay);
 187        }
 188
 68189        if (toRemoteAmount.Satoshi >= remoteDustLimitAmount.Satoshi)
 190        {
 68191            var remotePubKey = side == CommitmentSide.Local
 68192                                   ? channel.RemoteKeySet.PaymentCompactBasepoint
 68193                                   : channel.LocalKeySet.PaymentCompactBasepoint;
 194
 68195            toRemoteOutput =
 68196                new ToRemoteOutputInfo(toRemoteAmount, remotePubKey, channel.ChannelConfig.OptionAnchorOutputs);
 197        }
 198
 68199        if (offeredHtlcOutputs.Count == 0 && receivedHtlcOutputs.Count == 0)
 200        {
 201            // If no HTLCs and no to_local, we can remove our anchor output
 24202            if (toLocalOutput is null)
 8203                localAnchorOutput = null;
 204
 205            // If no HTLCs and no to_remote, we can remove their anchor output
 24206            if (toRemoteOutput is null)
 0207                remoteAnchorOutput = null;
 208        }
 209
 210        // Create and return the commitment transaction model
 68211        return new CommitmentTransactionModel(channel.CommitmentNumber, fee, channel.FundingOutput,
 68212                                              localAnchorOutput, remoteAnchorOutput, toLocalOutput, toRemoteOutput,
 68213                                              offeredHtlcOutputs, receivedHtlcOutputs);
 214    }
 215
 216    private static ref LightningMoney GetFeePayerAmount(CommitmentSide side, bool isInitiator,
 217                                                        ref LightningMoney toLocal,
 218                                                        ref LightningMoney toRemote)
 219    {
 220        // If we're the initiator, and it's our tx, deduct from toLocal
 221        // If not initiator and our tx, deduct from toRemote
 222        // For remote tx, logic is reversed
 68223        if ((side == CommitmentSide.Local && isInitiator) || (side == CommitmentSide.Remote && !isInitiator))
 68224            return ref toLocal;
 225
 0226        return ref toRemote;
 227    }
 228
 229    private static void AdjustForAnchorOutputs(ref LightningMoney amount, LightningMoney fee,
 230                                               LightningMoney anchorAmount)
 231    {
 0232        if (amount > fee)
 233        {
 0234            amount -= fee;
 0235            amount = amount > anchorAmount
 0236                         ? amount - anchorAmount
 0237                         : LightningMoney.Zero;
 238        }
 239        else
 0240            amount = LightningMoney.Zero;
 0241    }
 242}