Skip to content

Commit 6d1b389

Browse files
jkczyznaumenkogs
authored andcommitted
Encapsulate feature flag checking and manipulation
Each feature is represented by two bits within Features' flags field. Working with these flags requires bitwise operations, which can be error prone. Rather than directly checking and manipulating bits, encapsulate the bits within each feature trait and provide mechanisms for doing so. This removes the need to comment on which features correspond to bitwise expressions since the expressions use feature trait identifiers instead. With this approach, byte literals and expressions can be evaluated at compile time still. However, for these cases, knowing which byte within the flags that a feature corresponds to still must be determined by the implementor. Remove the special case where initial_routing_sync has no even bit. Now, it (bit 2) is considered known by the implementation.
1 parent 181e158 commit 6d1b389

File tree

1 file changed

+175
-56
lines changed

1 file changed

+175
-56
lines changed

lightning/src/ln/features.rs

Lines changed: 175 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::marker::PhantomData;
88
use ln::msgs::DecodeError;
99
use util::ser::{Readable, Writeable, Writer};
1010

11+
#[macro_use]
1112
mod sealed { // You should just use the type aliases instead.
1213
pub struct InitContext {}
1314
pub struct NodeContext {}
@@ -19,28 +20,112 @@ mod sealed { // You should just use the type aliases instead.
1920
impl Context for NodeContext {}
2021
impl Context for ChannelContext {}
2122

22-
pub trait DataLossProtect: Context {}
23-
impl DataLossProtect for InitContext {}
24-
impl DataLossProtect for NodeContext {}
25-
26-
pub trait InitialRoutingSync: Context {}
27-
impl InitialRoutingSync for InitContext {}
28-
29-
pub trait UpfrontShutdownScript: Context {}
30-
impl UpfrontShutdownScript for InitContext {}
31-
impl UpfrontShutdownScript for NodeContext {}
23+
/// Defines a feature with the given bits for the specified [`Context`]s. The generated trait is
24+
/// useful for manipulating feature flags.
25+
///
26+
/// [`Context`]: trait.Context.html
27+
macro_rules! define_feature {
28+
($odd_bit: expr, $feature: ident, [$($context: ty),+], $doc: expr) => {
29+
#[doc = $doc]
30+
///
31+
/// See [BOLT #9] for details.
32+
///
33+
/// [BOLT #9]: https://github.com/lightningnetwork/lightning-rfc/blob/master/09-features.md
34+
pub trait $feature: Context {
35+
/// The bit used to signify that the feature is required.
36+
const EVEN_BIT: usize = $odd_bit - 1;
37+
38+
/// The bit used to signify that the feature is optional.
39+
const ODD_BIT: usize = $odd_bit;
40+
41+
/// Assertion that [`EVEN_BIT`] is actually even.
42+
///
43+
/// [`EVEN_BIT`]: #associatedconstant.EVEN_BIT
44+
const ASSERT_EVEN_BIT_PARITY: usize;
45+
46+
/// Assertion that [`ODD_BIT`] is actually odd.
47+
///
48+
/// [`ODD_BIT`]: #associatedconstant.ODD_BIT
49+
const ASSERT_ODD_BIT_PARITY: usize;
50+
51+
/// The byte where the feature is set.
52+
const BYTE_OFFSET: usize = Self::EVEN_BIT / 8;
53+
54+
/// The bitmask for the feature's required flag relative to the [`BYTE_OFFSET`].
55+
///
56+
/// [`BYTE_OFFSET`]: #associatedconstant.BYTE_OFFSET
57+
const REQUIRED_MASK: u8 = 1 << (Self::EVEN_BIT - 8 * Self::BYTE_OFFSET);
58+
59+
/// The bitmask for the feature's optional flag relative to the [`BYTE_OFFSET`].
60+
///
61+
/// [`BYTE_OFFSET`]: #associatedconstant.BYTE_OFFSET
62+
const OPTIONAL_MASK: u8 = 1 << (Self::ODD_BIT - 8 * Self::BYTE_OFFSET);
63+
64+
/// Returns whether the feature is supported by the given flags.
65+
#[inline]
66+
fn supports_feature(flags: &Vec<u8>) -> bool {
67+
flags.len() > Self::BYTE_OFFSET &&
68+
(flags[Self::BYTE_OFFSET] & (Self::REQUIRED_MASK | Self::OPTIONAL_MASK)) != 0
69+
}
70+
71+
/// Sets the feature's optional (odd) bit in the given flags.
72+
#[inline]
73+
fn set_optional_bit(flags: &mut Vec<u8>) {
74+
if flags.len() <= Self::BYTE_OFFSET {
75+
flags.resize(Self::BYTE_OFFSET + 1, 0u8);
76+
}
77+
78+
flags[Self::BYTE_OFFSET] |= Self::OPTIONAL_MASK;
79+
}
80+
81+
/// Clears the feature's optional (odd) bit from the given flags.
82+
#[inline]
83+
fn clear_optional_bit(flags: &mut Vec<u8>) {
84+
if flags.len() > Self::BYTE_OFFSET {
85+
flags[Self::BYTE_OFFSET] &= !Self::OPTIONAL_MASK;
86+
}
87+
}
88+
}
3289

33-
pub trait VariableLengthOnion: Context {}
34-
impl VariableLengthOnion for InitContext {}
35-
impl VariableLengthOnion for NodeContext {}
90+
$(
91+
impl $feature for $context {
92+
// EVEN_BIT % 2 == 0
93+
const ASSERT_EVEN_BIT_PARITY: usize = 0 - (<Self as $feature>::EVEN_BIT % 2);
3694

37-
pub trait PaymentSecret: Context {}
38-
impl PaymentSecret for InitContext {}
39-
impl PaymentSecret for NodeContext {}
95+
// ODD_BIT % 2 == 1
96+
const ASSERT_ODD_BIT_PARITY: usize = (<Self as $feature>::ODD_BIT % 2) - 1;
97+
}
98+
)*
99+
}
100+
}
40101

41-
pub trait BasicMPP: Context {}
42-
impl BasicMPP for InitContext {}
43-
impl BasicMPP for NodeContext {}
102+
define_feature!(1, DataLossProtect, [InitContext, NodeContext],
103+
"Feature flags for `option_data_loss_protect`.");
104+
// NOTE: Per Bolt #9, initial_routing_sync has no even bit.
105+
define_feature!(3, InitialRoutingSync, [InitContext],
106+
"Feature flags for `initial_routing_sync`.");
107+
define_feature!(5, UpfrontShutdownScript, [InitContext, NodeContext],
108+
"Feature flags for `option_upfront_shutdown_script`.");
109+
define_feature!(9, VariableLengthOnion, [InitContext, NodeContext],
110+
"Feature flags for `var_onion_optin`.");
111+
define_feature!(15, PaymentSecret, [InitContext, NodeContext],
112+
"Feature flags for `payment_secret`.");
113+
define_feature!(17, BasicMPP, [InitContext, NodeContext],
114+
"Feature flags for `basic_mpp`.");
115+
116+
/// Generates a feature flag byte with the given features set as optional. Useful for initializing
117+
/// the flags within [`Features`].
118+
///
119+
/// [`Features`]: struct.Features.html
120+
macro_rules! feature_flags {
121+
($context: ty; $($feature: ident)|*) => {
122+
(0b00_00_00_00
123+
$(
124+
| <$context as sealed::$feature>::OPTIONAL_MASK
125+
)*
126+
)
127+
}
128+
}
44129
}
45130

46131
/// Tracks the set of features which a node implements, templated by the context in which it
@@ -81,7 +166,11 @@ impl InitFeatures {
81166
/// Create a Features with the features we support
82167
pub fn supported() -> InitFeatures {
83168
InitFeatures {
84-
flags: vec![2 | 1 << 3 | 1 << 5, 1 << (9-8) | 1 << (15 - 8), 1 << (17 - 8*2)],
169+
flags: vec![
170+
feature_flags![sealed::InitContext; DataLossProtect | InitialRoutingSync | UpfrontShutdownScript],
171+
feature_flags![sealed::InitContext; VariableLengthOnion | PaymentSecret],
172+
feature_flags![sealed::InitContext; BasicMPP],
173+
],
85174
mark: PhantomData,
86175
}
87176
}
@@ -144,14 +233,22 @@ impl NodeFeatures {
144233
#[cfg(not(feature = "fuzztarget"))]
145234
pub(crate) fn supported() -> NodeFeatures {
146235
NodeFeatures {
147-
flags: vec![2 | 1 << 5, 1 << (9 - 8) | 1 << (15 - 8), 1 << (17 - 8*2)],
236+
flags: vec![
237+
feature_flags![sealed::NodeContext; DataLossProtect | UpfrontShutdownScript],
238+
feature_flags![sealed::NodeContext; VariableLengthOnion | PaymentSecret],
239+
feature_flags![sealed::NodeContext; BasicMPP],
240+
],
148241
mark: PhantomData,
149242
}
150243
}
151244
#[cfg(feature = "fuzztarget")]
152245
pub fn supported() -> NodeFeatures {
153246
NodeFeatures {
154-
flags: vec![2 | 1 << 5, 1 << (9 - 8) | 1 << (15 - 8), 1 << (17 - 8*2)],
247+
flags: vec![
248+
feature_flags![sealed::NodeContext; DataLossProtect | UpfrontShutdownScript],
249+
feature_flags![sealed::NodeContext; VariableLengthOnion | PaymentSecret],
250+
feature_flags![sealed::NodeContext; BasicMPP],
251+
],
155252
mark: PhantomData,
156253
}
157254
}
@@ -160,15 +257,25 @@ impl NodeFeatures {
160257
/// relevant in a node-context features and creates a node-context features from them.
161258
/// Be sure to blank out features that are unknown to us.
162259
pub(crate) fn with_known_relevant_init_flags(init_ctx: &InitFeatures) -> Self {
260+
// Generates a bitmask with both even and odd bits set for the given features. Bitwise
261+
// AND-ing it with a byte will select only common features.
262+
macro_rules! features_including {
263+
($($feature: ident)|*) => {
264+
(0b00_00_00_00
265+
$(
266+
| <sealed::NodeContext as sealed::$feature>::REQUIRED_MASK
267+
| <sealed::NodeContext as sealed::$feature>::OPTIONAL_MASK
268+
)*
269+
)
270+
}
271+
}
272+
163273
let mut flags = Vec::new();
164274
for (i, feature_byte)in init_ctx.flags.iter().enumerate() {
165275
match i {
166-
// Blank out initial_routing_sync (feature bits 2/3), gossip_queries (6/7),
167-
// gossip_queries_ex (10/11), option_static_remotekey (12/13), and
168-
// option_support_large_channel (16/17)
169-
0 => flags.push(feature_byte & 0b00110011),
170-
1 => flags.push(feature_byte & 0b11000011),
171-
2 => flags.push(feature_byte & 0b00000011),
276+
0 => flags.push(feature_byte & features_including![DataLossProtect | UpfrontShutdownScript]),
277+
1 => flags.push(feature_byte & features_including![VariableLengthOnion | PaymentSecret]),
278+
2 => flags.push(feature_byte & features_including![BasicMPP]),
172279
_ => (),
173280
}
174281
}
@@ -201,33 +308,47 @@ impl<T: sealed::Context> Features<T> {
201308
}
202309

203310
pub(crate) fn requires_unknown_bits(&self) -> bool {
311+
// Generates a bitmask with all even bits set except for the given features. Bitwise
312+
// AND-ing it with a byte will select unknown required features.
313+
macro_rules! features_excluding {
314+
($($feature: ident)|*) => {
315+
(0b01_01_01_01
316+
$(
317+
& !(<sealed::InitContext as sealed::$feature>::REQUIRED_MASK)
318+
)*
319+
)
320+
}
321+
}
322+
204323
self.flags.iter().enumerate().any(|(idx, &byte)| {
205324
(match idx {
206-
// Unknown bits are even bits which we don't understand, we list ones which we do
207-
// here:
208-
// unknown, upfront_shutdown_script, unknown (actually initial_routing_sync, but it
209-
// is only valid as an optional feature), and data_loss_protect:
210-
0 => (byte & 0b01000100),
211-
// payment_secret, unknown, unknown, var_onion_optin:
212-
1 => (byte & 0b00010100),
213-
// unknown, unknown, unknown, basic_mpp:
214-
2 => (byte & 0b01010100),
215-
// fallback, all even bits set:
216-
_ => (byte & 0b01010101),
325+
0 => (byte & features_excluding![DataLossProtect | InitialRoutingSync | UpfrontShutdownScript]),
326+
1 => (byte & features_excluding![VariableLengthOnion | PaymentSecret]),
327+
2 => (byte & features_excluding![BasicMPP]),
328+
_ => (byte & features_excluding![]),
217329
}) != 0
218330
})
219331
}
220332

221333
pub(crate) fn supports_unknown_bits(&self) -> bool {
334+
// Generates a bitmask with all even and odd bits set except for the given features. Bitwise
335+
// AND-ing it with a byte will select unknown supported features.
336+
macro_rules! features_excluding {
337+
($($feature: ident)|*) => {
338+
(0b11_11_11_11
339+
$(
340+
& !(<sealed::InitContext as sealed::$feature>::REQUIRED_MASK)
341+
& !(<sealed::InitContext as sealed::$feature>::OPTIONAL_MASK)
342+
)*
343+
)
344+
}
345+
}
346+
222347
self.flags.iter().enumerate().any(|(idx, &byte)| {
223348
(match idx {
224-
// unknown, upfront_shutdown_script, initial_routing_sync (is only valid as an
225-
// optional feature), and data_loss_protect:
226-
0 => (byte & 0b11000100),
227-
// payment_secret, unknown, unknown, var_onion_optin:
228-
1 => (byte & 0b00111100),
229-
// unknown, unknown, unknown, basic_mpp:
230-
2 => (byte & 0b11111100),
349+
0 => (byte & features_excluding![DataLossProtect | InitialRoutingSync | UpfrontShutdownScript]),
350+
1 => (byte & features_excluding![VariableLengthOnion | PaymentSecret]),
351+
2 => (byte & features_excluding![BasicMPP]),
231352
_ => byte,
232353
}) != 0
233354
})
@@ -262,34 +383,32 @@ impl<T: sealed::Context> Features<T> {
262383

263384
impl<T: sealed::DataLossProtect> Features<T> {
264385
pub(crate) fn supports_data_loss_protect(&self) -> bool {
265-
self.flags.len() > 0 && (self.flags[0] & 3) != 0
386+
<T as sealed::DataLossProtect>::supports_feature(&self.flags)
266387
}
267388
}
268389

269390
impl<T: sealed::UpfrontShutdownScript> Features<T> {
270391
pub(crate) fn supports_upfront_shutdown_script(&self) -> bool {
271-
self.flags.len() > 0 && (self.flags[0] & (3 << 4)) != 0
392+
<T as sealed::UpfrontShutdownScript>::supports_feature(&self.flags)
272393
}
273394
#[cfg(test)]
274395
pub(crate) fn unset_upfront_shutdown_script(&mut self) {
275-
self.flags[0] &= !(1 << 5);
396+
<T as sealed::UpfrontShutdownScript>::clear_optional_bit(&mut self.flags)
276397
}
277398
}
278399

279400
impl<T: sealed::VariableLengthOnion> Features<T> {
280401
pub(crate) fn supports_variable_length_onion(&self) -> bool {
281-
self.flags.len() > 1 && (self.flags[1] & 3) != 0
402+
<T as sealed::VariableLengthOnion>::supports_feature(&self.flags)
282403
}
283404
}
284405

285406
impl<T: sealed::InitialRoutingSync> Features<T> {
286407
pub(crate) fn initial_routing_sync(&self) -> bool {
287-
self.flags.len() > 0 && (self.flags[0] & (1 << 3)) != 0
408+
<T as sealed::InitialRoutingSync>::supports_feature(&self.flags)
288409
}
289410
pub(crate) fn clear_initial_routing_sync(&mut self) {
290-
if self.flags.len() > 0 {
291-
self.flags[0] &= !(1 << 3);
292-
}
411+
<T as sealed::InitialRoutingSync>::clear_optional_bit(&mut self.flags)
293412
}
294413
}
295414

@@ -299,15 +418,15 @@ impl<T: sealed::PaymentSecret> Features<T> {
299418
// invoice provides a payment_secret, we assume that we can use it (ie that the recipient
300419
// supports payment_secret).
301420
pub(crate) fn supports_payment_secret(&self) -> bool {
302-
self.flags.len() > 1 && (self.flags[1] & (3 << (14-8))) != 0
421+
<T as sealed::PaymentSecret>::supports_feature(&self.flags)
303422
}
304423
}
305424

306425
impl<T: sealed::BasicMPP> Features<T> {
307426
// We currently never test for this since we don't actually *generate* multipath routes.
308427
#[allow(dead_code)]
309428
pub(crate) fn supports_basic_mpp(&self) -> bool {
310-
self.flags.len() > 2 && (self.flags[2] & (3 << (16-8*2))) != 0
429+
<T as sealed::BasicMPP>::supports_feature(&self.flags)
311430
}
312431
}
313432

0 commit comments

Comments
 (0)