< Summary - Combined Code Coverage

Information
Class: NLightning.Domain.Node.FeatureSet
Assembly: NLightning.Domain
File(s): /home/runner/work/nlightning/nlightning/src/NLightning.Domain/Node/FeatureSet.cs
Tag: 36_15743069263
Line coverage
91%
Covered lines: 109
Uncovered lines: 10
Coverable lines: 119
Total lines: 401
Line coverage: 91.5%
Branch coverage
91%
Covered branches: 79
Total branches: 86
Branch coverage: 91.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor()100%11100%
get_SizeInBits()100%11100%
SetFeature(...)100%1212100%
SetFeature(...)100%22100%
IsFeatureSet(...)0%620%
IsFeatureSet(...)100%22100%
IsFeatureSet(...)100%22100%
IsFeatureSet(...)100%22100%
IsOptionAnchorsSet()0%620%
IsCompatible(...)100%2626100%
WriteToBitWriter(...)100%66100%
HasFeature(...)0%620%
DeserializeFromBytes(...)100%2.09271.43%
DeserializeFromBitReader(...)100%2.09271.43%
Combine(...)100%44100%
ToString()100%44100%
AreDependenciesSet()100%1010100%
OnChanged()100%22100%
GetLastIndexOfOne(...)75%4.25475%

File(s)

/home/runner/work/nlightning/nlightning/src/NLightning.Domain/Node/FeatureSet.cs

