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