bolt7

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

IMPL2.md (9247B)


      1 # IMPL2: Refactoring and Type Safety Improvements
      2 
      3 ## Overview
      4 
      5 Internal refactoring to reduce code duplication and improve type safety.
      6 These changes are purely internal - no API changes to exported functions.
      7 
      8 ## Phase 1: Decoder Combinator (Codec.hs)
      9 
     10 **Goal:** Extract common decoder pattern into reusable combinator.
     11 
     12 **Files:** `lib/Lightning/Protocol/BOLT7/Codec.hs`
     13 
     14 **Current state:** Lines 140-211 contain 8 nearly-identical decoders:
     15 
     16 ```haskell
     17 decodeSignature bs = do
     18   (bytes, rest) <- decodeBytes signatureLen bs
     19   case signature bytes of
     20     Nothing -> Left DecodeInvalidSignature
     21     Just s  -> Right (s, rest)
     22 ```
     23 
     24 **Tasks:**
     25 1. Add `decodeFixed` combinator in "Primitive helpers" section (~line 90):
     26    ```haskell
     27    decodeFixed :: Int -> DecodeError -> (ByteString -> Maybe a)
     28                -> ByteString -> Either DecodeError (a, ByteString)
     29    decodeFixed len err mkVal bs = do
     30      (bytes, rest) <- decodeBytes len bs
     31      case mkVal bytes of
     32        Nothing -> Left err
     33        Just v  -> Right (v, rest)
     34    {-# INLINE decodeFixed #-}
     35    ```
     36 
     37 2. Rewrite type-specific decoders (lines 140-211) using `decodeFixed`:
     38    ```haskell
     39    decodeSignature :: ByteString -> Either DecodeError (Signature, ByteString)
     40    decodeSignature = decodeFixed signatureLen DecodeInvalidSignature signature
     41    {-# INLINE decodeSignature #-}
     42    ```
     43 
     44 3. Apply to all 8 decoders:
     45    - decodeSignature
     46    - decodeChainHash
     47    - decodeShortChannelId
     48    - decodeChannelId
     49    - decodeNodeId
     50    - decodePoint
     51    - decodeRgbColor
     52    - decodeAlias
     53 
     54 **Verification:**
     55 - `cabal build` succeeds
     56 - `cabal test` passes (roundtrip tests verify correctness)
     57 
     58 **Dependencies:** None (can start immediately)
     59 
     60 ---
     61 
     62 ## Phase 2: Double-SHA256 Helper (Hash.hs)
     63 
     64 **Goal:** Extract repeated double-hash pattern.
     65 
     66 **Files:** `lib/Lightning/Protocol/BOLT7/Hash.hs`
     67 
     68 **Current state:** Lines 45, 56, 67 all contain:
     69 ```haskell
     70 SHA256.hash (SHA256.hash payload)
     71 ```
     72 
     73 **Tasks:**
     74 1. Add internal helper after imports (~line 32):
     75    ```haskell
     76    -- | Double SHA-256 hash (used for Lightning message signing).
     77    doubleSha256 :: ByteString -> ByteString
     78    doubleSha256 = SHA256.hash . SHA256.hash
     79    {-# INLINE doubleSha256 #-}
     80    ```
     81 
     82 2. Update `channelAnnouncementHash` (line 45):
     83    ```haskell
     84    in  doubleSha256 payload
     85    ```
     86 
     87 3. Update `nodeAnnouncementHash` (line 56):
     88    ```haskell
     89    in  doubleSha256 payload
     90    ```
     91 
     92 4. Update `channelUpdateHash` (line 67):
     93    ```haskell
     94    in  doubleSha256 payload
     95    ```
     96 
     97 **Verification:**
     98 - `cabal build` succeeds
     99 - `cabal test` passes
    100 
    101 **Dependencies:** None (can start immediately)
    102 
    103 ---
    104 
    105 ## Phase 3: Length-Prefixed Encoding Helper (Codec.hs)
    106 
    107 **Goal:** Extract repeated length-prefix encoding pattern.
    108 
    109 **Files:** `lib/Lightning/Protocol/BOLT7/Codec.hs`
    110 
    111 **Current state:** Pattern appears in 5 encoders:
    112 ```haskell
    113 Prim.encodeU16 (fromIntegral $ BS.length features) <> features
    114 ```
    115 
    116 Locations:
    117 - Line 273-274 (encodeChannelAnnouncement)
    118 - Line 326-327 (encodeNodeAnnouncement, features)
    119 - Line 332-333 (encodeNodeAnnouncement, addresses)
    120 - Line 479 (encodeQueryShortChannelIds)
    121 - Line 559 (encodeReplyChannelRange)
    122 
    123 **Tasks:**
    124 1. Add `encodeLenPrefixed` helper in "Primitive helpers" section (~line 90):
    125    ```haskell
    126    -- | Encode with u16 length prefix.
    127    encodeLenPrefixed :: ByteString -> ByteString
    128    encodeLenPrefixed bs =
    129      Prim.encodeU16 (fromIntegral $ BS.length bs) <> bs
    130    {-# INLINE encodeLenPrefixed #-}
    131    ```
    132 
    133 2. Update all 5 locations to use the helper.
    134 
    135 **Verification:**
    136 - `cabal build` succeeds
    137 - `cabal test` passes
    138 
    139 **Dependencies:** None (can start immediately)
    140 
    141 ---
    142 
    143 ## Phase 4: Routing Parameter Newtypes (Types.hs)
    144 
    145 **Goal:** Replace type aliases with newtypes to prevent accidental mixing.
    146 
    147 **Files:**
    148 - `lib/Lightning/Protocol/BOLT7/Types.hs`
    149 - `lib/Lightning/Protocol/BOLT7/Messages.hs`
    150 - `lib/Lightning/Protocol/BOLT7/Codec.hs`
    151 
    152 **Current state (Types.hs:407-419):**
    153 ```haskell
    154 type CltvExpiryDelta = Word16
    155 type FeeBaseMsat = Word32
    156 type FeeProportionalMillionths = Word32
    157 type HtlcMinimumMsat = Word64
    158 type HtlcMaximumMsat = Word64
    159 ```
    160 
    161 **Tasks:**
    162 1. Convert type aliases to newtypes in Types.hs:
    163    ```haskell
    164    newtype CltvExpiryDelta = CltvExpiryDelta
    165      { getCltvExpiryDelta :: Word16 }
    166      deriving (Eq, Ord, Show, Generic)
    167 
    168    instance NFData CltvExpiryDelta
    169 
    170    newtype FeeBaseMsat = FeeBaseMsat
    171      { getFeeBaseMsat :: Word32 }
    172      deriving (Eq, Ord, Show, Generic)
    173 
    174    instance NFData FeeBaseMsat
    175 
    176    -- etc. for all 5 types
    177    ```
    178 
    179 2. Update exports in Types.hs to include constructors and accessors.
    180 
    181 3. Update ChannelUpdate record in Messages.hs - no changes needed,
    182    field types remain the same names.
    183 
    184 4. Update Codec.hs encode/decode:
    185    - `encodeChannelUpdate`: wrap primitives in constructors
    186    - `decodeChannelUpdate`: use constructors when building record
    187 
    188 5. Update Validate.hs if needed (comparison of HtlcMinimumMsat vs
    189    HtlcMaximumMsat may need unwrapping).
    190 
    191 **Verification:**
    192 - `cabal build` succeeds
    193 - `cabal test` passes
    194 
    195 **Dependencies:** None (can start immediately)
    196 
    197 **Note:** This is the most invasive change. Consider whether the type
    198 safety benefit outweighs the additional unwrapping boilerplate.
    199 
    200 ---
    201 
    202 ## Phase 5: Channel Flags ADT (Optional)
    203 
    204 **Goal:** Replace raw Word8 flags with structured ADT.
    205 
    206 **Files:**
    207 - `lib/Lightning/Protocol/BOLT7/Types.hs`
    208 - `lib/Lightning/Protocol/BOLT7/Messages.hs`
    209 - `lib/Lightning/Protocol/BOLT7/Codec.hs`
    210 
    211 **Current state (Messages.hs:129-130):**
    212 ```haskell
    213 chanUpdateMsgFlags  :: !Word8
    214 chanUpdateChanFlags :: !Word8
    215 ```
    216 
    217 **Tasks:**
    218 1. Add flag types to Types.hs:
    219    ```haskell
    220    -- | Message flags for channel_update.
    221    data MessageFlags = MessageFlags
    222      { mfHtlcMaximumPresent :: !Bool
    223      }
    224      deriving (Eq, Show, Generic)
    225 
    226    instance NFData MessageFlags
    227 
    228    -- | Channel flags for channel_update.
    229    data ChannelFlags = ChannelFlags
    230      { cfDirection :: !Bool  -- ^ True = node_id_2 is origin
    231      , cfDisabled  :: !Bool  -- ^ True = channel disabled
    232      }
    233      deriving (Eq, Show, Generic)
    234 
    235    instance NFData ChannelFlags
    236    ```
    237 
    238 2. Add encode/decode helpers:
    239    ```haskell
    240    encodeMessageFlags :: MessageFlags -> Word8
    241    decodeMessageFlags :: Word8 -> MessageFlags
    242 
    243    encodeChannelFlags :: ChannelFlags -> Word8
    244    decodeChannelFlags :: Word8 -> ChannelFlags
    245    ```
    246 
    247 3. Update ChannelUpdate in Messages.hs to use new types.
    248 
    249 4. Update Codec.hs to use encode/decode helpers.
    250 
    251 **Verification:**
    252 - `cabal build` succeeds
    253 - `cabal test` passes
    254 
    255 **Dependencies:** None (can start immediately)
    256 
    257 **Note:** This is optional. The benefit is making flag semantics explicit,
    258 but it adds complexity. Defer if unclear.
    259 
    260 ---
    261 
    262 ## Phase 6: Complete validateReplyChannelRange (Validate.hs)
    263 
    264 **Goal:** Implement the stubbed validation function.
    265 
    266 **Files:** `lib/Lightning/Protocol/BOLT7/Validate.hs`
    267 
    268 **Current state (lines 118-124):**
    269 ```haskell
    270 validateReplyChannelRange :: ReplyChannelRange -> Either ValidationError ()
    271 validateReplyChannelRange _msg = do
    272   Right ()  -- stubbed
    273 ```
    274 
    275 **Tasks:**
    276 1. Import `decodeShortChannelIdList` from Codec module.
    277 
    278 2. Implement ascending order check:
    279    ```haskell
    280    validateReplyChannelRange msg = do
    281      case decodeShortChannelIdList (replyRangeData msg) of
    282        Left _ -> Right ()  -- Can't decode, skip validation
    283        Right scids -> checkAscending scids
    284      where
    285        checkAscending [] = Right ()
    286        checkAscending [_] = Right ()
    287        checkAscending (a:b:rest)
    288          | getShortChannelId a < getShortChannelId b = checkAscending (b:rest)
    289          | otherwise = Left ValidateScidNotAscending
    290    ```
    291 
    292 **Verification:**
    293 - `cabal build` succeeds
    294 - `cabal test` passes
    295 - Add test case for non-ascending SCIDs
    296 
    297 **Dependencies:** None (can start immediately)
    298 
    299 ---
    300 
    301 ## Dependency Graph
    302 
    303 ```
    304 Phase 1 (decoder combinator)  \
    305                                \
    306 Phase 2 (double-sha256)  -------+---> All can proceed in parallel
    307                                /
    308 Phase 3 (len-prefixed)  ------/
    309                              /
    310 Phase 4 (routing newtypes) -/
    311 
    312 Phase 5 (channel flags) -----> Optional, defer if needed
    313 
    314 Phase 6 (validation) --------> Independent
    315 ```
    316 
    317 All phases are independent and can be executed in parallel.
    318 
    319 ---
    320 
    321 ## Complexity and Risk
    322 
    323 | Phase | Complexity | Risk   | Lines Changed | Notes                    |
    324 |-------|------------|--------|---------------|--------------------------|
    325 | 1     | Low        | Low    | ~80           | Pure extraction          |
    326 | 2     | Low        | Low    | ~10           | Trivial                  |
    327 | 3     | Low        | Low    | ~15           | Pure extraction          |
    328 | 4     | Medium     | Medium | ~50           | Touches multiple modules |
    329 | 5     | Medium     | Medium | ~40           | Optional, defer if unsure|
    330 | 6     | Low        | Low    | ~15           | Completes stub           |
    331 
    332 ---
    333 
    334 ## Execution Order Recommendation
    335 
    336 1. **First batch (parallel):** Phases 1, 2, 3, 6
    337    - Low risk, immediate benefit
    338    - Can be done by separate agents concurrently
    339 
    340 2. **Second batch:** Phase 4
    341    - Review after first batch merges
    342    - Evaluate if type safety benefit justifies churn
    343 
    344 3. **Defer:** Phase 5
    345    - Nice-to-have but adds complexity
    346    - Revisit if flag handling becomes error-prone