-
Notifications
You must be signed in to change notification settings - Fork 411
Support for custom feature bits #2204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for custom feature bits #2204
Conversation
Codecov ReportPatch coverage:
❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more. Additional details and impacted files@@ Coverage Diff @@
## main #2204 +/- ##
==========================================
- Coverage 91.50% 91.34% -0.16%
==========================================
Files 104 102 -2
Lines 52087 54840 +2753
Branches 52087 54840 +2753
==========================================
+ Hits 47660 50092 +2432
- Misses 4427 4748 +321
☔ View full report in Codecov by Sentry. |
14a41d6
to
72b51ca
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
lightning/src/ln/features.rs
Outdated
for (byte, o_byte) in self.flags.iter_mut().zip(o.flags.iter()) { | ||
*byte &= !*o_byte; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noting here I was confused by this at first but I realized Features::flags
are little endian so this makes sense 👍
lightning/src/ln/features.rs
Outdated
let last_non_zero_byte = self.flags.iter().rposition(|&byte| byte != 0); | ||
let size = if let Some(offset) = last_non_zero_byte { offset + 1 } else { 0 }; | ||
self.flags.resize(size, 0u8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think this is covered by tests, figure it'd probably be easy to if you weren't planning on it already :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
lightning/src/ln/features.rs
Outdated
/// Sets a custom feature bit. Errors if `bit` is outside the custom range as defined by | ||
/// [bLIP 2] or if it is a known `T` feature. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is a known T
feature bit that's outside the reserved 0-255 range for BOLTs? Would it be something like feature bits documents in blips like hosted_channels
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it's just a custom feature bit that LDK explicitly knows about and would already handle.
rust-lightning/lightning/src/ln/features.rs
Lines 86 to 92 in 72b51ca
/// The context in which [`Features`] are applicable. Defines which features are known to the | |
/// implementation, though specification of them as required or optional is up to the code | |
/// constructing a features object. | |
pub trait Context { | |
/// Bitmask for selecting features that are known to the implementation. | |
const KNOWN_FEATURE_MASK: &'static [u8]; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which would probably be already documented as a bLIP, yeah
lightning/src/ln/features.rs
Outdated
let known_bit = <sealed::InvoiceContext as sealed::PaymentSecret>::EVEN_BIT; | ||
let byte_offset = <sealed::InvoiceContext as sealed::PaymentSecret>::BYTE_OFFSET; | ||
assert_eq!(byte_offset, 1); | ||
assert_eq!(features.flags[byte_offset], 0b00000010); | ||
assert!(features.set_custom_bit(known_bit).is_err()); | ||
assert_eq!(features.flags[byte_offset], 0b00000010); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this test anything new since <sealed::InvoiceContext as sealed::PaymentSecret>::EVEN_BIT
= 14 which gets caught by the initial <256 check in set_custom_bit
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This checks that if set_custom_bit
errors then the feature bit remain unchanged.
lightning/src/ln/features.rs
Outdated
/// [bLIP 2] or if it is a known `T` feature. | ||
/// | ||
/// [bLIP 2]: https://github.com/lightning/blips/blob/master/blip-0002.md#feature-bits | ||
pub fn set_custom_bit(&mut self, bit: usize) -> Result<(), ()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this have a required/optional version and just deduce the bit by &ing with 2?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From review club: or validate that either optional or required is set, not both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this have a required/optional version and just deduce the bit by &ing with 2?
Hmm... I'm not sure I follow how that's useful or how that would even work for that matter (i.e., shift + AND 0b10
would either set no bits or the odd bit). And the user has to pass in something. Would they be expected to pass in an even or an odd bit? Seems error prone whereas explicitly giving the bit to set isn't. Maybe I'm missing something here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok yeah I can't really see what would be an appropriate input in each case. I don't think there's a way of making it seems obvious/natural other than just providing the bit to set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, missed the above comment. We'd basically mod the bit selected by two (and add one for optional), but the issue with the current API is its not at all clear whether we're setting a required or optional bit in the custom-bit-setting logic, but we do use the required/optional distinction in the connection checking logic (and across the codebase). Even if we don't split the method for convenience, we definitely need to carefully document the even/odd distinction here (which feels more verbose than just two methods, but whatever).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite get it. Are you saying we'd have two methods -- one for required and one for optional -- where two different inputs for each would result in the same value being set? (e.g., giving the required setter an odd bit would set the even bit?) Seems either way the caller needs to pass some bit, so how do they decide what to pass?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'm suggesting mapping the bit they pass to the "correct" one to the optional/required bit for that bit-pair. Its definitely awkward, but forcing the user to consider the even/odd thing manually seems just as awkward if not more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So for example, if the user gives an odd or even bit from the feature's bit-pair to set_required_custom_bit()
it should just set the corresponding even bit of the pair? I think as long as that little awkwardness is also documented then it's fine.
/// which are sent in our [`Init`] message. | ||
/// | ||
/// [`Init`]: crate::ln::msgs::Init | ||
fn provided_init_features(&self, their_node_id: &PublicKey) -> InitFeatures; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mentioned it briefly in review club - I'm wondering why there's only provided_x_features
for node
and init
in the various message handlers and no provided_channel_features
? I see in the channelmanager
module there's provided_channel_features
but it just wraps provided_init_features
with the user config. Is this just because right now there aren't much use for the channel announcement message to communicate features?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly but will let @TheBlueMatt confirm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ChannelFeatures are channel-specific - they describe things like "does the channel support PLTCs, is the channel taproot, etc", they're not something that's generic for a handler. Thus, they're not useful outside of the specific channel announcement (generated by the channelmanager) ie not useful in the peermanager.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah gotcha, thanks
72b51ca
to
03f0e13
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs a small rebase, one comment, otherwise LGTM.
lightning/src/ln/peer_handler.rs
Outdated
@@ -1323,7 +1346,8 @@ impl<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, OM: Deref, L: Deref, CM | |||
|
|||
// Need an Init as first message | |||
if let wire::Message::Init(msg) = message { | |||
if msg.features.requires_unknown_bits() { | |||
let additional_features = msg.features.clone() - self.init_features(&their_node_id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to do this via clone + subtract, vs just having a requires_unknown_bits_not_in()
method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly to be able to reuse requires_unknown_bits
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mmm, right, so this is confusing and buggy already today. Like, if we have a set of handlers that don't set, for example, data_loss_protect, but if our peer requires it we'll continue happily here (though our peer should disconnect us). Further, if we do require an unknown bit, and our peer doesn't support it, we'll happily continue talking to them. Instead, we should really check both ways for any required feature, not just required uknown-to-LDK features.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it seems requires_unknown_bits
is obsolete given it uses KNOWN_FEATURE_MASK
. A set of handlers may exclude a known feature. If the peer requires the feature, the method would return false instead of true. We really want to use a mask specific to the features supported by the handlers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I think this is implemented correctly now. PTAL. I can remove the set difference code when I squash the fixups.
03f0e13
to
bd7e721
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, feel free to squash + rebase imo.
The `lightning-custom-message` crate will need access to Features::or in order combine features of a composite handler. Expose this via a core::ops::BitOr implementation.
CustomMessageHandler implementations may need to advertise support for features. Add methods to CustomMessageHandler to provide these and combine them with features from other message handlers.
When checking features, rather than checking against which features LDK knows about, it is more useful to check against a peer's features. Add Features::requires_unknown_bits_from such that the given features are used instead.
bd7e721
to
c9553cf
Compare
Done |
Oops, sorry, missed your above comment, lgtm aside from #2204 (comment) |
c9553cf
to
125d6d5
Compare
LGTM, at least glancing ta the fixup commits, feel free to squash IMO. |
Each message handler provides which features it supports. A custom message handler may support unknown features. Therefore, these features should be checked against instead of the features known by LDK. Additionally, fail the connection if the peer requires features unknown to the handler. The peer should already fail the connection in the latter case.
Custom message handlers may need to set feature bits that are unknown to LDK. Provide Features::set_required_custom_bit and Features::set_optional_custom_bit to allow for this.
125d6d5
to
432d180
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your turn @dunxen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wanted to check the set_optional_/set_required_ methods and they seem good to me actually. Not so awkward and quite explicit. 👍
Each
CustomMessageHandler
may support customFeatures
and thus those must be included in theInit
message. Update the trait to include a way to advertise these. Additionally, they must be taken account when checking if our peer requires unknown features (i.e., it is ok if both we and our peer support the same "unknown" features).Also adds support for setting custom feature bits so that
CustomMessageHandler
implementations can set them.Fixes #2131