bolt7

Routing gossip protocol, per BOLT #7 (docs.ppad.tech/bolt7).
git clone git://git.ppad.tech/bolt7.git
Log | Files | Refs | README | LICENSE

Types.hs (13868B)


      1 {-# OPTIONS_HADDOCK hide #-}
      2 {-# LANGUAGE BangPatterns #-}
      3 {-# LANGUAGE DeriveGeneric #-}
      4 
      5 -- |
      6 -- Module: Lightning.Protocol.BOLT7.Types
      7 -- Copyright: (c) 2025 Jared Tobin
      8 -- License: MIT
      9 -- Maintainer: Jared Tobin <jared@ppad.tech>
     10 --
     11 -- Field types for BOLT #7 gossip messages.
     12 
     13 module Lightning.Protocol.BOLT7.Types (
     14   -- * Short channel ids
     15     format_short_channel_id
     16 
     17   -- * channel_update flags
     18   , MessageFlags(..)
     19   , Forwarding(..)
     20   , message_flags
     21   , message_flags_forwarding
     22   , has_must_be_one
     23   , ChannelFlags(..)
     24   , Direction(..)
     25   , ChannelStatus(..)
     26   , channel_flags
     27   , channel_flags_direction
     28   , channel_flags_status
     29 
     30   -- * node_announcement fields
     31   , RgbColor(..)
     32   , Alias(..)
     33   , alias
     34   , un_alias
     35   , Address(..)
     36   , IPv4Addr(..)
     37   , ipv4_addr
     38   , un_ipv4_addr
     39   , IPv6Addr(..)
     40   , ipv6_addr
     41   , un_ipv6_addr
     42   , TorV2Addr(..)
     43   , tor_v2_addr
     44   , un_tor_v2_addr
     45   , TorV3Addr(..)
     46   , tor_v3_addr
     47   , un_tor_v3_addr
     48   , Hostname(..)
     49   , hostname
     50   , un_hostname
     51   , is_ascii_hostname
     52   , usable_addresses
     53 
     54   -- * Query fields
     55   , QueryFlags(..)
     56   , QueryFlag(..)
     57   , query_flags
     58   , has_query_flag
     59   , QueryOption(..)
     60   , QueryOptionFlag(..)
     61   , query_option
     62   , has_query_option
     63   , ChannelUpdateTimestamps(..)
     64   , ChannelUpdateChecksums(..)
     65   ) where
     66 
     67 import Control.DeepSeq (NFData)
     68 import Data.Bits ((.|.), bit, testBit)
     69 import qualified Data.ByteString as BS
     70 import Data.Word (Word8, Word16, Word32, Word64)
     71 import GHC.Generics (Generic)
     72 import Lightning.Protocol.BOLT1
     73   (ShortChannelId, scid_block_height, scid_tx_index, scid_output_index)
     74 
     75 -- short channel ids ----------------------------------------------------------
     76 
     77 -- | Render a short channel id in the human-readable format of BOLT #7:
     78 --   block height, transaction index and output index, in decimal,
     79 --   separated by @x@.
     80 --
     81 --   >>> fmap format_short_channel_id (short_channel_id 539268 845 1)
     82 --   Just "539268x845x1"
     83 format_short_channel_id :: ShortChannelId -> String
     84 format_short_channel_id s =
     85   show (scid_block_height s) ++ "x" ++ show (scid_tx_index s) ++ "x"
     86     ++ show (scid_output_index s)
     87 
     88 -- channel_update flags -------------------------------------------------------
     89 
     90 -- | The @message_flags@ byte of a @channel_update@: bit 0 is
     91 --   @must_be_one@, bit 1 is @dont_forward@, and the other bits are
     92 --   unassigned. Every byte is a valid value; unassigned bits are kept so
     93 --   that re-encoding reproduces the signed bytes.
     94 newtype MessageFlags = MessageFlags Word8
     95   deriving (Eq, Show, Generic)
     96 
     97 instance NFData MessageFlags
     98 
     99 -- | Whether a @channel_update@ may be relayed to other peers (the
    100 --   @dont_forward@ bit).
    101 data Forwarding
    102   = Forward      -- ^ @dont_forward@ is 0
    103   | DontForward  -- ^ @dont_forward@ is 1, as for unannounced channels
    104   deriving (Eq, Ord, Show, Generic)
    105 
    106 instance NFData Forwarding
    107 
    108 -- | The t'MessageFlags' an origin node sets: @must_be_one@ set, the given
    109 --   @dont_forward@ bit, and no unassigned bits.
    110 --
    111 --   >>> message_flags DontForward
    112 --   MessageFlags 3
    113 message_flags :: Forwarding -> MessageFlags
    114 message_flags Forward     = MessageFlags 0x01
    115 message_flags DontForward = MessageFlags 0x03
    116 {-# INLINE message_flags #-}
    117 
    118 -- | The @dont_forward@ bit of t'MessageFlags'.
    119 --
    120 --   >>> message_flags_forwarding (MessageFlags 0x01)
    121 --   Forward
    122 message_flags_forwarding :: MessageFlags -> Forwarding
    123 message_flags_forwarding (MessageFlags w)
    124   | testBit w 1 = DontForward
    125   | otherwise   = Forward
    126 {-# INLINE message_flags_forwarding #-}
    127 
    128 -- | Whether the @must_be_one@ bit is set. Senders must set it; receivers
    129 --   ignore it.
    130 --
    131 --   >>> has_must_be_one (MessageFlags 0x00)
    132 --   False
    133 has_must_be_one :: MessageFlags -> Bool
    134 has_must_be_one (MessageFlags w) = testBit w 0
    135 {-# INLINE has_must_be_one #-}
    136 
    137 -- | The @channel_flags@ byte of a @channel_update@: bit 0 is
    138 --   @direction@, bit 1 is @disable@, and the other bits are unassigned.
    139 --   Every byte is a valid value; unassigned bits are kept so that
    140 --   re-encoding reproduces the signed bytes.
    141 newtype ChannelFlags = ChannelFlags Word8
    142   deriving (Eq, Show, Generic)
    143 
    144 instance NFData ChannelFlags
    145 
    146 -- | The node a @channel_update@ originates from.
    147 data Direction
    148   = NodeOne  -- ^ @node_id_1@ (@direction@ is 0)
    149   | NodeTwo  -- ^ @node_id_2@ (@direction@ is 1)
    150   deriving (Eq, Ord, Show, Generic)
    151 
    152 instance NFData Direction
    153 
    154 -- | Whether a channel direction is usable.
    155 data ChannelStatus
    156   = Enabled   -- ^ @disable@ is 0
    157   | Disabled  -- ^ @disable@ is 1
    158   deriving (Eq, Ord, Show, Generic)
    159 
    160 instance NFData ChannelStatus
    161 
    162 -- | The t'ChannelFlags' for the given direction and status, with no
    163 --   unassigned bits set.
    164 --
    165 --   >>> channel_flags NodeTwo Disabled
    166 --   ChannelFlags 3
    167 channel_flags :: Direction -> ChannelStatus -> ChannelFlags
    168 channel_flags d s = ChannelFlags (dir .|. sta)
    169   where
    170     dir = case d of
    171       NodeOne -> 0x00
    172       NodeTwo -> 0x01
    173     sta = case s of
    174       Enabled  -> 0x00
    175       Disabled -> 0x02
    176 {-# INLINE channel_flags #-}
    177 
    178 -- | The @direction@ bit of t'ChannelFlags'.
    179 --
    180 --   >>> channel_flags_direction (ChannelFlags 0x05)
    181 --   NodeTwo
    182 channel_flags_direction :: ChannelFlags -> Direction
    183 channel_flags_direction (ChannelFlags w)
    184   | testBit w 0 = NodeTwo
    185   | otherwise   = NodeOne
    186 {-# INLINE channel_flags_direction #-}
    187 
    188 -- | The @disable@ bit of t'ChannelFlags'.
    189 --
    190 --   >>> channel_flags_status (ChannelFlags 0x02)
    191 --   Disabled
    192 channel_flags_status :: ChannelFlags -> ChannelStatus
    193 channel_flags_status (ChannelFlags w)
    194   | testBit w 1 = Disabled
    195   | otherwise   = Enabled
    196 {-# INLINE channel_flags_status #-}
    197 
    198 -- node_announcement fields ---------------------------------------------------
    199 
    200 -- | A node's colour: red, green and blue.
    201 data RgbColor = RgbColor
    202   {-# UNPACK #-} !Word8
    203   {-# UNPACK #-} !Word8
    204   {-# UNPACK #-} !Word8
    205   deriving (Eq, Show, Generic)
    206 
    207 instance NFData RgbColor
    208 
    209 -- | A 32-byte node alias. Origin nodes should use UTF-8 padded with zero
    210 --   bytes, but receivers must accept any bytes.
    211 newtype Alias = Alias BS.ByteString
    212   deriving (Eq, Show, Generic)
    213 
    214 instance NFData Alias
    215 
    216 -- | Construct an t'Alias' from exactly 32 bytes.
    217 --
    218 --   >>> alias "too short"
    219 --   Nothing
    220 alias :: BS.ByteString -> Maybe Alias
    221 alias bs
    222   | BS.length bs == 32 = Just (Alias bs)
    223   | otherwise          = Nothing
    224 {-# INLINE alias #-}
    225 
    226 -- | The bytes of an t'Alias'.
    227 un_alias :: Alias -> BS.ByteString
    228 un_alias (Alias bs) = bs
    229 {-# INLINE un_alias #-}
    230 
    231 -- | A 4-byte IPv4 address.
    232 newtype IPv4Addr = IPv4Addr BS.ByteString
    233   deriving (Eq, Show, Generic)
    234 
    235 instance NFData IPv4Addr
    236 
    237 -- | Construct an t'IPv4Addr' from exactly 4 bytes.
    238 --
    239 --   >>> ipv4_addr "\127\NUL\NUL\SOH"
    240 --   Just (IPv4Addr "\DEL\NUL\NUL\SOH")
    241 ipv4_addr :: BS.ByteString -> Maybe IPv4Addr
    242 ipv4_addr bs
    243   | BS.length bs == 4 = Just (IPv4Addr bs)
    244   | otherwise         = Nothing
    245 {-# INLINE ipv4_addr #-}
    246 
    247 -- | The bytes of an t'IPv4Addr'.
    248 un_ipv4_addr :: IPv4Addr -> BS.ByteString
    249 un_ipv4_addr (IPv4Addr bs) = bs
    250 {-# INLINE un_ipv4_addr #-}
    251 
    252 -- | A 16-byte IPv6 address.
    253 newtype IPv6Addr = IPv6Addr BS.ByteString
    254   deriving (Eq, Show, Generic)
    255 
    256 instance NFData IPv6Addr
    257 
    258 -- | Construct an t'IPv6Addr' from exactly 16 bytes.
    259 ipv6_addr :: BS.ByteString -> Maybe IPv6Addr
    260 ipv6_addr bs
    261   | BS.length bs == 16 = Just (IPv6Addr bs)
    262   | otherwise          = Nothing
    263 {-# INLINE ipv6_addr #-}
    264 
    265 -- | The bytes of an t'IPv6Addr'.
    266 un_ipv6_addr :: IPv6Addr -> BS.ByteString
    267 un_ipv6_addr (IPv6Addr bs) = bs
    268 {-# INLINE un_ipv6_addr #-}
    269 
    270 -- | The 12 data bytes of a deprecated Tor v2 address descriptor (type
    271 --   3). They are kept, uninterpreted, so that re-encoding reproduces
    272 --   the signed bytes.
    273 newtype TorV2Addr = TorV2Addr BS.ByteString
    274   deriving (Eq, Show, Generic)
    275 
    276 instance NFData TorV2Addr
    277 
    278 -- | Construct a t'TorV2Addr' from exactly 12 bytes.
    279 tor_v2_addr :: BS.ByteString -> Maybe TorV2Addr
    280 tor_v2_addr bs
    281   | BS.length bs == 12 = Just (TorV2Addr bs)
    282   | otherwise          = Nothing
    283 {-# INLINE tor_v2_addr #-}
    284 
    285 -- | The bytes of a t'TorV2Addr'.
    286 un_tor_v2_addr :: TorV2Addr -> BS.ByteString
    287 un_tor_v2_addr (TorV2Addr bs) = bs
    288 {-# INLINE un_tor_v2_addr #-}
    289 
    290 -- | A 35-byte Tor v3 onion service address: a 32-byte ed25519 public
    291 --   key, a 2-byte checksum and a 1-byte version.
    292 newtype TorV3Addr = TorV3Addr BS.ByteString
    293   deriving (Eq, Show, Generic)
    294 
    295 instance NFData TorV3Addr
    296 
    297 -- | Construct a t'TorV3Addr' from exactly 35 bytes.
    298 tor_v3_addr :: BS.ByteString -> Maybe TorV3Addr
    299 tor_v3_addr bs
    300   | BS.length bs == 35 = Just (TorV3Addr bs)
    301   | otherwise          = Nothing
    302 {-# INLINE tor_v3_addr #-}
    303 
    304 -- | The bytes of a t'TorV3Addr'.
    305 un_tor_v3_addr :: TorV3Addr -> BS.ByteString
    306 un_tor_v3_addr (TorV3Addr bs) = bs
    307 {-# INLINE un_tor_v3_addr #-}
    308 
    309 -- | A DNS hostname of at most 255 bytes (the limit of its length byte).
    310 --
    311 --   BOLT #7 requires hostnames to be ASCII, with other characters
    312 --   Punycode-encoded, but receivers keep whatever bytes they are given;
    313 --   see 'is_ascii_hostname'.
    314 newtype Hostname = Hostname BS.ByteString
    315   deriving (Eq, Show, Generic)
    316 
    317 instance NFData Hostname
    318 
    319 -- | Construct a t'Hostname' from at most 255 bytes.
    320 --
    321 --   >>> hostname "ln.example.com"
    322 --   Just (Hostname "ln.example.com")
    323 --   >>> hostname (BS.replicate 256 0x61)
    324 --   Nothing
    325 hostname :: BS.ByteString -> Maybe Hostname
    326 hostname bs
    327   | BS.length bs <= 255 = Just (Hostname bs)
    328   | otherwise           = Nothing
    329 {-# INLINE hostname #-}
    330 
    331 -- | The bytes of a t'Hostname'.
    332 un_hostname :: Hostname -> BS.ByteString
    333 un_hostname (Hostname bs) = bs
    334 {-# INLINE un_hostname #-}
    335 
    336 -- | Whether a t'Hostname' is non-empty and ASCII, as BOLT #7 requires.
    337 --
    338 --   >>> fmap is_ascii_hostname (hostname "caf\233")
    339 --   Just False
    340 is_ascii_hostname :: Hostname -> Bool
    341 is_ascii_hostname (Hostname bs) = not (BS.null bs) && BS.all (< 0x80) bs
    342 {-# INLINE is_ascii_hostname #-}
    343 
    344 -- | An address descriptor of a known type, with its port where it has
    345 --   one.
    346 data Address
    347   = AddrIPv4  !IPv4Addr  {-# UNPACK #-} !Word16  -- ^ type 1
    348   | AddrIPv6  !IPv6Addr  {-# UNPACK #-} !Word16  -- ^ type 2
    349   | AddrTorV2 !TorV2Addr                         -- ^ type 3 (deprecated)
    350   | AddrTorV3 !TorV3Addr {-# UNPACK #-} !Word16  -- ^ type 4
    351   | AddrDNS   !Hostname  {-# UNPACK #-} !Word16  -- ^ type 5
    352   deriving (Eq, Show, Generic)
    353 
    354 instance NFData Address
    355 
    356 -- | The addresses a receiver should connect to, per the BOLT #7 receiver
    357 --   rules: drops Tor v2 descriptors, descriptors with port 0, DNS
    358 --   hostnames that are empty or not ASCII, and every DNS descriptor
    359 --   after the first.
    360 usable_addresses :: [Address] -> [Address]
    361 usable_addresses = go False
    362   where
    363     go _ [] = []
    364     go !dns (a : as) = case a of
    365       AddrIPv4 _ p  -> port p
    366       AddrIPv6 _ p  -> port p
    367       AddrTorV2 _   -> go dns as
    368       AddrTorV3 _ p -> port p
    369       AddrDNS h p
    370         | dns                            -> go dns as
    371         | p /= 0 && is_ascii_hostname h  -> a : go True as
    372         | otherwise                      -> go True as
    373       where
    374         port p
    375           | p == 0    = go dns as
    376           | otherwise = a : go dns as
    377 
    378 -- query fields ---------------------------------------------------------------
    379 
    380 -- | A @query_flags@ bitfield, sent for each short channel id in a
    381 --   @query_short_channel_ids@. Every value is valid; unassigned bits are
    382 --   kept.
    383 newtype QueryFlags = QueryFlags Word64
    384   deriving (Eq, Show, Generic)
    385 
    386 instance NFData QueryFlags
    387 
    388 -- | The assigned bits of t'QueryFlags'.
    389 data QueryFlag
    390   = WantChannelAnnouncement  -- ^ bit 0
    391   | WantChannelUpdate1       -- ^ bit 1: the @channel_update@ of node 1
    392   | WantChannelUpdate2       -- ^ bit 2: the @channel_update@ of node 2
    393   | WantNodeAnnouncement1    -- ^ bit 3: the @node_announcement@ of node 1
    394   | WantNodeAnnouncement2    -- ^ bit 4: the @node_announcement@ of node 2
    395   deriving (Eq, Ord, Show, Enum, Bounded, Generic)
    396 
    397 instance NFData QueryFlag
    398 
    399 -- | The t'QueryFlags' with exactly the given bits set.
    400 --
    401 --   >>> query_flags [WantChannelAnnouncement, WantChannelUpdate2]
    402 --   QueryFlags 5
    403 query_flags :: [QueryFlag] -> QueryFlags
    404 query_flags = QueryFlags . foldr (\f acc -> acc .|. bit (fromEnum f)) 0
    405 
    406 -- | Whether the given bit is set.
    407 --
    408 --   >>> has_query_flag WantChannelUpdate1 (QueryFlags 2)
    409 --   True
    410 has_query_flag :: QueryFlag -> QueryFlags -> Bool
    411 has_query_flag f (QueryFlags w) = testBit w (fromEnum f)
    412 {-# INLINE has_query_flag #-}
    413 
    414 -- | The @query_option_flags@ bitfield of a @query_channel_range@. Every
    415 --   value is valid; unassigned bits are kept.
    416 newtype QueryOption = QueryOption Word64
    417   deriving (Eq, Show, Generic)
    418 
    419 instance NFData QueryOption
    420 
    421 -- | The assigned bits of t'QueryOption'.
    422 data QueryOptionFlag
    423   = WantTimestamps  -- ^ bit 0
    424   | WantChecksums   -- ^ bit 1
    425   deriving (Eq, Ord, Show, Enum, Bounded, Generic)
    426 
    427 instance NFData QueryOptionFlag
    428 
    429 -- | The t'QueryOption' with exactly the given bits set.
    430 --
    431 --   >>> query_option [WantTimestamps, WantChecksums]
    432 --   QueryOption 3
    433 query_option :: [QueryOptionFlag] -> QueryOption
    434 query_option = QueryOption . foldr (\f acc -> acc .|. bit (fromEnum f)) 0
    435 
    436 -- | Whether the given bit is set.
    437 --
    438 --   >>> has_query_option WantChecksums (QueryOption 1)
    439 --   False
    440 has_query_option :: QueryOptionFlag -> QueryOption -> Bool
    441 has_query_option f (QueryOption w) = testBit w (fromEnum f)
    442 {-# INLINE has_query_option #-}
    443 
    444 -- | The timestamps of a channel's latest @channel_update@s, from
    445 --   @node_id_1@ and @node_id_2@ (0 where there is none).
    446 data ChannelUpdateTimestamps = ChannelUpdateTimestamps
    447   {-# UNPACK #-} !Word32
    448   {-# UNPACK #-} !Word32
    449   deriving (Eq, Show, Generic)
    450 
    451 instance NFData ChannelUpdateTimestamps
    452 
    453 -- | The checksums of a channel's latest @channel_update@s, from
    454 --   @node_id_1@ and @node_id_2@ (0 where there is none).
    455 data ChannelUpdateChecksums = ChannelUpdateChecksums
    456   {-# UNPACK #-} !Word32
    457   {-# UNPACK #-} !Word32
    458   deriving (Eq, Show, Generic)
    459 
    460 instance NFData ChannelUpdateChecksums