Skip to content

Commit 7d93fe0

Browse files
committed
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.
1 parent ec68a01 commit 7d93fe0

File tree

1 file changed

+160
-57
lines changed

1 file changed

+160
-57
lines changed

lightning/src/ln/features.rs

Lines changed: 160 additions & 57 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,96 @@ 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 {}
32-
33-
pub trait VariableLengthOnion: Context {}
34-
impl VariableLengthOnion for InitContext {}
35-
impl VariableLengthOnion 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+
($even_bit: expr, $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 = $even_bit;
37+
38+
/// The bit used to signify that the feature is optional.
39+
const ODD_BIT: usize = $odd_bit;
40+
41+
/// The byte where the feature is set.
42+
const BYTE_OFFSET: usize = Self::EVEN_BIT / 8;
43+
44+
/// The bitmask for the feature's required flag relative to the [`BYTE_OFFSET`].
45+
///
46+
/// [`BYTE_OFFSET`]: #associatedconstant.BYTE_OFFSET
47+
const REQUIRED_MASK: u8 = 1 << (Self::EVEN_BIT - 8 * Self::BYTE_OFFSET);
48+
49+
/// The bitmask for the feature's optional flag relative to the [`BYTE_OFFSET`].
50+
///
51+
/// [`BYTE_OFFSET`]: #associatedconstant.BYTE_OFFSET
52+
const OPTIONAL_MASK: u8 = 1 << (Self::ODD_BIT - 8 * Self::BYTE_OFFSET);
53+
54+
/// Returns whether the feature is supported by the given flags.
55+
#[inline]
56+
fn supports_feature(flags: &Vec<u8>) -> bool {
57+
flags.len() > Self::BYTE_OFFSET &&
58+
(flags[Self::BYTE_OFFSET] & (Self::REQUIRED_MASK | Self::OPTIONAL_MASK)) != 0
59+
}
60+
61+
/// Sets the feature's optional (odd) bit in the given flags.
62+
#[inline]
63+
fn set_optional_bit(flags: &mut Vec<u8>) {
64+
if flags.len() <= Self::BYTE_OFFSET {
65+
flags.resize(Self::BYTE_OFFSET + 1, 0u8);
66+
}
67+
68+
flags[Self::BYTE_OFFSET] |= Self::OPTIONAL_MASK;
69+
}
70+
71+
/// Clears the feature's optional (odd) bit from the given flags.
72+
#[inline]
73+
fn clear_optional_bit(flags: &mut Vec<u8>) {
74+
if flags.len() > Self::BYTE_OFFSET {
75+
flags[Self::BYTE_OFFSET] &= !Self::OPTIONAL_MASK;
76+
}
77+
}
78+
}
3679

37-
pub trait PaymentSecret: Context {}
38-
impl PaymentSecret for InitContext {}
39-
impl PaymentSecret for NodeContext {}
80+
$(
81+
impl $feature for $context {}
82+
)*
83+
}
84+
}
4085

41-
pub trait BasicMPP: Context {}
42-
impl BasicMPP for InitContext {}
43-
impl BasicMPP for NodeContext {}
86+
define_feature!(0, 1, DataLossProtect, [InitContext, NodeContext],
87+
"Feature flags for `option_data_loss_protect`.");
88+
// NOTE: Per Bolt #9, initial_routing_sync has no even bit.
89+
define_feature!(3, 3, InitialRoutingSync, [InitContext],
90+
"Feature flags for `initial_routing_sync`.");
91+
define_feature!(4, 5, UpfrontShutdownScript, [InitContext, NodeContext],
92+
"Feature flags for `option_upfront_shutdown_script`.");
93+
define_feature!(8, 9, VariableLengthOnion, [InitContext, NodeContext],
94+
"Feature flags for `var_onion_optin`.");
95+
define_feature!(14, 15, PaymentSecret, [InitContext, NodeContext],
96+
"Feature flags for `payment_secret`.");
97+
define_feature!(16, 17, BasicMPP, [InitContext, NodeContext],
98+
"Feature flags for `basic_mpp`.");
99+
100+
/// Generates a feature flag byte with the given features set as optional. Useful for initializing
101+
/// the flags within [`Features`].
102+
///
103+
/// [`Features`]: struct.Features.html
104+
macro_rules! feature_flags {
105+
($context: ty; $($feature: ident)|*) => {
106+
(0b00_00_00_00
107+
$(
108+
| <$context as sealed::$feature>::OPTIONAL_MASK
109+
)*
110+
)
111+
}
112+
}
44113
}
45114

