-
Notifications
You must be signed in to change notification settings - Fork 409
Implement DisconnectPeer event + test #43
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
Conversation
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.
Started to review and then stepped back cause I'm not sure about the approach. Generally, the error handling stuff is all a bit of a partial-implement right now - my high-level thinking was the msg in HandleError would be renamed to something like action and moved from an Option<> to just an ErrorAction. Then, when a HandleError comes up the stack, you do what it says in the action field, and there would be a SendErrorMessage enum for ErrorAction, instead of making HandleError something you can serialize into an error message and send.
src/ln/peer_channel_encryptor.rs
Outdated
@@ -242,7 +242,7 @@ impl PeerChannelEncryptor { | |||
} | |||
|
|||
// Separated for testing: | |||
fn process_act_one_with_ephemeral_key(&mut self, act_one: &[u8], our_node_secret: &SecretKey, our_ephemeral: SecretKey) -> Result<[u8; 50], HandleError> { | |||
pub fn process_act_one_with_ephemeral_key(&mut self, act_one: &[u8], our_node_secret: &SecretKey, our_ephemeral: SecretKey) -> Result<[u8; 50], HandleError> { |
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.
Please only use pub(super) not pub for this kind of thing (and maybe note that its also for testing only). Still, I think you can do this testing without exposing a bunch of testing-specific functions - is it possible to hit it with keys that will result in build_remote_transaction_keys failing 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.
Yes, in fact dropping the send message part from DisconnectPeer event means that I don't need the peer_channel_encryptor testing function anymore. I'll move the test to channelmanager and hit via build_remote_transaction_keys, but I'm stuck on generating "valid" invalid pubkeys to pass them to build_remote_transaction_keys , as the rust-secp256k1 internals prevent you of doing this.
src/ln/channelmanager.rs
Outdated
//TODO: Push e to pendingevents | ||
Err(e) => { | ||
let mut pending_events = self.pending_events.lock().unwrap(); | ||
pending_events.push(events::Event::DisconnectPeer { |
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 should be more like HandleError, not DisconnectPeer, and we should handle the error as any other error instead, I think.
aa622b9
to
fd4524b
Compare
In funding_transaction_generated, I rewrote with a match to let taking action on every kind of errors. If match, and if needed, push the event to peer_handler. I'm worried that for every HandleError to manage, we'll have a lot of particular code, than we can't simplifiy because you need to pass specific data from current function environnement. I implement also SendErrorMessage as I understand what you describe, even if there is no case for the moment. Maybe all this stuff is too much early and if so close the PR, but I hope it will help clarify thinking on error management. |
Combining ErrorAction should be also easy, in some case you want SendErrorMessage + DisconnectPeer or BanPeer + DisconnectPeer |
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.
Awesome, looking good, few API/general comments, and haven't had a chance to review the new test-cases yet, but thanks for adding some tests for the peer_handling logic!
src/ln/channelmanager.rs
Outdated
if let Some(action) = e.msg { | ||
match action { | ||
msgs::ErrorAction::DisconnectPeer {} => { | ||
add_pending_event!(events::Event::DisconnectPeer { |
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.
Please dont take both the pending_events lock and the channel_stat lock at the same time here. I'm probably overparanoid about deadlocks, but we dont have any automated analysis to detect them so best to take the perf hit of just using a temporary vector to ensure they cant happen.
src/ln/channelmanager.rs
Outdated
@@ -1811,6 +1828,12 @@ impl ChannelMessageHandler for ChannelManager { | |||
} | |||
} | |||
} | |||
|
|||
//For testing purpose in peer_handler | |||
fn push_event(&self, event: events::Event) { |
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.
Hmm, I mean you could put a #[cfg(test)] here, actually.
src/ln/msgs.rs
Outdated
@@ -435,6 +439,9 @@ pub trait ChannelMessageHandler : events::EventsProvider + Send + Sync { | |||
/// understand or indicate they require unknown feature bits), no_connection_possible is set | |||
/// and any outstanding channels should be failed. | |||
fn peer_disconnected(&self, their_node_id: &PublicKey, no_connection_possible: bool); | |||
|
|||
//Testing | |||
fn push_event(&self, event: events::Event); |
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.
Please at least put a #[cfg(test)] here so that there isn't a testing function in a pub trait.
src/util/events.rs
Outdated
}, | ||
// Events indicating the network loop should change the state of peer connection: | ||
/// Used to indicate that the peer connection should be closed with a message holding the reason | ||
DisconnectPeer { |
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.
Why not just make it a bool in SendErrorMessage as to whether to disconnect? Feels like thats usually what you'd want, no?
src/ln/peer_handler.rs
Outdated
} | ||
|
||
upstream_events.push(event); | ||
} | ||
} | ||
|
||
for descriptor in disconnect_peers { | ||
self.disconnect_event(&descriptor); |
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.
You also need some fn in the SocketDescriptor trait that allows us to notify the network provider that we want to disconnect the peer. For simplicity of this patch, I think its fine to indicate that if its called, disconnect_event must not be called, nor any future read_events.
fd4524b
to
0df41f4
Compare
Implemented comments, will add also test on funding_transaction_generated case in channelmanager I think in another PR to stay simple here. Peer_handler test is very basic for the moment but I hope it will help more test on this part later |
src/util/events.rs
Outdated
@@ -100,6 +100,13 @@ pub enum Event { | |||
BroadcastChannelUpdate { | |||
msg: msgs::ChannelUpdate, | |||
}, | |||
/// Used to tell a peer that something is incorrect | |||
SendErrorMessage { |
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 change as recommanded, but add two flags, one for disconnection and another one for a "hard" disconnect, when you want you to disconnect without spoiling ressources on an error message , in case of banned peers or malicious ones.
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.
Note that travis failed as fuzz failed to build, also please check that you're not adding more rustc warnings when they aren't neccessary.
src/ln/channelmanager.rs
Outdated
@@ -677,6 +677,15 @@ impl ChannelManager { | |||
/// Call this upon creation of a funding transaction for the given channel. | |||
/// Panics if a funding transaction has already been provided for this channel. | |||
pub fn funding_transaction_generated(&self, temporary_channel_id: &Uint256, funding_txo: OutPoint) { | |||
|
|||
macro_rules! add_pending_event { |
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.
nit: maybe move down closer to first use?
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.
Might as well fix this while you're fixing other stuff, so the duplicate-lock issue doesn't get re-introduced.
src/ln/channelmanager.rs
Outdated
println!("Got error handling message: {}!", e.err); | ||
if let Some(action) = e.msg { | ||
match action { | ||
msgs::ErrorAction::DisconnectPeer {} => { |
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.
Hmmm, I'm really not a fan of this setup - needing to handle conversion of errors to different types of errors at each place where we can generate outbound messages really sucks. Just having a general HandleOutboundMsgGenerationError event which looks like SendErrorMessage as proposed but instead of msg, disconnect, and hard, it just holds a HandleError should simplify this PR, and more importantly, future PRs. Further, SendErrorMessage would be a HandleError::msg action option (with a disconnect option, or maybe it could go in DisconnectPeer? I'm not picky there, I think).
src/ln/peer_handler.rs
Outdated
@@ -37,6 +37,9 @@ pub trait SocketDescriptor : cmp::Eq + hash::Hash + Clone { | |||
/// indicating that read events on this descriptor should resume. A resume_read of false does | |||
/// *not* imply that further read events should be paused. | |||
fn send_data(&mut self, data: &Vec<u8>, write_offset: usize, resume_read: bool) -> usize; | |||
///Indicate to the network provider that we want to disconnect this peer, no more allowing any call | |||
///to disconnect_event or read_event | |||
fn notify_disconnection(&mut self); |
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.
Maybe call this "disconnect_socket" instead, and the comment needs flushed out more since its an external API that's easy to mess up:
/// Disconnect the socket pointed to by this SocketDescriptor. Once this function returns, no
/// more calls to write_event, read_event or disconnect_event may be made with this descriptor.
/// No disconnect_event should be generated as a result of this call, though obviously races
/// may occur whereby disconnect_socket is called after a call to disconnect_event but prior to
/// that event completing.
should be sufficient, no?
0df41f4
to
53cea1b
Compare
src/ln/channelmanager.rs
Outdated
//TODO: Push e to pendingevents | ||
Err(e) => { | ||
mem::drop(channel_state); | ||
add_pending_event!(events::Event::HandleOutboundError { |
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.
After previous version, was working on others parts on channelmanager on error handling, and yes it's cumbersome to deal with multiple errors each time, doesn't ease the codebase. So I wrote HandleOutboundError event (really not sure about the name, for me HandleOutboundMsgGenerationError was too specific with DisconnectPeer-only case) which contains a vec of ErrorAction to combine SendErrorMessage, DisconnectPeer and futures errors. That's still hacky, ideally that would be HandleError which hold multiple ErrorAction instead of an Option like right now and having wrapper event like HandleOutboundError avoiding a 1:1 mapping ErrorAction-Events. What do you think about if I refactor first HandleError with a Vec of ErrorAction and after going back to this PR ? Open an issue if you want to think on it before.
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.
Hmm, I think I kinda prefer a 1-to-1 mapping for now, on both - a DisconnectPeer ErrorAction could reasonably take an ErrorMessage to be sent pre-disconnect. If we ever want to do more than just send an ErrorMessage we could revisit it, but having the Vec indirection kinda sucks, especially when we really are using it as an Option and never anything more.
Yes, sorry for the fuzzing, didn't think about the adding of disconnect_socket. |
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.
Sorry it took me a bit to get back to this.
@@ -114,6 +114,7 @@ impl SocketDescriptor for Peer { | |||
assert!(write_offset < data.len()); | |||
data.len() - write_offset | |||
} | |||
fn disconnect_socket(&mut self) {} |
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 needs to be implemented - it needs to set the bool in peers appropriately, which I guess is gonna need some kind of Rc<RefCell<[]>>. I think you can skip handling the race condition where a disconnect_socket and a disconnect_event happen just because its all single threaded here.
src/ln/channelmanager.rs
Outdated
//TODO: Push e to pendingevents | ||
Err(e) => { | ||
mem::drop(channel_state); | ||
add_pending_event!(events::Event::HandleOutboundError { |
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.
Hmm, I think I kinda prefer a 1-to-1 mapping for now, on both - a DisconnectPeer ErrorAction could reasonably take an ErrorMessage to be sent pre-disconnect. If we ever want to do more than just send an ErrorMessage we could revisit it, but having the Vec indirection kinda sucks, especially when we really are using it as an Option and never anything more.
@@ -1534,3 +1546,11 @@ impl MsgEncodable for OnionErrorPacket { | |||
res | |||
} | |||
} | |||
|
|||
impl MsgEncodable for ErrorMessage { |
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.
Can you add a TODO for MsgDecodable for ErrorMessage? We should obviously also support receiving them.
src/ln/msgs.rs
Outdated
@@ -139,6 +139,10 @@ pub struct Init { | |||
pub local_features: LocalFeatures, | |||
} | |||
|
|||
pub struct ErrorMessage { | |||
pub err: &'static str, |
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 that we can both send and receive, I think this should be String.
src/ln/channelmanager.rs
Outdated
@@ -677,6 +677,15 @@ impl ChannelManager { | |||
/// Call this upon creation of a funding transaction for the given channel. | |||
/// Panics if a funding transaction has already been provided for this channel. | |||
pub fn funding_transaction_generated(&self, temporary_channel_id: &Uint256, funding_txo: OutPoint) { | |||
|
|||
macro_rules! add_pending_event { |
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.
Might as well fix this while you're fixing other stuff, so the duplicate-lock issue doesn't get re-introduced.
src/ln/peer_handler.rs
Outdated
@@ -710,12 +721,32 @@ impl<Descriptor: SocketDescriptor> PeerManager<Descriptor> { | |||
} | |||
continue; | |||
}, | |||
Event::HandleOutboundError { ref node_id, ref err } => { | |||
let (mut descriptor, peer) = get_peer_for_forwarding!(node_id, { | |||
//TODO: Do whatever we're gonna do for handling dropped messages |
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.
Note duplicate indentation here, and also probably don't need a TODO, just note that we don't have to disconnect them if they're already disconnected.
src/ln/peer_handler.rs
Outdated
@@ -576,6 +586,7 @@ impl<Descriptor: SocketDescriptor> PeerManager<Descriptor> { | |||
/// calls to ChannelManager::process_pending_htlc_forward. | |||
pub fn process_events(&self) { | |||
let mut upstream_events = Vec::new(); | |||
let mut disconnect_peers = Vec::new(); |
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 dont see why you need this - you're doing two map lookups, passing an event to that entry, then doing another two map lookups to remove it, might as well replace the get_peer_for_forwarding with a written out version that removes the entries all at once.
} | ||
|
||
#[test] | ||
fn test_disconnect_peer() { |
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 seems way overkill for what you want - instead of starting up Monitors and Broadcasters and ChainWatchers and Routers and ChannelManagers, why not add some dummy ChannelMessageHandler and RoutingMessageHandlers to test_utils (or just put them here). That way you can mock the message handlers without needing to add a push_event to the traits themselves and you can more directly test the peer_handler logic without needing to worry about what's handling the messages themselves.
53cea1b
to
c26b0fb
Compare
I split the ErrorMessage implementation in a commit appart with encode/decode stuff. Add a lot of things in test_utils for easing the disconnect test case, is this what you had in head ? |
} | ||
impl SocketDescriptor for Peer { | ||
fn send_data(&mut self, data: &Vec<u8>, write_offset: usize, _resume_read: bool) -> usize { | ||
assert!(write_offset < data.len()); | ||
data.len() - write_offset | ||
} | ||
fn disconnect_socket(&mut self) { | ||
self.connected = false; | ||
} |
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.
Sorry didn't get you on this one, disconnect_socket is used by the network provider to notify that we want to disconnect the peer, right ? So what do we want here is setting a bool in SocketDescriptor object and let the network provider disconnect peer, block further disconnect_event and remove peer infos. Why do we need RefCell ?
Add test for DisconnectPeer event Update DisconnectPeer with optional ErrorMessage Manage error for funding_transaction_generated Add disconnect_socket to SocketDescriptor trait
c26b0fb
to
f32d1ea
Compare
Awesome, will take another look at this tomorrow morning, thanks for doing the rebase. |
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, thanks for finishing this one. A few one-line comments, but I'm gonna go ahead and fix them, rebase and merge this.
let len = byte_utils::slice_to_be16(&v[33..34]); | ||
let mut data = String::new(); | ||
if len > 0 { | ||
data = String::from_utf8(v[35..len as usize].to_vec()).unwrap(); |
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.
unwrap() here can panic if the message contains non-utf-8 crap.
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.
Also, some of the indexing is off in the decoder here.
return Err(HandleError{err: "Peer never wants payout outputs?", action: Some(msgs::ErrorAction::DisconnectPeer{})}); | ||
return Err(HandleError{err: "Peer never wants payout outputs?", action: Some(msgs::ErrorAction::DisconnectPeer{ msg: None })}); | ||
} | ||
if msg.max_htlc_value_in_flight_msat > msg.funding_satoshis * 1000 { |
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.
Rebase error here - this if was removed on master.
} | ||
descriptor.disconnect_socket(); | ||
} | ||
self.message_handler.chan_handler.peer_disconnected(&node_id, false); |
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 should be redundant - if we dont have a descriptor for the node_id we've already disconnected it, and, thus, already called peer_disconnected.
peers[0].message_handler.chan_handler = Arc::new(chan_handler); | ||
|
||
peers[0].process_events(); | ||
assert_eq!(peers[0].peers.lock().unwrap().peers.len(), 0); |
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, this test looks good, I'm gonna add a few more asserts just to increase coverage but I like the layout here.
Will take as #78 |
At first, I intended to clean the TODO in funding_transaction_generated, and then I ended implementing DisconnectPeer event into peer_handler and events.
It contains only node_id and an HandleError, the latter holding differents states to adapt Disconnect processing and make the sending of an error message optionnal.
Add a basic module test into peer_handler, pulling from peer_handler_encryptor to push the peers into NoiseState::Finished and try the disconnection.
I would like also to test funding_transaction_generated but I didn't find a way to provide invalid PubKeys to build_remote_transaction_keys, does bitcoin crates API give us a way to generate invalid PubKeys for testing ?
Waiting your feedbacks!