< 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: 30_15166811759
Line coverage
93%
Covered lines: 96
Uncovered lines: 7
Coverable lines: 103
Total lines: 397
Line coverage: 93.2%
Branch coverage
93%
Covered branches: 71
Total branches: 76
Branch coverage: 93.4%
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(...)100%22100%
IsFeatureSet(...)100%22100%
IsFeatureSet(...)100%22100%
IsOptionAnchorsSet()0%620%
IsCompatible(...)100%1818100%
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;
 4
 5namespace NLightning.Domain.Node;
 6
 7using Enums;
 8using Serialization;
 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>
 78038    public FeatureSet()
 39    {
 78040        FeatureFlags = new BitArray(128);
 41        // Always set the compulsory bit of var_onion_optin
 78042        SetFeature(Feature.VarOnionOptin, false);
 78043    }
 44
 45    public event EventHandler? Changed;
 46
 47    /// <summary>
 48    /// Gets the position of 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
 329265        if (isSet)
 66        {
 242067            if (s_featureDependencies.TryGetValue(feature, out var dependencies))
 68            {
 187269                foreach (var dependency in dependencies)
 70                {
 46871                    SetFeature(dependency, isCompulsory, isSet);
 72                }
 73            }
 74        }
 75        else // If we're unsetting the feature, and it has dependents, unset them first
 76        {
 964877            foreach (var dependent in s_featureDependencies.Where(x => x.Value.Contains(feature)).Select(x => x.Key))
 78            {
 60079                SetFeature(dependent, isCompulsory, isSet);
 80            }
 81        }
 82
 329283        var bitPosition = (int)feature;
 84
 329285        if (isCompulsory)
 86        {
 87            // Unset the non-compulsory bit
 57288            SetFeature(bitPosition, false);
 57289            --bitPosition;
 90        }
 91        else
 92        {
 93            // Unset the compulsory bit
 272094            SetFeature(bitPosition - 1, false);
 95        }
 96
 97        // Then set the feature itself
 329298        SetFeature(bitPosition, isSet);
 329299    }
 100    /// <summary>
 101    /// Sets a feature.
 102    /// </summary>
 103    /// <param name="bitPosition">The bit position of the feature to set.</param>
 104    /// <param name="isSet">true to set the feature, false to unset it</param>
 105    public void SetFeature(int bitPosition, bool isSet)
 106    {
 6664107        if (bitPosition >= FeatureFlags.Length)
 108        {
 64109            FeatureFlags.Length = bitPosition + 1;
 110        }
 111
 6664112        FeatureFlags.Set(bitPosition, isSet);
 113
 6664114        OnChanged();
 6664115    }
 116
 117    /// <summary>
 118    /// Checks if a feature is set.
 119    /// </summary>
 120    /// <param name="feature">Feature to check.</param>
 121    /// <param name="isCompulsory">If the feature is compulsory.</param>
 122    /// <returns>true if the feature is set, false otherwise.</returns>
 123    public bool IsFeatureSet(Feature feature, bool isCompulsory)
 124    {
 880125        var bitPosition = (int)feature;
 126
 127        // If the feature is compulsory, adjust the bit position to be even
 880128        if (isCompulsory)
 129        {
 356130            bitPosition--;
 131        }
 132
 880133        return IsFeatureSet(bitPosition);
 134    }
 135    /// <summary>
 136    /// Checks if a feature is set.
 137    /// </summary>
 138    /// <param name="bitPosition">The bit position of the feature to check.</param>
 139    /// <param name="isCompulsory">If the feature is compulsory.</param>
 140    /// <returns>true if the feature is set, false otherwise.</returns>
 141    public bool IsFeatureSet(int bitPosition, bool isCompulsory)
 142    {
 143        // If the feature is compulsory, adjust the bit position to be even
 21868144        if (isCompulsory)
 145        {
 10792146            bitPosition--;
 147        }
 148
 21868149        return IsFeatureSet(bitPosition);
 150    }
 151    /// <summary>
 152    /// Checks if a feature is set.
 153    /// </summary>
 154    /// <param name="bitPosition">The bit position of the feature to check.</param>
 155    /// <returns>true if the feature is set, false otherwise.</returns>
 156    private bool IsFeatureSet(int bitPosition)
 157    {
 25280158        if (bitPosition >= FeatureFlags.Length)
 159        {
 1156160            return false;
 161        }
 162
 24124163        return 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 are set, false otherwise.</returns>
 170    public bool IsOptionAnchorsSet()
 171    {
 0172        return IsFeatureSet(Feature.OptionAnchorOutputs, false) || IsFeatureSet(Feature.OptionAnchorsZeroFeeHtlcTx, fals
 173    }
 174
 175    /// <summary>
 176    /// Check if this feature set is compatible with the other provided feature set.
 177    /// </summary>
 178    /// <param name="other">The other feature set to check compatibility with.</param>
 179    /// <returns>true if the feature sets are compatible, false otherwise.</returns>
 180    /// <remarks>
 181    /// The other feature set must support the var_onion_optin feature.
 182    /// The other feature set must have all dependencies set.
 183    /// </remarks>
 184    public bool IsCompatible(FeatureSet other)
 185    {
 186        // Check if the other node supports var_onion_optin
 132187        if (!other.IsFeatureSet(Feature.VarOnionOptin, false) && !other.IsFeatureSet(Feature.VarOnionOptin, true))
 188        {
 4189            return false;
 190        }
 191
 9024192        for (var i = 1; i < FeatureFlags.Length; i += 2)
 193        {
 4400194            var isLocalOptionalSet = IsFeatureSet(i, false);
 4400195            var isLocalCompulsorySet = IsFeatureSet(i, true);
 4400196            var isOtherOptionalSet = other.IsFeatureSet(i, false);
 4400197            var isOtherCompulsorySet = other.IsFeatureSet(i, true);
 198
 199            // If feature is unknown
 4400200            if (!Enum.IsDefined(typeof(Feature), i))
 201            {
 202                // If the feature is unknown and even, close the connection
 2544203                if (isOtherCompulsorySet)
 204                {
 4205                    return false;
 206                }
 207            }
 208            else
 209            {
 210                // If the local feature is compulsory, the other feature should also be set (either optional or compulso
 1856211                if (isLocalCompulsorySet && !(isOtherOptionalSet || isOtherCompulsorySet))
 212                {
 4213                    return false;
 214                }
 215
 216                // If the other feature is compulsory, the local feature should also be set (either optional or compulso
 1852217                if (isOtherCompulsorySet && !(isLocalOptionalSet || isLocalCompulsorySet))
 218                {
 8219                    return false;
 220                }
 221            }
 222        }
 223
 224        // Check if all the other node's dependencies are set
 112225        return other.AreDependenciesSet();
 226    }
 227
 228    /// <summary>
 229    /// Serializes the features to a byte array.
 230    /// </summary>
 231    public void WriteToBitWriter(IBitWriter bitWriter, int length, bool shouldPad)
 232    {
 233        // Check if _featureFlags is as long as the length
 64234        var extraLength = length - FeatureFlags.Length;
 64235        if (extraLength > 0)
 236        {
 4237            FeatureFlags.Length += extraLength;
 238        }
 239
 4648240        for (var i = 0; i < length && bitWriter.HasMoreBits(1); i++)
 241        {
 2260242            bitWriter.WriteBit(FeatureFlags[length - i - (shouldPad ? 0 : 1)]);
 243        }
 64244    }
 245
 246    /// <summary>
 247    /// Checks if a feature is set.
 248    /// </summary>
 249    /// <param name="feature">The feature to check.</param>
 250    /// <returns>true if the feature is set, false otherwise.</returns>
 251    /// <remarks>
 252    /// We don't care if the feature is compulsory or optional.
 253    /// </remarks>
 254    public bool HasFeature(Feature feature)
 255    {
 256        // Check if feature is either set as compulsory or optional
 0257        return IsFeatureSet(feature, false) || IsFeatureSet(feature, true);
 258    }
 259
 260    /// <summary>
 261    /// Deserializes the features from a byte array.
 262    /// </summary>
 263    /// <param name="data">The byte array to deserialize from.</param>
 264    /// <remarks>
 265    /// The byte array can have a length less than or equal to 8 bytes.
 266    /// </remarks>
 267    /// <returns>The deserialized features.</returns>
 268    /// <exception cref="SerializationException">Error deserializing Features</exception>
 269    public static FeatureSet DeserializeFromBytes(byte[] data)
 270    {
 271        try
 272        {
 136273            if (BitConverter.IsLittleEndian)
 274            {
 136275                Array.Reverse(data);
 276            }
 277
 136278            var bitArray = new BitArray(data);
 136279            return new FeatureSet { FeatureFlags = bitArray };
 280        }
 0281        catch (Exception e)
 282        {
 0283            throw new SerializationException("Error deserializing Features", e);
 284        }
 136285    }
 286
 287    /// <summary>
 288    /// Deserializes the features from a BitReader.
 289    /// </summary>
 290    /// <param name="bitReader">The bit reader to read from.</param>
 291    /// <param name="length">The number of bits to read.</param>
 292    /// <param name="shouldPad">If the bit array should be padded.</param>
 293    /// <returns>The deserialized features.</returns>
 294    /// <exception cref="SerializationException">Error deserializing Features</exception>
 295    public static FeatureSet DeserializeFromBitReader(IBitReader bitReader, int length, bool shouldPad)
 296    {
 297        try
 298        {
 299            // Create a new bit array
 76300            var bitArray = new BitArray(length + (shouldPad ? 1 : 0));
 5712301            for (var i = 0; i < length; i++)
 302            {
 2780303                bitArray.Set(length - i - (shouldPad ? 0 : 1), bitReader.ReadBit());
 304            }
 305
 76306            return new FeatureSet { FeatureFlags = bitArray };
 307        }
 0308        catch (Exception e)
 309        {
 0310            throw new SerializationException("Error deserializing Features", e);
 311        }
 76312    }
 313
 314    /// <summary>
 315    /// Combines two feature sets.
 316    /// </summary>
 317    /// <param name="first">The first feature set.</param>
 318    /// <param name="second">The second feature set.</param>
 319    /// <returns>The combined feature set.</returns>
 320    /// <remarks>
 321    /// The combined feature set is the logical OR of the two feature sets.
 322    /// </remarks>
 323    public static FeatureSet Combine(FeatureSet first, FeatureSet second)
 324    {
 16325        var combinedLength = Math.Max(first.FeatureFlags.Length, second.FeatureFlags.Length);
 16326        var combinedFlags = new BitArray(combinedLength);
 327
 1440328        for (var i = 0; i < combinedLength; i++)
 329        {
 704330            combinedFlags.Set(i, first.IsFeatureSet(i) || second.IsFeatureSet(i));
 331        }
 332
 16333        return new FeatureSet { FeatureFlags = combinedFlags };
 334    }
 335
 336    public override string ToString()
 337    {
 16338        var sb = new StringBuilder();
 2336339        for (var i = 0; i < FeatureFlags.Length; i++)
 340        {
 1152341            if (IsFeatureSet(i))
 342            {
 16343                sb.Append($"{(Feature)i}, ");
 344            }
 345        }
 346
 16347        return sb.ToString().TrimEnd(' ', ',');
 348    }
 349
 350    /// <summary>
 351    /// Checks if all dependencies are set.
 352    /// </summary>
 353    /// <returns>true if all dependencies are set, false otherwise.</returns>
 354    /// <remarks>
 355    /// This method is used to check if all dependencies are set when a feature is set.
 356    /// </remarks>
 357    private bool AreDependenciesSet()
 358    {
 359        // Check if all known (Feature Enum) dependencies are set if the feature is set
 4700360        foreach (var feature in Enum.GetValues<Feature>())
 361        {
 2240362            if (!IsFeatureSet((int)feature, false) && !IsFeatureSet((int)feature, true))
 363            {
 364                continue;
 365            }
 366
 320367            if (!s_featureDependencies.TryGetValue(feature, out var dependencies))
 368            {
 369                continue;
 370            }
 371
 224372            if (dependencies.Any(dependency => !IsFeatureSet(dependency, false) && !IsFeatureSet(dependency, true)))
 373            {
 4374                return false;
 375            }
 376        }
 377
 108378        return true;
 379    }
 380
 381    private void OnChanged()
 382    {
 6664383        Changed?.Invoke(this, EventArgs.Empty);
 532384    }
 385
 386    private static int GetLastIndexOfOne(BitArray bitArray)
 387    {
 880388        for (var i = bitArray.Length - 1; i >= 0; i--)
 389        {
 440390            if (bitArray[i])
 391            {
 304392                return i;
 393            }
 394        }
 0395        return -1; // Return -1 if no 1 is found
 396    }
 397}