46115
/// Tracks the set of features which a node implements, templated by the context in which it
@@ -81,7 +150,11 @@ impl InitFeatures {
81150
/// Create a Features with the features we support
82151
pub fn supported() -> InitFeatures {
83152
InitFeatures {
84-
flags: vec![2 | 1 << 3 | 1 << 5, 1 << (9-8) | 1 << (15 - 8), 1 << (17 - 8*2)],
153+
flags: vec![
154+
feature_flags![sealed::InitContext; DataLossProtect | InitialRoutingSync | UpfrontShutdownScript],
155+
feature_flags![sealed::InitContext; VariableLengthOnion | PaymentSecret],
156+
feature_flags![sealed::InitContext; BasicMPP],
157+
],
85158
mark: PhantomData,
86159
}
87160
}
@@ -144,14 +217,22 @@ impl NodeFeatures {
144217
#[cfg(not(feature = "fuzztarget"))]
145218
pub(crate) fn supported() -> NodeFeatures {
146219
NodeFeatures {
147-
flags: vec![2 | 1 << 5, 1 << (9 - 8) | 1 << (15 - 8), 1 << (17 - 8*2)],
220+
flags: vec![
221+
feature_flags![sealed::NodeContext; DataLossProtect | UpfrontShutdownScript],
222+
feature_flags![sealed::NodeContext; VariableLengthOnion | PaymentSecret],
223+
feature_flags![sealed::NodeContext; BasicMPP],
224+
],
148225
mark: PhantomData,
149226
}
150227
}
151228
#[cfg(feature = "fuzztarget")]
152229
pub fn supported() -> NodeFeatures {
153230
NodeFeatures {
154-
flags: vec![2 | 1 << 5, 1 << (9 - 8) | 1 << (15 - 8), 1 << (17 - 8*2)],
231+
flags: vec![
232+
feature_flags![sealed::NodeContext; DataLossProtect | UpfrontShutdownScript],
233+
feature_flags![sealed::NodeContext; VariableLengthOnion | PaymentSecret],
234+
feature_flags![sealed::NodeContext; BasicMPP],
235+
],
155236
mark: PhantomData,
156237
}
157238
}
@@ -160,15 +241,25 @@ impl NodeFeatures {
160241
/// relevant in a node-context features and creates a node-context features from them.
161242
/// Be sure to blank out features that are unknown to us.
162243
pub(crate) fn with_known_relevant_init_flags(init_ctx: &InitFeatures) -> Self {
244+
// Generates a bitmask with both even and odd bits set for the given features. Bitwise
245+
// AND-ing it with a byte will select only common features.
246+
macro_rules! features_including {
247+
($($feature: ident)|*) => {
248+
(0b00_00_00_00
249+
$(
250+
| <sealed::NodeContext as sealed::$feature>::REQUIRED_MASK
251+
| <sealed::NodeContext as sealed::$feature>::OPTIONAL_MASK
252+
)*
253+
)
254+
}
255+
}
256+
163257
let mut flags = Vec::new();
164258
for (i, feature_byte)in init_ctx.flags.iter().enumerate() {
165259
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),
260+
0 => flags.push(feature_byte & features_including![DataLossProtect | UpfrontShutdownScript]),
261+
1 => flags.push(feature_byte & features_including![VariableLengthOnion | PaymentSecret]),
262+
2 => flags.push(feature_byte & features_including![BasicMPP]),
172263
_ => (),
173264
}
174265
}
@@ -201,33 +292,47 @@ impl<T: sealed::Context> Features<T> {
201292
}
202293

203294
pub(crate) fn requires_unknown_bits(&self) -> bool {
295+
// Generates a bitmask with all even bits set except for the given features. Bitwise
296+
// AND-ing it with a byte will select unknown required features.
297+
macro_rules! features_excluding {
298+
($($feature: ident)|*) => {
299+
(0b01_01_01_01
300+
$(
301+
& !(<sealed::InitContext as sealed::$feature>::REQUIRED_MASK)
302+
)*
303+
)
304+
}
305+
}
306+
204307
self.flags.iter().enumerate().any(|(idx, &byte)| {
205308
(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),
309+
0 => (byte & features_excluding![DataLossProtect | InitialRoutingSync | UpfrontShutdownScript]),
310+
1 => (byte & features_excluding![VariableLengthOnion | PaymentSecret]),
311+
2 => (byte & features_excluding![BasicMPP]),
312+
_ => (byte & features_excluding![]),
217313
}) != 0
218314
})
219315
}
220316