#LineLine coverage
 1using System.Collections;
 2using System.Runtime.Serialization;
 3using System.Text;
 4using NLightning.Domain.Utils.Interfaces;
 5
 6namespace NLightning.Domain.Node;
 7
 8using Enums;
 9
 10/// <summary>
 11/// Represents the features supported by a node. <see href="https://github.com/lightning/bolts/blob/master/09-features.m
 12/// </summary>
 13public class FeatureSet
 14{
 15    /// <summary>
 16    /// Some features are dependent on other features. This dictionary contains the dependencies.
 17    /// </summary>
 2018    private static readonly Dictionary<Feature, Feature[]> s_featureDependencies = new()
 2019    {
 2020        // This \/ --- Depends on this \/
 2021        { Feature.GossipQueriesEx, [Feature.GossipQueries] },
 2022        { Feature.PaymentSecret, [Feature.VarOnionOptin] },
 2023        { Feature.BasicMpp, [Feature.PaymentSecret] },
 2024        { Feature.OptionAnchorOutputs, [Feature.OptionStaticRemoteKey] },
 2025        { Feature.OptionAnchorsZeroFeeHtlcTx, [Feature.OptionStaticRemoteKey] },
 2026        { Feature.OptionRouteBlinding, [Feature.VarOnionOptin] },
 2027        { Feature.OptionZeroconf, [Feature.OptionScidAlias] },
 2028    };
 29
 30    internal BitArray FeatureFlags;
 31
 32    /// <summary>
 33    /// Initializes a new instance of the <see cref="FeatureSet"/> class.
 34    /// </summary>
 35    /// <remarks>
 36    /// Always set the bit of <see cref="Feature.VarOnionOptin"/> as Optional.
 37    /// </remarks>
 86838    public FeatureSet()
 39    {
 86840        FeatureFlags = new BitArray(128);
 41        // Always set the compulsory bit of var_onion_optin
 86842        SetFeature(Feature.VarOnionOptin, false);
 86843    }
 44
 45    public event EventHandler? Changed;
 46
 47    /// <summary>
 48    /// Gets the last index-of-one in the BitArray and add 1 because arrays starts at 0.
 49    /// </summary>
 30450    public int SizeInBits => GetLastIndexOfOne(FeatureFlags);
 51
 52    /// <summary>
 53    /// Sets a feature.
 54    /// </summary>
 55    /// <param name="feature">The feature to set.</param>
 56    /// <param name="isCompulsory">If the feature is compulsory.</param>
 57    /// <param name="isSet">true to set the feature, false to unset it</param>
 58    /// <remarks>
 59    /// If the feature has dependencies, they will be set first.
 60    /// The dependencies keep the same isCompulsory value as the feature being set.
 61    /// </remarks>
 62    public void SetFeature(Feature feature, bool isCompulsory, bool isSet = true)
 63    {
 64        // If we're setting the feature, and it has dependencies, set them first
 223265        if (isSet)
 66        {
 136067            if (s_featureDependencies.TryGetValue(feature, out var dependencies))
 68            {
 41669                foreach (var dependency in dependencies)
 10470                    SetFeature(dependency, isCompulsory, isSet);
 71            }
 72        }
 73        else // If we're unsetting the feature, and it has dependents, unset them first
 74        {
 964875            foreach (var dependent in s_featureDependencies.Where(x => x.Value.Contains(feature)).Select(x => x.Key))
 60076                SetFeature(dependent, isCompulsory, isSet);
 77        }
 78
 223279        var bitPosition = (int)feature;
 80
 223281        if (isCompulsory)
 82        {
 83            // Unset the non-compulsory bit
 32484            SetFeature(bitPosition, false);
 32485            --bitPosition;
 86        }
 87        else
 88        {
 89            // Unset the compulsory bit
 190890            SetFeature(bitPosition - 1, false);
 91        }
 92
 93        // Then set the feature itself
 223294        SetFeature(bitPosition, isSet);
 223295    }
 96
 97    /// <summary>
 98    /// Sets a feature.
 99    /// </summary>
 100    /// <param name="bitPosition">The bit position of the feature to set.</param>
 101    /// <param name="isSet">true to set the feature, false to unset it</param>
 102    public void SetFeature(int bitPosition, bool isSet)
 103    {
 4764104        if (bitPosition >= FeatureFlags.Length)
 64105            FeatureFlags.Length = bitPosition + 1;
 106
 4764107        FeatureFlags.Set(bitPosition, isSet);
 108
 4764109        OnChanged();
 4764110    }
 111
 112    /// <summary>
 113    /// Checks if a feature is set either as compulsory or optional.
 114    /// </summary>
 115    /// <param name="feature">Feature to check.</param>
 116    /// <returns>true if the feature is set, false otherwise.</returns>
 117    public bool IsFeatureSet(Feature feature)
 118    {
 0119        var bitPosition = (int)feature;
 120
 0121        return IsFeatureSet(bitPosition) || IsFeatureSet(bitPosition - 1);
 122    }
 123
 124    /// <summary>
 125    /// Checks if a feature is set.
 126    /// </summary>
 127    /// <param name="feature">Feature to check.</param>
 128    /// <param name="isCompulsory">If the feature is compulsory.</param>
 129    /// <returns>true if the feature is set, false otherwise.</returns>
 130    public bool IsFeatureSet(Feature feature, bool isCompulsory)
 131    {
 680132        var bitPosition = (int)feature;
 133
 134        // If the feature is compulsory, adjust the bit position to be even
 680135        if (isCompulsory)
 280136            bitPosition--;
 137
 680138        return IsFeatureSet(bitPosition);
 139    }
 140
 141    /// <summary>
 142    /// Checks if a feature is set.
 143    /// </summary>
 144    /// <param name="bitPosition">The bit position of the feature to check.</param>
 145    /// <param name="isCompulsory">If the feature is compulsory.</param>
 146    /// <returns>true if the feature is set, false otherwise.</returns>
 147    public bool IsFeatureSet(int bitPosition, bool isCompulsory)
 148    {
 149        // If the feature is compulsory, adjust the bit position to be even
 19188150        if (isCompulsory)
 9504151            bitPosition--;
 152
 19188153        return IsFeatureSet(bitPosition);
 154    }
 155
 156    /// <summary>
 157    /// Checks if a feature is set.
 158    /// </summary>
 159    /// <param name="bitPosition">The bit position of the feature to check.</param>
 160    /// <returns>true if the feature is set, false otherwise.</returns>
 161    private bool IsFeatureSet(int bitPosition)
 162    {
 22400163        return bitPosition < FeatureFlags.Length && FeatureFlags.Get(bitPosition);
 164    }
 165
 166    /// <summary>
 167    /// Checks if the option_anchor_outputs or option_anchors_zero_fee_htlc_tx feature is set.
 168    /// </summary>
 169    /// <returns>true if one of the features is set, false otherwise.</returns>
 170    public bool IsOptionAnchorsSet()
 171    {
 0172        return IsFeatureSet(Feature.OptionAnchorOutputs, false) ||
 0173               IsFeatureSet(Feature.OptionAnchorsZeroFeeHtlcTx, false);
 174    }
 175
 176    /// <summary>
 177    /// Check if this feature set is compatible with the other provided feature set.
 178    /// </summary>
 179    /// <param name="other">The other feature set to check compatibility with.</param>
 180    /// <param name="negotiatedFeatureSet">The resulting negotiated feature set.</param>
 181    /// <returns>true if the feature sets are compatible, false otherwise.</returns>
 182    /// <remarks>
 183    /// The other feature set must support the var_onion_optin feature.
 184    /// The other feature set must have all the dependencies set.
 185    /// </remarks>
 186    public bool IsCompatible(FeatureSet other, out FeatureSet? negotiatedFeatureSet)
 187    {
 188        // Check if the other node supports var_onion_optin
 120189        if (!other.IsFeatureSet(Feature.VarOnionOptin, false) && !other.IsFeatureSet(Feature.VarOnionOptin, true))
 190        {
 4191            negotiatedFeatureSet = null;
 4192            return false;
 193        }
 194
 195        // Check which one is bigger and iterate on it
 116196        var maxLength = Math.Max(FeatureFlags.Length, other.FeatureFlags.Length);
 197
 198        // Create a temporary feature set to store the negotiated features
 116199        negotiatedFeatureSet = new FeatureSet();
 7776200        for (var i = 1; i < maxLength; i += 2)
 201        {
 3784202            var isLocalOptionalSet = IsFeatureSet(i, false);
 3784203            var isLocalCompulsorySet = IsFeatureSet(i, true);
 3784204            var isOtherOptionalSet = other.IsFeatureSet(i, false);
 3784205            var isOtherCompulsorySet = other.IsFeatureSet(i, true);
 206
 207            // If the feature is unknown
 3784208            if (!Enum.IsDefined(typeof(Feature), i))
 209            {
 210                // If the feature is unknown and even, close the connection
 2168211                if (isOtherCompulsorySet)
 212                {
 4213                    negotiatedFeatureSet = null;
 4214                    return false;
 215                }
 216
 2164217                if (isOtherOptionalSet)
 20218                    negotiatedFeatureSet.SetFeature(i, false);
 219            }
 220            else
 221            {
 222                // If the local feature is compulsory, the other feature should also be set (either optional or compulso
 1616223                if (isLocalCompulsorySet && !(isOtherOptionalSet || isOtherCompulsorySet))
 224                {
 4225                    negotiatedFeatureSet = null;
 4226                    return false;
 227                }
 228
 229                // If the other feature is compulsory, the local feature should also be set (either optional or compulso
 1612230                if (isOtherCompulsorySet && !(isLocalOptionalSet || isLocalCompulsorySet))
 231                {
 4232                    negotiatedFeatureSet = null;
 4233                    return false;
 234                }
 235
 1608236                if (isOtherCompulsorySet || isLocalCompulsorySet)
 237                {
 64238                    negotiatedFeatureSet.SetFeature(i, true);
 239                }
 1544240                else if (isLocalOptionalSet && isOtherOptionalSet)
 241                {
 136242                    negotiatedFeatureSet.SetFeature(i, false);
 243                }
 244            }
 245        }
 246
 247        // Check if all the other node's dependencies are set
 104248        if (other.AreDependenciesSet())
 100249            return true;
 250
 4251        negotiatedFeatureSet = null;
 4252        return false;
 253    }
 254
 255    /// <summary>
 256    /// Serializes the features to a byte array.
 257    /// </summary>
 258    public void WriteToBitWriter(IBitWriter bitWriter, int length, bool shouldPad)
 259    {
 260        // Check if _featureFlags is as long as the length
 64261        var extraLength = length - FeatureFlags.Length;
 64262        if (extraLength > 0)
 4263            FeatureFlags.Length += extraLength;
 264
 4648265        for (var i = 0; i < length && bitWriter.HasMoreBits(1); i++)
 2260266            bitWriter.WriteBit(FeatureFlags[length - i - (shouldPad ? 0 : 1)]);
 64267    }
 268
 269    /// <summary>
 270    /// Checks if a feature is set.
 271    /// </summary>
 272    /// <param name="feature">The feature to check.</param>
 273    /// <returns>true if the feature is set, false otherwise.</returns>
 274    /// <remarks>
 275    /// We don't care if the feature is compulsory or optional.
 276    /// </remarks>
 0277    public bool HasFeature(Feature feature) => IsFeatureSet(feature, false) || IsFeatureSet(feature, true);
 278
 279    /// <summary>
 280    /// Deserializes the features from a byte array.
 281    /// </summary>
 282    /// <param name="data">The byte array to deserialize from.</param>
 283    /// <remarks>
 284    /// The byte array can have a length less than or equal to 8 bytes.
 285    /// </remarks>
 286    /// <returns>The deserialized features.</returns>
 287    /// <exception cref="SerializationException">Error deserializing Features</exception>
 288    public static FeatureSet DeserializeFromBytes(byte[] data)
 289    {
 290        try
 291        {
 168292            if (BitConverter.IsLittleEndian)
 168293                Array.Reverse(data);
 294
 168295            var bitArray = new BitArray(data);
 168296            return new FeatureSet { FeatureFlags = bitArray };
 297        }
 0298        catch (Exception e)
 299        {
 0300            throw new SerializationException("Error deserializing Features", e);
 301        }
 168302    }
 303
 304    /// <summary>
 305    /// Deserializes the features from a BitReader.
 306    /// </summary>
 307    /// <param name="bitReader">The bit reader to read from.</param>
 308    /// <param name="length">The number of bits to read.</param>
 309    /// <param name="shouldPad">If the bit array should be padded.</param>
 310    /// <returns>The deserialized features.</returns>
 311    /// <exception cref="SerializationException">Error deserializing Features</exception>
 312    public static FeatureSet DeserializeFromBitReader(IBitReader bitReader, int length, bool shouldPad)
 313    {
 314        try
 315        {
 316            // Create a new bit array
 76317            var bitArray = new BitArray(length + (shouldPad ? 1 : 0));
 5712318            for (var i = 0; i < length; i++)
 2780319                bitArray.Set(length - i - (shouldPad ? 0 : 1), bitReader.ReadBit());
 320
 76321            return new FeatureSet { FeatureFlags = bitArray };
 322        }
 0323        catch (Exception e)
 324        {
 0325            throw new SerializationException("Error deserializing Features", e);
 326        }
 76327    }
 328
 329    /// <summary>
 330    /// Combines two feature sets.
 331    /// </summary>
 332    /// <param name="first">The first feature set.</param>
 333    /// <param name="second">The second feature set.</param>
 334    /// <returns>The combined feature set.</returns>
 335    /// <remarks>
 336    /// The combined feature set is the logical OR of the two feature sets.
 337    /// </remarks>
 338    public static FeatureSet Combine(FeatureSet first, FeatureSet second)
 339    {
 16340        var combinedLength = Math.Max(first.FeatureFlags.Length, second.FeatureFlags.Length);
 16341        var combinedFlags = new BitArray(combinedLength);
 342
 1440343        for (var i = 0; i < combinedLength; i++)
 704344            combinedFlags.Set(i, first.IsFeatureSet(i) || second.IsFeatureSet(i));
 345
 16346        return new FeatureSet { FeatureFlags = combinedFlags };
 347    }
 348
 349    public override string ToString()
 350    {
 16351        var sb = new StringBuilder();
 2336352        for (var i = 0; i < FeatureFlags.Length; i++)
 353        {
 1152354            if (IsFeatureSet(i))
 16355                sb.Append($"{(Feature)i}, ");
 356        }
 357
 16358        return sb.ToString().TrimEnd(' ', ',');
 359    }
 360
 361    /// <summary>
 362    /// Checks if all dependencies are set.
 363    /// </summary>
 364    /// <returns>true if all dependencies are set, false otherwise.</returns>
 365    /// <remarks>
 366    /// This method is used to check if all dependencies are set when a feature is set.
 367    /// </remarks>
 368    private bool AreDependenciesSet()
 369    {
 370        // Check if all known (Feature Enum) dependencies are set if the feature is set
 4364371        foreach (var feature in Enum.GetValues<Feature>())
 372        {
 2080373            if (!IsFeatureSet((int)feature, false) && !IsFeatureSet((int)feature, true))
 374                continue;
 375
 208376            if (!s_featureDependencies.TryGetValue(feature, out var dependencies))
 377                continue;
 378
 144379            if (dependencies.Any(dependency => !IsFeatureSet(dependency, false) && !IsFeatureSet(dependency, true)))
 4380                return false;
 381        }
 382
 100383        return true;
 384    }
 385
 386    private void OnChanged()
 387    {
 4764388        Changed?.Invoke(this, EventArgs.Empty);
 532389    }
 390
 391    private static int GetLastIndexOfOne(BitArray bitArray)
 392    {
 880393        for (var i = bitArray.Length - 1; i >= 0; i--)
 394        {
 440395            if (bitArray[i])
 304396                return i;
 397        }
 398
 0399        return -1; // Return -1 if no number 1 is found
 400    }
 401}