bolt9

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

commit e64ee423c47e5cf259b4f80d8e9124e39f16f190
parent cbdf43150a9671a42cb301fd9741b76f93f27762
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 25 Jan 2026 15:55:31 +0400

Reconcile Context type between Types and Features modules

Features.hs now imports Context from Types.hs instead of defining its
own version. This ensures a single source of truth for context handling.

Also add .gitignore for build artifacts and worktrees.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Diffstat:
A.gitignore | 6++++++
Mlib/Lightning/Protocol/BOLT9.hs | 7+++++++
Mlib/Lightning/Protocol/BOLT9/Features.hs | 51+++++++++++++++++----------------------------------
Aplans/ARCH1.md | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aplans/IMPL1.md | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 198 insertions(+), 34 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,6 @@ +dist-newstyle/ +impl-*/ +*.hi +*.o +*.dyn_hi +*.dyn_o diff --git a/lib/Lightning/Protocol/BOLT9.hs b/lib/Lightning/Protocol/BOLT9.hs @@ -41,6 +41,13 @@ module Lightning.Protocol.BOLT9 ( , set , clear , member + + -- * Known features + , Feature(..) + , featureByBit + , featureByName + , knownFeatures ) where +import Lightning.Protocol.BOLT9.Features import Lightning.Protocol.BOLT9.Types as FV diff --git a/lib/Lightning/Protocol/BOLT9/Features.hs b/lib/Lightning/Protocol/BOLT9/Features.hs @@ -12,11 +12,8 @@ -- [BOLT #9](https://github.com/lightning/bolts/blob/master/09-features.md). module Lightning.Protocol.BOLT9.Features ( - -- * Context - Context(..) - -- * Feature - , Feature(..) + Feature(..) -- * Lookup , featureByBit @@ -30,21 +27,7 @@ import Control.DeepSeq (NFData) import Data.List (find) import Data.Word (Word16) import GHC.Generics (Generic) - --- | Contexts in which a feature may be presented. -data Context - = Init -- ^ 'I': presented in the @init@ message. - | NodeAnnouncement -- ^ 'N': presented in @node_announcement@ messages. - | ChannelAnnouncement - -- ^ 'C': presented in @channel_announcement@ messages. - | Invoice -- ^ '9': presented in BOLT 11 invoices. - | BlindedPath -- ^ 'B': presented in @allowed_features@ of a - -- blinded path. - | ChannelType -- ^ 'T': used in @channel_type@ field when opening - -- channels. - deriving (Eq, Ord, Show, Generic) - -instance NFData Context +import Lightning.Protocol.BOLT9.Types (Context(..)) -- | A known feature from the BOLT #9 specification. data Feature = Feature { @@ -68,30 +51,30 @@ instance NFData Feature knownFeatures :: [Feature] knownFeatures = [ Feature "option_data_loss_protect" 0 [] [] True - , Feature "option_upfront_shutdown_script" 4 [Init, NodeAnnouncement] [] False + , Feature "option_upfront_shutdown_script" 4 [Init, NodeAnn] [] False , Feature "gossip_queries" 6 [] [] False , Feature "var_onion_optin" 8 [] [] True - , Feature "gossip_queries_ex" 10 [Init, NodeAnnouncement] [] False + , Feature "gossip_queries_ex" 10 [Init, NodeAnn] [] False , Feature "option_static_remotekey" 12 [] [] True , Feature "payment_secret" 14 [] [] True - , Feature "basic_mpp" 16 [Init, NodeAnnouncement, Invoice] + , Feature "basic_mpp" 16 [Init, NodeAnn, Invoice] ["payment_secret"] False - , Feature "option_support_large_channel" 18 [Init, NodeAnnouncement] [] False - , Feature "option_anchors" 22 [Init, NodeAnnouncement, ChannelType] [] False - , Feature "option_route_blinding" 24 [Init, NodeAnnouncement, Invoice] [] False - , Feature "option_shutdown_anysegwit" 26 [Init, NodeAnnouncement] [] False - , Feature "option_dual_fund" 28 [Init, NodeAnnouncement] [] False - , Feature "option_quiesce" 34 [Init, NodeAnnouncement] [] False - , Feature "option_attribution_data" 36 [Init, NodeAnnouncement, Invoice] + , Feature "option_support_large_channel" 18 [Init, NodeAnn] [] False + , Feature "option_anchors" 22 [Init, NodeAnn, ChanType] [] False + , Feature "option_route_blinding" 24 [Init, NodeAnn, Invoice] [] False + , Feature "option_shutdown_anysegwit" 26 [Init, NodeAnn] [] False + , Feature "option_dual_fund" 28 [Init, NodeAnn] [] False + , Feature "option_quiesce" 34 [Init, NodeAnn] [] False + , Feature "option_attribution_data" 36 [Init, NodeAnn, Invoice] [] False - , Feature "option_onion_messages" 38 [Init, NodeAnnouncement] [] False - , Feature "option_provide_storage" 42 [Init, NodeAnnouncement] [] False + , Feature "option_onion_messages" 38 [Init, NodeAnn] [] False + , Feature "option_provide_storage" 42 [Init, NodeAnn] [] False , Feature "option_channel_type" 44 [] [] True - , Feature "option_scid_alias" 46 [Init, NodeAnnouncement, ChannelType] [] False + , Feature "option_scid_alias" 46 [Init, NodeAnn, ChanType] [] False , Feature "option_payment_metadata" 48 [Invoice] [] False - , Feature "option_zeroconf" 50 [Init, NodeAnnouncement, ChannelType] + , Feature "option_zeroconf" 50 [Init, NodeAnn, ChanType] ["option_scid_alias"] False - , Feature "option_simple_close" 60 [Init, NodeAnnouncement] + , Feature "option_simple_close" 60 [Init, NodeAnn] ["option_shutdown_anysegwit"] False ] diff --git a/plans/ARCH1.md b/plans/ARCH1.md @@ -0,0 +1,95 @@ +# ARCH1: High-level architecture for ppad-bolt9 + +## Goals + +- Represent BOLT 9 feature bits with strong types and safe invariants. +- Provide total parsing, validation, and construction of feature vectors. +- Enforce dependencies, context restrictions, and optional/required pairing. +- Keep the core API small and stable; push policy to explicit validators. + +## Core concepts + +- Feature bit: numbered bit (0-based) with optional/required pairing + (odd/optional, even/required). +- Feature pair: optional bit N and required bit N-1 (for odd N). +- Context: where a feature vector appears (init, node_announcement, + channel_announcement, invoice, blinded path, channel_type). +- Feature vector: packed bitset with context-specific restrictions. + +## Module layout (proposed) + +- `Lightning.Protocol.BOLT9` + - Public API: feature types, parsing, rendering, validation helpers. + - Re-export safe constructors and lookup functions. + +- `Lightning.Protocol.BOLT9.Feature` + - Enumerates known features from 09-features.md. + - Tables: bit number, name, contexts, dependencies, assumed. + - Total lookup: bit -> known feature info (Maybe). + +- `Lightning.Protocol.BOLT9.Context` + - ADT for contexts: Init, NodeAnn, ChanAnn, Invoice, Blinded, ChanType. + - Helpers for context-specific rules (C-, C+ handling). + +- `Lightning.Protocol.BOLT9.Vector` + - Packed representation of feature bits (ByteString). + - Functions: empty, set, clear, member, union, normalize. + - Safe constructors for optional vs required bits. + +- `Lightning.Protocol.BOLT9.Validate` + - Validation of a vector against: + - context restrictions + - optional/required conflict + - dependency closure + - must-not-set unknown bits (if required by caller) + - Validation of remote vectors for unknown bits handling. + +## Data model notes + +- Use newtypes for bit indices and for optional vs required bits. +- Encode odd/even constraints in constructors (smart constructors). +- Store dependencies as feature pairs (logical dependencies) rather than + raw bit numbers, then expand to actual bits for validation. + +## Invariants to enforce + +- No vector may contain both optional and required bits for a feature. +- Context restrictions are respected: + - Some bits only in Init/Node/Chan/Invoice/Blinded/ChanType. + - C-: only optional (odd bit) allowed in channel_announcement. + - C+: only required (even bit) allowed in channel_announcement. +- Known dependencies are transitively satisfied. +- Unknown bits may be allowed but must be treated as mandatory if + both optional and required are set (receiver rule). + +## API shape + +- Parsing and rendering: + - parseFeatureVector :: ByteString -> Either Error FeatureVector + - renderFeatureVector :: FeatureVector -> ByteString + +- Construction: + - emptyVector + - setOptional :: Feature -> FeatureVector -> FeatureVector + - setRequired :: Feature -> FeatureVector -> FeatureVector + +- Validation: + - validateLocal :: Context -> FeatureVector -> Either Error Validated + - validateRemote :: Context -> FeatureVector -> Either Error Validated + +## Validation policy + +- Local validation is strict: unknown bits are rejected by default. +- Remote validation is configurable: + - accept unknown optional bits + - reject unknown required bits + - configurable fallback for unknown pairs + +## Testing strategy + +- Unit tests for all known features (bit numbers, contexts, deps). +- Property tests: + - dependencies are closed under set operations + - no constructor can produce invalid parity + - render/parse roundtrip + diff --git a/plans/IMPL1.md b/plans/IMPL1.md @@ -0,0 +1,73 @@ +# IMPL1: Implementation plan for ppad-bolt9 + +## Scope + +Implement BOLT 9 feature flags with typed representation, parsing, +validation, and tests driven by etc/09-features.md. + +## Step plan + +1) Baseline types and context model + - Add `Context` ADT and helpers for context rules (C-/C+). + - Add newtypes for `BitIndex`, `OptionalBit`, `RequiredBit` with + smart constructors enforcing odd/even parity. + - Define `FeatureVector` wrapper over strict ByteString with basic + operations (empty, set, clear, member). + - Independent: yes, can proceed in parallel with step 2. + +2) Known feature table + - Encode the known features from 09-features.md in a table with: + name, base bit, contexts, dependencies, assumed. + - Provide total lookup functions: + `featureByBit`, `featureByName`. + - Provide accessors for contexts and dependencies. + - Independent: yes, can proceed in parallel with step 1. + +3) Parsing and rendering + - Implement parse from ByteString into FeatureVector (bit order per + BOLT 1, least-significant bit is bit 0). + - Implement rendering back to ByteString preserving compact length + rules (trim leading zero bytes). + - Add total helpers to map between bits and features. + - Depends on steps 1-2. + +4) Validation engine + - Implement local validation: + - no optional+required pair set + - context restrictions (C-/C+ and allowed contexts) + - dependency closure (transitive) + - must set required bits for required features + - Implement remote validation: + - treat unknown optional bits as ignorable + - reject unknown required bits + - if both bits set, treat as required + - Provide error ADT with structured reasons. + - Depends on steps 1-3. + +5) Public API surface in Lightning.Protocol.BOLT9 + - Expose safe constructors and validation helpers. + - Add Haddock examples for exported functions. + - Ensure module header and OPTIONS_HADDOCK prune. + - Depends on steps 1-4. + +6) Tests + - Unit tests for known features table and bit parity. + - Unit tests for context restrictions and dependency checks. + - Property tests for render/parse roundtrip and dependency closure. + - Depends on steps 1-4. + +7) Benchmarks + - Criterion benchmarks for parse/validate/render. + - Weigh benchmark for allocation profile. + - Define NFData instances as needed. + - Depends on steps 1-4. + +## Notes for subagents + +- Use strictness annotations and INLINE for small helpers. +- Avoid partial functions; make pattern matches exhaustive. +- Keep lines under 80 chars. +- Do not add new external deps without approval. +- If you introduce validation policy knobs, keep defaults conservative + (reject unknown required bits). +