bolt9

Lightning feature flags, per BOLT #9 (docs.ppad.tech/bolt9).
git clone git://git.ppad.tech/bolt9.git
Log | Files | Refs | README | LICENSE

Features.hs (5457B)


      1 {-# OPTIONS_HADDOCK prune #-}
      2 {-# LANGUAGE BangPatterns #-}
      3 {-# LANGUAGE DeriveGeneric #-}
      4 
      5 -- |
      6 -- Module: Lightning.Protocol.BOLT9.Features
      7 -- Copyright: (c) 2025 Jared Tobin
      8 -- License: MIT
      9 -- Maintainer: Jared Tobin <jared@ppad.tech>
     10 --
     11 -- Known feature table for the Lightning Network, per
     12 -- [BOLT #9](https://github.com/lightning/bolts/blob/master/09-features.md).
     13 
     14 module Lightning.Protocol.BOLT9.Features (
     15     -- * Feature
     16     Feature(..)
     17 
     18     -- * Known feature
     19   , KnownFeature(..)
     20   , knownFeatureByBit
     21   , knownFeatureByName
     22 
     23     -- * Lookup
     24   , featureByBit
     25   , featureByName
     26 
     27     -- * Known features table
     28   , knownFeatures
     29   ) where
     30 
     31 import Control.DeepSeq (NFData)
     32 import Data.IntMap.Strict (IntMap)
     33 import qualified Data.IntMap.Strict as IM
     34 import Data.Map.Strict (Map)
     35 import qualified Data.Map.Strict as M
     36 import Data.Word (Word16)
     37 import GHC.Generics (Generic)
     38 import Lightning.Protocol.BOLT9.Types (Context(..))
     39 
     40 -- | A known feature from the BOLT #9 specification.
     41 data Feature = Feature {
     42     featureName         :: !String
     43     -- ^ The canonical name of the feature.
     44   , featureBaseBit      :: {-# UNPACK #-} !Word16
     45     -- ^ The even (compulsory) bit number; the odd (optional) bit is
     46     --   @baseBit + 1@.
     47   , featureContexts     :: ![Context]
     48     -- ^ Contexts in which this feature may be presented.
     49   , featureDependencies :: ![String]
     50     -- ^ Names of features this feature depends on.
     51   , featureAssumed      :: !Bool
     52     -- ^ Whether this feature is assumed to be universally supported.
     53   }
     54   deriving (Eq, Show, Generic)
     55 
     56 instance NFData Feature
     57 
     58 -- | A feature that is known to be in the BOLT #9 specification.
     59 --
     60 -- Constructed via 'knownFeatureByBit' or 'knownFeatureByName',
     61 -- which validate that the feature exists in the known table.
     62 newtype KnownFeature = KnownFeature {
     63     unKnownFeature :: Feature
     64     -- ^ Extract the underlying 'Feature'.
     65   }
     66   deriving (Eq, Show, Generic)
     67 
     68 instance NFData KnownFeature
     69 
     70 -- | Look up a known feature by bit number.
     71 --
     72 --   Accepts either the even (compulsory) or odd (optional) bit.
     73 --
     74 --   >>> fmap (featureName . unKnownFeature) (knownFeatureByBit 16)
     75 --   Just "basic_mpp"
     76 --   >>> knownFeatureByBit 999
     77 --   Nothing
     78 knownFeatureByBit :: Word16 -> Maybe KnownFeature
     79 knownFeatureByBit !bit = fmap KnownFeature (featureByBit bit)
     80 {-# INLINE knownFeatureByBit #-}
     81 
     82 -- | Look up a known feature by its canonical name.
     83 --
     84 --   >>> fmap (featureName . unKnownFeature)
     85 --   ...       (knownFeatureByName "basic_mpp")
     86 --   Just "basic_mpp"
     87 --   >>> knownFeatureByName "nonexistent"
     88 --   Nothing
     89 knownFeatureByName :: String -> Maybe KnownFeature
     90 knownFeatureByName !name =
     91   fmap KnownFeature (featureByName name)
     92 {-# INLINE knownFeatureByName #-}
     93 
     94 -- | The complete table of known features from BOLT #9.
     95 knownFeatures :: [Feature]
     96 knownFeatures = [
     97     Feature "option_data_loss_protect" 0 [] [] True
     98   , Feature "option_upfront_shutdown_script" 4 [Init, NodeAnn] [] False
     99   , Feature "gossip_queries" 6 [] [] False
    100   , Feature "var_onion_optin" 8 [] [] True
    101   , Feature "gossip_queries_ex" 10 [Init, NodeAnn] [] False
    102   , Feature "option_static_remotekey" 12 [] [] True
    103   , Feature "payment_secret" 14 [] [] True
    104   , Feature "basic_mpp" 16 [Init, NodeAnn, Invoice]
    105       ["payment_secret"] False
    106   , Feature "option_support_large_channel" 18 [Init, NodeAnn] [] False
    107   , Feature "option_anchors" 22 [Init, NodeAnn, ChanType] [] False
    108   , Feature "option_route_blinding" 24 [Init, NodeAnn, Invoice] [] False
    109   , Feature "option_shutdown_anysegwit" 26 [Init, NodeAnn] [] False
    110   , Feature "option_dual_fund" 28 [Init, NodeAnn] [] False
    111   , Feature "option_quiesce" 34 [Init, NodeAnn] [] False
    112   , Feature "option_attribution_data" 36 [Init, NodeAnn, Invoice]
    113       [] False
    114   , Feature "option_onion_messages" 38 [Init, NodeAnn] [] False
    115   , Feature "option_provide_storage" 42 [Init, NodeAnn] [] False
    116   , Feature "option_channel_type" 44 [] [] True
    117   , Feature "option_scid_alias" 46 [Init, NodeAnn, ChanType] [] False
    118   , Feature "option_payment_metadata" 48 [Invoice] [] False
    119   , Feature "option_zeroconf" 50 [Init, NodeAnn, ChanType]
    120       ["option_scid_alias"] False
    121   , Feature "option_simple_close" 60 [Init, NodeAnn]
    122       ["option_shutdown_anysegwit"] False
    123   ]
    124 
    125 -- | Look up a feature by bit number.
    126 --
    127 --   Accepts either the even (compulsory) or odd (optional) bit of the pair.
    128 --
    129 --   >>> fmap featureName (featureByBit 16)
    130 --   Just "basic_mpp"
    131 --   >>> fmap featureName (featureByBit 17)  -- odd bit also works
    132 --   Just "basic_mpp"
    133 --   >>> featureByBit 999
    134 --   Nothing
    135 featureByBit :: Word16 -> Maybe Feature
    136 featureByBit !bit =
    137   let !baseBit = fromIntegral bit - (fromIntegral bit `mod` 2)
    138   in  IM.lookup baseBit featuresByBit
    139 {-# INLINE featureByBit #-}
    140 
    141 -- | Look up a feature by its canonical name.
    142 --
    143 --   >>> fmap featureBaseBit (featureByName "basic_mpp")
    144 --   Just 16
    145 --   >>> featureByName "nonexistent"
    146 --   Nothing
    147 featureByName :: String -> Maybe Feature
    148 featureByName !name = M.lookup name featuresByName
    149 {-# INLINE featureByName #-}
    150 
    151 -- Lookup tables -------------------------------------------------------------
    152 
    153 -- | Features indexed by base bit (even bit number).
    154 featuresByBit :: IntMap Feature
    155 featuresByBit = IM.fromList
    156   [(fromIntegral (featureBaseBit f), f) | f <- knownFeatures]
    157 
    158 -- | Features indexed by canonical name.
    159 featuresByName :: Map String Feature
    160 featuresByName = M.fromList
    161   [(featureName f, f) | f <- knownFeatures]