< Summary - Combined Code Coverage

Information
Class: NLightning.Infrastructure.Bitcoin.Transactions.CommitmentTransaction
Assembly: NLightning.Infrastructure.Bitcoin
File(s): /home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure.Bitcoin/Transactions/CommitmentTransaction.cs
Tag: 30_15166811759
Line coverage
85%
Covered lines: 108
Uncovered lines: 18
Coverable lines: 126
Total lines: 329
Line coverage: 85.7%
Branch coverage
75%
Covered branches: 47
Total branches: 62
Branch coverage: 75.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_ToLocalOutput()100%11100%
get_ToRemoteOutput()100%11100%
get_LocalAnchorOutput()100%11100%
get_RemoteAnchorOutput()100%11100%
get_CommitmentNumber()100%11100%
get_OfferedHtlcOutputs()100%11100%
get_ReceivedHtlcOutputs()100%11100%
.ctor(...)100%88100%
AddOfferedHtlcOutput(...)50%2.03280%
AddReceivedHtlcOutput(...)50%2.03280%
AppendRemoteSignatureAndSign(...)100%11100%
GetSignedTransaction()100%22100%
ConstructTransaction(...)59.38%108.463257.89%
SignTransaction(...)100%11100%
SetLocalAndRemoteAmounts(...)100%66100%
SetTxIdAndIndexes()100%1010100%

File(s)

/home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure.Bitcoin/Transactions/CommitmentTransaction.cs

