Skip to content
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

Add gossipable trait + relevant logic #45

Draft
wants to merge 59 commits into
base: main
Choose a base branch
from

Conversation

Sanghren
Copy link

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

  1. Test File Structure: Currently, I've included tests within the same file as the logic they're intended to test. Is it better to separate them into their own files?
  2. Dependency Choices: I opted for using the probabilistic-collections crate for its BloomFilter implementation. Is there a preferred alternative that I should consider?
  3. File Placement: I'm uncertain where to place the sdk.proto file and its corresponding Rust-generated file. Could you please provide guidance on this?

Would love to have feedback on this. First dive into avalanche-rs so am not really familiar with it.

@Sanghren Sanghren mentioned this pull request Sep 14, 2023
@richardpringle
Copy link
Collaborator

@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!

let n = socket.read(&mut buf).await.unwrap();

// Empty, wait 5 sec before next attempt
if n == 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When socket.read returns 0, that means the client has disconnected. You will forever get 0, so this is an infinite loop.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if the client disconnects, the read call will error and the program will panic on that unwrap. If wouldn't infinite loop.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Collaborator

@richardpringle richardpringle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

@@ -12,25 +12,42 @@ readme = "README.md"

[dependencies]
avalanche-types = { path = "../../crates/avalanche-types", features = ["message"] }
async-trait = { version = "0.1.73", features = [] }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔, 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"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

random-manager = "0.0.5"
tokio = { version = "1.32.0", features = ["full"] }
testing_logger = "0.1.1"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔, not sure we need this as it looks like it's unmaintained. I guess I'll see where it's used.

Comment on lines 1 to 15
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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer merged imports as it's easier to keep them consistent when using automatic imports from tools like rust-analyzer.

Suggested change
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},
};

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I overlooked this, should check how you can configure this in CLion .

#[allow(unused_variables)]
impl Client for TestClient {
async fn app_gossip(&mut self, request_bytes: Vec<u8>) {
unimplemented!()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to clone msg_bytes here.

Suggested change
guard.app_request_any(msg_bytes.clone(), on_response).await;
guard.app_request_any(msg_bytes, on_response).await;

use std::sync::Arc;
use async_trait::async_trait;

pub type AppResponseCallback = Arc<dyn Fn(Vec<u8>) + Send + Sync>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you have to wrap the callback in an Arc?

}

#[async_trait]
#[allow(unused_variables)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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| {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@Sanghren
Copy link
Author

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!

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 ;)

@richardpringle
Copy link
Collaborator

@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!
image

@Sanghren
Copy link
Author

Sanghren commented Oct 2, 2023

@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! image

Thank you so much to take time to review this, helping me a lot and learning tons of stuff.
I tried to cover all your remarks, there is still some wonky stuff going on with the lock at the end of my start_fake_node function, that's why I left the scope around the asserts . Without the scope it sometimes hang. Trying to find out why.

@vikinatora
Copy link

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.

@Sanghren
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants