From db5a29137bb96834220f0bda803fb6601179ea74 Mon Sep 17 00:00:00 2001 From: Jos Dehaes Date: Wed, 6 Mar 2024 10:59:10 +0100 Subject: [PATCH] feat: peergossip --- cli/src/commands/start.rs | 6 +++++- node/router/src/handshake.rs | 6 ++++++ node/router/src/heartbeat.rs | 15 ++++++++++++--- node/router/src/inbound.rs | 3 +++ node/router/src/lib.rs | 14 ++++++++++++++ node/router/tests/common/mod.rs | 3 +++ node/src/client/mod.rs | 1 + node/src/node.rs | 2 ++ node/src/prover/mod.rs | 1 + node/src/validator/mod.rs | 3 +++ node/tests/common/node.rs | 1 + 11 files changed, 51 insertions(+), 4 deletions(-) diff --git a/cli/src/commands/start.rs b/cli/src/commands/start.rs index 8205d35c05..5e5862298d 100644 --- a/cli/src/commands/start.rs +++ b/cli/src/commands/start.rs @@ -144,6 +144,10 @@ pub struct Start { #[clap(long)] /// If development mode is enabled, specify the custom bonded balances as a json object. (default: None) dev_bonded_balances: Option, + + #[clap(long = "allow-outside-peers")] + /// If the flag is set, the node will engage in P2P gossip to request more peers. (default: false) + allow_outside_peers: bool, } impl Start { @@ -527,7 +531,7 @@ impl Start { // Initialize the node. let bft_ip = if self.dev.is_some() { self.bft } else { None }; match node_type { - NodeType::Validator => Node::new_validator(self.node, bft_ip, rest_ip, self.rest_rps, account, &trusted_peers, &trusted_validators, genesis, cdn, storage_mode).await, + NodeType::Validator => Node::new_validator(self.node, bft_ip, rest_ip, self.rest_rps, account, &trusted_peers, &trusted_validators, genesis, cdn, storage_mode, self.allow_outside_peers).await, NodeType::Prover => Node::new_prover(self.node, account, &trusted_peers, genesis, storage_mode).await, NodeType::Client => Node::new_client(self.node, rest_ip, self.rest_rps, account, &trusted_peers, genesis, cdn, storage_mode).await, } diff --git a/node/router/src/handshake.rs b/node/router/src/handshake.rs index 195298eaea..8a45cee439 100644 --- a/node/router/src/handshake.rs +++ b/node/router/src/handshake.rs @@ -264,6 +264,12 @@ impl Router { if self.is_connected(&peer_ip) { bail!("Dropping connection request from '{peer_ip}' (already connected)") } + // Only allow trusted peers to connect if we are a validator + // (unless allow_outside_peers is set) + let is_validator = self.node_type().is_validator(); + if is_validator && !self.allow_outside_peers() && !self.is_trusted(&peer_ip) { + bail!("Dropping connection request from '{peer_ip}' (untrusted)") + } // Ensure the peer is not restricted. if self.is_restricted(&peer_ip) { bail!("Dropping connection request from '{peer_ip}' (restricted)") diff --git a/node/router/src/heartbeat.rs b/node/router/src/heartbeat.rs index 9eb1247ea0..599da2c6ea 100644 --- a/node/router/src/heartbeat.rs +++ b/node/router/src/heartbeat.rs @@ -107,6 +107,12 @@ pub trait Heartbeat: Outbound { return; } + let is_validator = self.router().node_type().is_validator(); + // Skip if the node is not requesting peers. + if is_validator && !self.router().allow_outside_peers() { + return; + } + // Retrieve the trusted peers. let trusted = self.router().trusted_peers(); // Retrieve the bootstrap peers. @@ -216,9 +222,12 @@ pub trait Heartbeat: Outbound { for peer_ip in self.router().candidate_peers().into_iter().choose_multiple(rng, num_deficient) { self.router().connect(peer_ip); } - // Request more peers from the connected peers. - for peer_ip in self.router().connected_peers().into_iter().choose_multiple(rng, 3) { - self.send(peer_ip, Message::PeerRequest(PeerRequest)); + let is_validator = self.router().node_type().is_validator(); + if !is_validator || self.router().allow_outside_peers() { + // Request more peers from the connected peers. + for peer_ip in self.router().connected_peers().into_iter().choose_multiple(rng, 3) { + self.send(peer_ip, Message::PeerRequest(PeerRequest)); + } } } } diff --git a/node/router/src/inbound.rs b/node/router/src/inbound.rs index 3f29d28257..a44098042d 100644 --- a/node/router/src/inbound.rs +++ b/node/router/src/inbound.rs @@ -125,6 +125,9 @@ pub trait Inbound: Reading + Outbound { if !self.router().cache.contains_outbound_peer_request(peer_ip) { bail!("Peer '{peer_ip}' is not following the protocol (unexpected peer response)") } + if self.router().node_type().is_validator() && !self.router().allow_outside_peers() { + bail!("Not accepting peer response from '{peer_ip}' (validator gossip is disabled)"); + } match self.peer_response(peer_ip, &message.peers) { true => Ok(()), diff --git a/node/router/src/lib.rs b/node/router/src/lib.rs index 84b00a5031..5bba3cac41 100644 --- a/node/router/src/lib.rs +++ b/node/router/src/lib.rs @@ -95,6 +95,8 @@ pub struct InnerRouter { handles: Mutex>>, /// The boolean flag for the development mode. is_dev: bool, + /// If the flag is set, the node will not engage in P2P gossip to request more peers. + allow_outside_peers: bool, } impl Router { @@ -116,6 +118,7 @@ impl Router { trusted_peers: &[SocketAddr], max_peers: u16, is_dev: bool, + allow_outside_peers: bool, ) -> Result { // Initialize the TCP stack. let tcp = Tcp::new(Config::new(node_ip, max_peers)); @@ -133,6 +136,7 @@ impl Router { restricted_peers: Default::default(), handles: Default::default(), is_dev, + allow_outside_peers, }))) } } @@ -251,6 +255,11 @@ impl Router { self.is_dev } + /// Returns `true` if the node is not engaging in P2P gossip to request more peers. + pub fn allow_outside_peers(&self) -> bool { + self.allow_outside_peers + } + /// Returns the listener IP address from the (ambiguous) peer address. pub fn resolve_to_listener(&self, peer_addr: &SocketAddr) -> Option { self.resolver.get_listener(peer_addr) @@ -295,6 +304,11 @@ impl Router { .unwrap_or(false) } + /// Returns `true` if the given IP is trusted. + pub fn is_trusted(&self, ip: &SocketAddr) -> bool { + self.trusted_peers.contains(ip) + } + /// Returns the maximum number of connected peers. pub fn max_connected_peers(&self) -> usize { self.tcp.config().max_connections as usize diff --git a/node/router/tests/common/mod.rs b/node/router/tests/common/mod.rs index c173d43772..780bcbacac 100644 --- a/node/router/tests/common/mod.rs +++ b/node/router/tests/common/mod.rs @@ -78,6 +78,7 @@ pub async fn client(listening_port: u16, max_peers: u16) -> TestRouter TestRouter TestRouter> Client { trusted_peers, Self::MAXIMUM_NUMBER_OF_PEERS as u16, matches!(storage_mode, StorageMode::Development(_)), + false, ) .await?; // Load the coinbase puzzle. diff --git a/node/src/node.rs b/node/src/node.rs index e6435d6ef6..563f81f152 100644 --- a/node/src/node.rs +++ b/node/src/node.rs @@ -50,6 +50,7 @@ impl Node { genesis: Block, cdn: Option, storage_mode: StorageMode, + allow_outside_peers: bool, ) -> Result { Ok(Self::Validator(Arc::new( Validator::new( @@ -63,6 +64,7 @@ impl Node { genesis, cdn, storage_mode, + allow_outside_peers, ) .await?, ))) diff --git a/node/src/prover/mod.rs b/node/src/prover/mod.rs index 0872a35fd7..de71ead0a3 100644 --- a/node/src/prover/mod.rs +++ b/node/src/prover/mod.rs @@ -110,6 +110,7 @@ impl> Prover { trusted_peers, Self::MAXIMUM_NUMBER_OF_PEERS as u16, matches!(storage_mode, StorageMode::Development(_)), + false, ) .await?; // Load the coinbase puzzle. diff --git a/node/src/validator/mod.rs b/node/src/validator/mod.rs index efb8003f6e..8add855f6f 100644 --- a/node/src/validator/mod.rs +++ b/node/src/validator/mod.rs @@ -83,6 +83,7 @@ impl> Validator { genesis: Block, cdn: Option, storage_mode: StorageMode, + allow_outside_peers: bool, ) -> Result { // Prepare the shutdown flag. let shutdown: Arc = Default::default(); @@ -125,6 +126,7 @@ impl> Validator { trusted_peers, Self::MAXIMUM_NUMBER_OF_PEERS as u16, matches!(storage_mode, StorageMode::Development(_)), + allow_outside_peers, ) .await?; @@ -496,6 +498,7 @@ mod tests { genesis, None, storage_mode, + false, ) .await .unwrap(); diff --git a/node/tests/common/node.rs b/node/tests/common/node.rs index 80c0262903..5ac01167a9 100644 --- a/node/tests/common/node.rs +++ b/node/tests/common/node.rs @@ -59,6 +59,7 @@ pub async fn validator() -> Validator