Add gossipable trait + relevant logic#45
Conversation
|
@Sanghren, thanks for the contribution! I took a quick look and I think things look pretty good. I'll leave a couple of comments next week! |
core/network/examples/peer_gossip.rs
Outdated
| let n = socket.read(&mut buf).await.unwrap(); | ||
|
|
||
| // Empty, wait 5 sec before next attempt | ||
| if n == 0 { |
There was a problem hiding this comment.
When socket.read returns 0, that means the client has disconnected. You will forever get 0, so this is an infinite loop.
There was a problem hiding this comment.
I think if the client disconnects, the read call will error and the program will panic on that unwrap. If wouldn't infinite loop.
There was a problem hiding this comment.
That's not true. Example program: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d809a48e2983d4fe94833852a8a28961
Client drops the tcp_stream on line 35, and waits for the server to finish up before exiting. While it's in this state, read() repeatedly returns Ok(0)
richardpringle
left a comment
There was a problem hiding this comment.
Cool! Awesome job if some of those examples work!
I'm happy to keep reviewing your code and give you some Rust-specific pointers, but as @exdx mentioned, it might be tough getting this merged in as the P2P stuff isn't stable yet.
Right now, there's quite a bit of code that could panic in spots where you wouldn't want code panicking.
If you're just looking to learn, I'm happy to help as much as I can, but if it's contributions that you want, it might be a better idea to take a look at something smaller. In either case, I'm here to help!
|
|
||
| [dependencies] | ||
| avalanche-types = { path = "../../crates/avalanche-types", features = ["message"] } | ||
| async-trait = { version = "0.1.73", features = [] } |
There was a problem hiding this comment.
🤔, usually libraries re-export async-trait such that the trait definition remains consistent with the trait implementation.
I know I'll get my answer below, but are we defining traits with the macro or as we implementing traits with the macro? Or both?
| [dev-dependencies] | ||
| env_logger = "0.10.0" | ||
| mockall = "0.11.4" | ||
| proptest = "1.2.0" |
| proptest = "1.2.0" | ||
| random-manager = "0.0.5" | ||
| tokio = { version = "1.32.0", features = ["full"] } | ||
| testing_logger = "0.1.1" |
There was a problem hiding this comment.
🤔, not sure we need this as it looks like it's unmaintained. I guess I'll see where it's used.
core/network/examples/peer_gossip.rs
Outdated
| use std::error::Error; | ||
| use std::hash::Hash; | ||
| use tokio::sync::Mutex; | ||
| use std::sync::Arc; | ||
| use std::time::Duration; | ||
| use async_trait::async_trait; | ||
| use tokio::net::{TcpListener, TcpStream}; | ||
| use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||
| use network::p2p::gossip::gossip::{Config, Gossiper}; | ||
| use tokio::sync::mpsc::{channel}; | ||
| use avalanche_types::ids::Id; | ||
| use network::p2p::client::{AppResponseCallback, Client}; | ||
| use network::p2p::gossip::{Gossipable, Set}; | ||
| use network::p2p::gossip::handler::{Handler, HandlerConfig, new_handler}; | ||
| use network::p2p::handler::Handler as TraitHandler; |
There was a problem hiding this comment.
Prefer merged imports as it's easier to keep them consistent when using automatic imports from tools like rust-analyzer.
| use std::error::Error; | |
| use std::hash::Hash; | |
| use tokio::sync::Mutex; | |
| use std::sync::Arc; | |
| use std::time::Duration; | |
| use async_trait::async_trait; | |
| use tokio::net::{TcpListener, TcpStream}; | |
| use tokio::io::{AsyncReadExt, AsyncWriteExt}; | |
| use network::p2p::gossip::gossip::{Config, Gossiper}; | |
| use tokio::sync::mpsc::{channel}; | |
| use avalanche_types::ids::Id; | |
| use network::p2p::client::{AppResponseCallback, Client}; | |
| use network::p2p::gossip::{Gossipable, Set}; | |
| use network::p2p::gossip::handler::{Handler, HandlerConfig, new_handler}; | |
| use network::p2p::handler::Handler as TraitHandler; | |
| use async_trait::async_trait; | |
| use avalanche_types::ids::Id; | |
| use network::p2p::{ | |
| client::{AppResponseCallback, Client}, | |
| gossip::{ | |
| gossip::{Config, Gossiper}, | |
| handler::{new_handler, Handler, HandlerConfig}, | |
| Gossipable, Set, | |
| }, | |
| handler::Handler as TraitHandler, | |
| }; | |
| use std::{error::Error, hash::Hash, sync::Arc, time::Duration}; | |
| use tokio::{ | |
| io::{AsyncReadExt, AsyncWriteExt}, | |
| net::{TcpListener, TcpStream}, | |
| sync::{mpsc::channel, Mutex}, | |
| }; |
There was a problem hiding this comment.
Oh I overlooked this, should check how you can configure this in CLion .
core/network/examples/peer_gossip.rs
Outdated
| #[allow(unused_variables)] | ||
| impl Client for TestClient { | ||
| async fn app_gossip(&mut self, request_bytes: Vec<u8>) { | ||
| unimplemented!() |
There was a problem hiding this comment.
FYI, todo! is a little shorter, and they pretty much do the same thing.
| }); | ||
|
|
||
| let mut guard = self.client.try_lock().expect("Failed to acquire a lock on client"); | ||
| guard.app_request_any(msg_bytes.clone(), on_response).await; |
There was a problem hiding this comment.
I don't think you need to clone msg_bytes here.
| guard.app_request_any(msg_bytes.clone(), on_response).await; | |
| guard.app_request_any(msg_bytes, on_response).await; |
core/network/src/p2p/client.rs
Outdated
| use std::sync::Arc; | ||
| use async_trait::async_trait; | ||
|
|
||
| pub type AppResponseCallback = Arc<dyn Fn(Vec<u8>) + Send + Sync>; |
There was a problem hiding this comment.
Why do you have to wrap the callback in an Arc?
| } | ||
|
|
||
| #[async_trait] | ||
| #[allow(unused_variables)] |
There was a problem hiding this comment.
You shouldn't really ever have this on a trait. Just use the _ prefix where necessary.
| let mut response_size = 0_usize; | ||
| let mut gossip_bytes: Vec<Vec<u8>> = Vec::new(); | ||
| let guard = self.set.try_lock().expect("Lock failed on set"); | ||
| guard.iterate(&|gossipable| { |
There was a problem hiding this comment.
| guard.iterate(&|gossipable| { | |
| guard.iterate(|gossipable| { |
| set: Arc<Mutex<S>>, | ||
| client: Arc<Client>, | ||
| stop_rx: Receiver<()>, | ||
| phantom: PhantomData<T>, // Had to use this to please the compiler about T not being used. |
There was a problem hiding this comment.
Quick lesson on when to use associated types vs. generics (in case you don't already know this):
A generic type represents many types, right? So, would it make sense to have many different trait implementations, one for each concrete type? OR... is there a single type that should be associated with this particular trait implementation?
Say the Iterator trait did not exist, and we wanted to create an Iterator trait and implement it for Vec<T> (not actually how this works, but I think the example will be clear). Would we want to have a different implementation for a Vec<String> vs. a Vec<u8>? Definitely not! Here's where we want an associated type.
A good rule of thumb is to actually start with an associated type and see if you can get it to work the way you want; it will become clear very quickly if you actually need to make your trait generic.
Thanks for all the feedbacks ! My goal here is more to learn something (never used rust for this kind of work, so definitely lot of things I don't know) than to have something merged in. Am gonna aim for something working, would be a nice achievement ;) |
|
@Sanghren, hit the "Re-request review" button when you're ready for another round! I can't promise I'll get to it right away, but I will get there when I get a chance! |
Thank you so much to take time to review this, helping me a lot and learning tons of stuff. |
|
Hey @Sanghren, do you still intend to work on this PR in the near future? Our team can hop in and try to finish the great work you've started. |
eyh o/ Well I submitted some stuff and was waiting for a review (kinda lost track of this tbh). Please do, not sure my code is worth it though, I am learning here. |


Summary
This pull request is an initial attempt to address the issue: #29
Changes
Added traits for Gossipable and Set.
Implemented logic for Gossiper and Handler (first dab at it).
Build rust source from the sdk.proto
Dummy Client implementation (real implementation out of scope of this PR, imho)
Basic Handler implemenation.
ToDos
[ ] More tests
[ ] Logic review (feel like i can improve stuff)
Questions
Would love to have feedback on this. First dive into avalanche-rs so am not really familiar with it.