#LineLine coverage
 1using NBitcoin;
 2using NBitcoin.Crypto;
 3
 4namespace NLightning.Infrastructure.Bitcoin.Transactions;
 5
 6using Domain.Money;
 7using Domain.Protocol.Constants;
 8using Domain.ValueObjects;
 9using Outputs;
 10using Protocol.Models;
 11
 12/// <summary>
 13/// Represents a commitment transaction.
 14/// </summary>
 15public class CommitmentTransaction : BaseTransaction
 16{
 17    #region Private Fields
 18    private readonly LightningMoney _anchorAmount;
 19    private readonly LightningMoney _dustLimitAmount;
 20    private readonly bool _isChannelFunder;
 21    private readonly bool _mustTrimHtlcOutputs;
 22
 23    private LightningMoney _toFunderAmount;
 24    #endregion
 25
 26    #region Public Properties
 52827    public ToLocalOutput ToLocalOutput { get; }
 76028    public ToRemoteOutput ToRemoteOutput { get; }
 28429    public ToAnchorOutput? LocalAnchorOutput { get; private set; }
 28430    public ToAnchorOutput? RemoteAnchorOutput { get; private set; }
 831    public CommitmentNumber CommitmentNumber { get; }
 36032    public IList<OfferedHtlcOutput> OfferedHtlcOutputs { get; } = [];
 36833    public IList<ReceivedHtlcOutput> ReceivedHtlcOutputs { get; } = [];
 34
 35    #endregion
 36
 37    #region Constructors
 38
 39    /// <summary>
 40    /// Initializes a new instance of the <see cref="CommitmentTransaction"/> class.
 41    /// </summary>
 42    /// <param name="anchorAmount">The anchor amount.</param>
 43    /// <param name="network">The network type.</param>
 44    /// <param name="mustTrimHtlcOutputs">Indicates if HTLC outputs must be trimmed.</param>
 45    /// <param name="dustLimitAmount"></param>
 46    /// <param name="fundingOutput">The funding coin.</param>
 47    /// <param name="localPaymentBasepoint">The local public key.</param>
 48    /// <param name="remotePaymentBasepoint">The remote public key.</param>
 49    /// <param name="localDelayedPubKey">The local delayed public key.</param>
 50    /// <param name="revocationPubKey">The revocation public key.</param>
 51    /// <param name="toLocalAmount">The amount for the to_local output in satoshis.</param>
 52    /// <param name="toRemoteAmount">The amount for the to_remote output in satoshis.</param>
 53    /// <param name="toSelfDelay">The to_self_delay in blocks.</param>
 54    /// <param name="commitmentNumber">The commitment number object.</param>
 55    /// <param name="isChannelFunder">Indicates if the local node is the channel funder.</param>
 56    internal CommitmentTransaction(LightningMoney anchorAmount, LightningMoney dustLimitAmount,
 57                                   bool mustTrimHtlcOutputs, Network network, FundingOutput fundingOutput,
 58                                   PubKey localPaymentBasepoint, PubKey remotePaymentBasepoint,
 59                                   PubKey localDelayedPubKey, PubKey revocationPubKey, LightningMoney toLocalAmount,
 60                                   LightningMoney toRemoteAmount, uint toSelfDelay, CommitmentNumber commitmentNumber,
 61                                   bool isChannelFunder)
 14062        : base(!anchorAmount.IsZero, network, TransactionConstants.COMMITMENT_TRANSACTION_VERSION, SigHash.All,
 14063               (fundingOutput.ToCoin(), commitmentNumber.CalculateSequence()))
 64    {
 14065        ArgumentNullException.ThrowIfNull(localPaymentBasepoint);
 14066        ArgumentNullException.ThrowIfNull(remotePaymentBasepoint);
 14067        ArgumentNullException.ThrowIfNull(localDelayedPubKey);
 14068        ArgumentNullException.ThrowIfNull(revocationPubKey);
 69
 14070        if (toLocalAmount.IsZero && toRemoteAmount.IsZero)
 71        {
 472            throw new ArgumentException("Both toLocalAmount and toRemoteAmount cannot be zero.");
 73        }
 74
 13675        _anchorAmount = anchorAmount;
 13676        _dustLimitAmount = dustLimitAmount;
 13677        _isChannelFunder = isChannelFunder;
 13678        _mustTrimHtlcOutputs = mustTrimHtlcOutputs;
 13679        CommitmentNumber = commitmentNumber;
 80
 81        // Set locktime
 13682        SetLockTime(commitmentNumber.CalculateLockTime());
 83
 84        // Set funder amount
 13685        var localAmount = LightningMoney.Zero;
 13686        var remoteAmount = LightningMoney.Zero;
 13687        if (_isChannelFunder)
 88        {
 89            // localAmount will be calculated later
 12890            _toFunderAmount = toLocalAmount;
 12891            remoteAmount = toRemoteAmount;
 92        }
 93        else
 94        {
 95            // remoteAmount will be calculated later
 896            _toFunderAmount = toRemoteAmount;
 897            localAmount = toLocalAmount;
 98        }
 99
 100        // to_local output
 136101        ToLocalOutput = new ToLocalOutput(localDelayedPubKey, revocationPubKey, toSelfDelay, localAmount);
 136102        AddOutput(ToLocalOutput);
 103
 104        // to_remote output
 136105        ToRemoteOutput = new ToRemoteOutput(!anchorAmount.IsZero, remotePaymentBasepoint, remoteAmount);
 136106        AddOutput(ToRemoteOutput);
 107
 136108        if (anchorAmount == LightningMoney.Zero)
 109        {
 84110            return;
 111        }
 112
 113        // Local anchor output
 52114        LocalAnchorOutput = new ToAnchorOutput(fundingOutput.LocalPubKey, anchorAmount);
 52115        AddOutput(LocalAnchorOutput);
 116
 117        // Remote anchor output
 52118        RemoteAnchorOutput = new ToAnchorOutput(fundingOutput.RemotePubKey, anchorAmount);
 52119        AddOutput(RemoteAnchorOutput);
 52120    }
 121    #endregion
 122
 123    #region Public Methods
 124    public void AddOfferedHtlcOutput(OfferedHtlcOutput offeredHtlcOutput)
 125    {
 96126        if (Finalized)
 127        {
 0128            throw new InvalidOperationException("You can't add outputs to an already finalized transaction.");
 129        }
 130
 131        // Add output
 96132        OfferedHtlcOutputs.Add(offeredHtlcOutput);
 96133        AddOutput(offeredHtlcOutput);
 96134    }
 135
 136    public void AddReceivedHtlcOutput(ReceivedHtlcOutput receivedHtlcOutput)
 137    {
 104138        if (Finalized)
 139        {
 0140            throw new InvalidOperationException("You can't add outputs to an already finalized transaction.");
 141        }
 142
 104143        ReceivedHtlcOutputs.Add(receivedHtlcOutput);
 104144        AddOutput(receivedHtlcOutput);
 104145    }
 146
 147    public void AppendRemoteSignatureAndSign(ECDSASignature remoteSignature, PubKey remotePubKey)
 148    {
 104149        AppendRemoteSignatureToTransaction(new TransactionSignature(remoteSignature), remotePubKey);
 104150        SignTransactionWithExistingKeys();
 104151    }
 152
 153    public Transaction GetSignedTransaction()
 154    {
 124155        if (Finalized)
 156        {
 120157            return FinalizedTransaction;
 158        }
 159
 4160        throw new InvalidOperationException("You have to sign and finalize the transaction first.");
 161    }
 162    #endregion
 163
 164    #region Internal Methods
 165    internal override void ConstructTransaction(LightningMoney currentFeePerKw)
 166    {
 167        // Calculate base fee
 116168        var outputWeight = CalculateOutputWeight();
 116169        var calculatedFee = (outputWeight + TransactionConstants.COMMITMENT_TRANSACTION_INPUT_WEIGHT)
 116170                          * currentFeePerKw.Satoshi / 1000L;
 116171        if (CalculatedFee.Satoshi != calculatedFee)
 172        {
 112173            CalculatedFee.Satoshi = calculatedFee;
 174        }
 175
 176        // Deduct base fee from the funder amount
 116177        if (CalculatedFee > _toFunderAmount)
 178        {
 4179            _toFunderAmount = LightningMoney.Zero;
 180        }
 181        else
 182        {
 112183            _toFunderAmount -= CalculatedFee;
 184        }
 185
 186        // Deduct anchor fee from the funder amount
 116187        if (!_anchorAmount.IsZero && !_toFunderAmount.IsZero)
 188        {
 40189            _toFunderAmount -= _anchorAmount;
 40190            _toFunderAmount -= _anchorAmount;
 191        }
 192
 193        // Trim Local and Remote outputs
 116194        if (_isChannelFunder)
 195        {
 112196            SetLocalAndRemoteAmounts(ToLocalOutput, ToRemoteOutput);
 197        }
 198        else
 199        {
 4200            SetLocalAndRemoteAmounts(ToRemoteOutput, ToLocalOutput);
 201        }
 202
 203        // Trim HTLCs
 116204        if (_mustTrimHtlcOutputs)
 205        {
 0206            var offeredHtlcWeight = _anchorAmount.IsZero
 0207                ? WeightConstants.HTLC_TIMEOUT_WEIGHT_NO_ANCHORS
 0208                : WeightConstants.HTLC_TIMEOUT_WEIGHT_ANCHORS;
 0209            var offeredHtlcFee = offeredHtlcWeight * currentFeePerKw.Satoshi / 1000L;
 0210            foreach (var offeredHtlcOutput in OfferedHtlcOutputs)
 211            {
 0212                var htlcAmount = offeredHtlcOutput.Amount - offeredHtlcFee;
 0213                if (htlcAmount < _dustLimitAmount)
 214                {
 0215                    RemoveOutput(offeredHtlcOutput);
 216                }
 217            }
 218
 0219            var receivedHtlcWeight = _anchorAmount.IsZero
 0220                ? WeightConstants.HTLC_SUCCESS_WEIGHT_NO_ANCHORS
 0221                : WeightConstants.HTLC_SUCCESS_WEIGHT_ANCHORS;
 0222            var receivedHtlcFee = receivedHtlcWeight * currentFeePerKw.Satoshi / 1000L;
 0223            foreach (var receivedHtlcOutput in ReceivedHtlcOutputs)
 224            {
 0225                var htlcAmount = receivedHtlcOutput.Amount - receivedHtlcFee;
 0226                if (htlcAmount < _dustLimitAmount)
 227                {
 0228                    RemoveOutput(receivedHtlcOutput);
 229                }
 230            }
 231        }
 232
 233        // Anchors are always needed, except when one of the outputs is zero and there's no htlc output
 248234        if (!_anchorAmount.IsZero && !Outputs.Any(o => o is BaseHtlcOutput))
 235        {
 20236            if (ToLocalOutput.Amount.IsZero)
 237            {
 4238                RemoveOutput(LocalAnchorOutput);
 239            }
 240
 20241            if (ToRemoteOutput.Amount.IsZero)
 242            {
 4243                RemoveOutput(RemoteAnchorOutput);
 244            }
 245        }
 246
 247        // Order Outputs
 116248        AddOrderedOutputsToTransaction();
 116249    }
 250
 251    internal new void SignTransaction(params BitcoinSecret[] secrets)
 252    {
 116253        base.SignTransaction(secrets);
 254
 116255        SetTxIdAndIndexes();
 116256    }
 257    #endregion
 258
 259    #region Private Methods
 260    private void SetLocalAndRemoteAmounts(BaseOutput funderOutput, BaseOutput otherOutput)
 261    {
 116262        if (_toFunderAmount >= _dustLimitAmount)
 263        {
 96264            if (_toFunderAmount != funderOutput.Amount)
 265            {
 266                // Remove old output
 96267                RemoveOutput(funderOutput);
 268
 269                // Set amount
 96270                funderOutput.Amount = _toFunderAmount;
 271
 272                // Add new output
 96273                AddOutput(funderOutput);
 274            }
 275        }
 276        else
 277        {
 20278            RemoveOutput(funderOutput);
 20279            funderOutput.Amount = LightningMoney.Zero;
 280        }
 281
 116282        RemoveOutput(otherOutput);
 116283        if (otherOutput.Amount >= _dustLimitAmount)
 284        {
 112285            AddOutput(otherOutput);
 286        }
 287        else
 288        {
 4289            otherOutput.Amount = LightningMoney.Zero;
 290        }
 4291    }
 292
 293    private void SetTxIdAndIndexes()
 294    {
 116295        ToRemoteOutput.TxId = TxId;
 116296        ToRemoteOutput.Index = Outputs.IndexOf(ToRemoteOutput);
 297
 116298        ToLocalOutput.TxId = TxId;
 116299        ToRemoteOutput.Index = Outputs.IndexOf(ToLocalOutput);
 300
 424301        foreach (var offeredHtlcOutput in OfferedHtlcOutputs)
 302        {
 96303            offeredHtlcOutput.TxId = TxId;
 96304            offeredHtlcOutput.Index = Outputs.IndexOf(offeredHtlcOutput);
 305        }
 306
 440307        foreach (var receivedHtlcOutput in ReceivedHtlcOutputs)
 308        {
 104309            receivedHtlcOutput.TxId = TxId;
 104310            receivedHtlcOutput.Index = Outputs.IndexOf(receivedHtlcOutput);
 311        }
 312
 116313        if (!_anchorAmount.IsZero)
 314        {
 40315            if (LocalAnchorOutput is not null)
 316            {
 40317                LocalAnchorOutput.TxId = TxId;
 40318                LocalAnchorOutput.Index = Outputs.IndexOf(LocalAnchorOutput);
 319            }
 320
 40321            if (RemoteAnchorOutput is not null)
 322            {
 40323                RemoteAnchorOutput.TxId = TxId;
 40324                RemoteAnchorOutput.Index = Outputs.IndexOf(RemoteAnchorOutput);
 325            }
 326        }
 116327    }
 328    #endregion
 329}