221317
pub(crate) fn supports_unknown_bits(&self) -> bool {
318+
// Generates a bitmask with all even and odd bits set except for the given features. Bitwise
319+
// AND-ing it with a byte will select unknown supported features.
320+
macro_rules! features_excluding {
321+
($($feature: ident)|*) => {
322+
(0b11_11_11_11
323+
$(
324+
& !(<sealed::InitContext as sealed::$feature>::REQUIRED_MASK)
325+
& !(<sealed::InitContext as sealed::$feature>::OPTIONAL_MASK)
326+
)*
327+
)
328+
}
329+
}
330+
222331
self.flags.iter().enumerate().any(|(idx, &byte)| {
223332
(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),
333+
0 => (byte & features_excluding![DataLossProtect | InitialRoutingSync | UpfrontShutdownScript]),
334+
1 => (byte & features_excluding![VariableLengthOnion | PaymentSecret]),
335+
2 => (byte & features_excluding![BasicMPP]),
231336
_ => byte,
232337
}) != 0
233338
})
@@ -262,34 +367,32 @@ impl<T: sealed::Context> Features<T> {
262367

263368
impl<T: sealed::DataLossProtect> Features<T> {
264369
pub(crate) fn supports_data_loss_protect(&self) -> bool {
265-
self.flags.len() > 0 && (self.flags[0] & 3) != 0
370+
<T as sealed::DataLossProtect>::supports_feature(&self.flags)
266371
}
267372
}
268373

269374
impl<T: sealed::UpfrontShutdownScript> Features<T> {
270375
pub(crate) fn supports_upfront_shutdown_script(&self) -> bool {
271-
self.flags.len() > 0 && (self.flags[0] & (3 << 4)) != 0
376+
<T as sealed::UpfrontShutdownScript>::supports_feature(&self.flags)
272377
}
273378
#[cfg(test)]
274379
pub(crate) fn unset_upfront_shutdown_script(&mut self) {
275-
self.flags[0] &= !(1 << 5);
380+
<T as sealed::UpfrontShutdownScript>::clear_optional_bit(&mut self.flags)
276381
}
277382
}
278383

279384
impl<T: sealed::VariableLengthOnion> Features<T> {
280385
pub(crate) fn supports_variable_length_onion(&self) -> bool {
281-
self.flags.len() > 1 && (self.flags[1] & 3) != 0
386+
<T as sealed::VariableLengthOnion>::supports_feature(&self.flags)
282387
}
283388
}
284389

285390
impl<T: sealed::InitialRoutingSync> Features<T> {
286391
pub(crate) fn initial_routing_sync(&self) -> bool {
287-
self.flags.len() > 0 && (self.flags[0] & (1 << 3)) != 0
392+
<T as sealed::InitialRoutingSync>::supports_feature(&self.flags)
288393
}
289394
pub(crate) fn clear_initial_routing_sync(&mut self) {
290-
if self.flags.len() > 0 {
291-
self.flags[0] &= !(1 << 3);
292-
}
395+
<T as sealed::InitialRoutingSync>::clear_optional_bit(&mut self.flags)
293396
}
294397
}
295398

@@ -299,15 +402,15 @@ impl<T: sealed::PaymentSecret> Features<T> {
299402
// invoice provides a payment_secret, we assume that we can use it (ie that the recipient
300403
// supports payment_secret).
301404
pub(crate) fn supports_payment_secret(&self) -> bool {
302-
self.flags.len() > 1 && (self.flags[1] & (3 << (14-8))) != 0
405+
<T as sealed::PaymentSecret>::supports_feature(&self.flags)
303406
}
304407
}
305408

306409
impl<T: sealed::BasicMPP> Features<T> {
307410
// We currently never test for this since we don't actually *generate* multipath routes.
308411
#[allow(dead_code)]
309412
pub(crate) fn supports_basic_mpp(&self) -> bool {
310-
self.flags.len() > 2 && (self.flags[2] & (3 << (16-8*2))) != 0
413+
<T as sealed::BasicMPP>::supports_feature(&self.flags)
311414
}
312415
}
313416

0 commit comments

Comments
 (0)