diff --git a/.devnet/start.sh b/.devnet/start.sh index f4c635892b..77294f080c 100755 --- a/.devnet/start.sh +++ b/.devnet/start.sh @@ -37,7 +37,7 @@ start_snarkos_in_tmux() { tmux new-session -d -s snarkos-session # Send the snarkOS start command to the tmux session with the NODE_ID - tmux send-keys -t "snarkos-session" "snarkos start --nodisplay --bft 0.0.0.0:5000 --rest 0.0.0.0:3030 --peers $NODE_IP:4130 --validators $NODE_IP:5000 --rest-rps 1000 --verbosity $VERBOSITY --dev $NODE_ID --dev-num-validators $NUM_INSTANCES --validator --metrics" C-m + tmux send-keys -t "snarkos-session" "snarkos start --nodisplay --bft 0.0.0.0:5000 --rest 0.0.0.0:3030 --allow-external-peers --peers $NODE_IP:4130 --validators $NODE_IP:5000 --rest-rps 1000 --verbosity $VERBOSITY --dev $NODE_ID --dev-num-validators $NUM_INSTANCES --validator --metrics" C-m exit # Exit root user EOF diff --git a/cli/src/commands/start.rs b/cli/src/commands/start.rs index e0b149c6bd..f618c66e04 100644 --- a/cli/src/commands/start.rs +++ b/cli/src/commands/start.rs @@ -144,9 +144,13 @@ pub struct Start { #[clap(long = "storage_path")] pub storage_path: Option, - #[clap(long)] /// If development mode is enabled, specify the custom bonded balances as a json object. (default: None) + #[clap(long)] dev_bonded_balances: Option, + + /// If the flag is set, the validator will allow untrusted peers to connect + #[clap(long = "allow-external-peers")] + allow_external_peers: bool, } impl Start { @@ -542,7 +546,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, dev_txs).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_external_peers, dev_txs).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/devnet.sh b/devnet.sh index e91627335a..ce2ac870a4 100755 --- a/devnet.sh +++ b/devnet.sh @@ -64,12 +64,12 @@ for validator_index in "${validator_indices[@]}"; do # Send the command to start the validator to the new window and capture output to the log file if [ "$validator_index" -eq 0 ]; then - tmux send-keys -t "devnet:window$validator_index" "snarkos start --nodisplay --dev $validator_index --dev-num-validators $total_validators --validator --logfile $log_file --metrics" C-m + tmux send-keys -t "devnet:window$validator_index" "snarkos start --nodisplay --dev $validator_index --allow-external-peers --dev-num-validators $total_validators --validator --logfile $log_file --metrics" C-m else # Create a new window with a unique name window_index=$((validator_index + index_offset)) tmux new-window -t "devnet:$window_index" -n "window$validator_index" - tmux send-keys -t "devnet:window$validator_index" "snarkos start --nodisplay --dev $validator_index --dev-num-validators $total_validators --validator --logfile $log_file" C-m + tmux send-keys -t "devnet:window$validator_index" "snarkos start --nodisplay --dev $validator_index --allow-external-peers --dev-num-validators $total_validators --validator --logfile $log_file" C-m fi done diff --git a/node/router/src/handshake.rs b/node/router/src/handshake.rs index 195298eaea..57198692d4 100644 --- a/node/router/src/handshake.rs +++ b/node/router/src/handshake.rs @@ -264,6 +264,10 @@ impl Router { if self.is_connected(&peer_ip) { bail!("Dropping connection request from '{peer_ip}' (already connected)") } + // Only allow trusted peers to connect if allow_external_peers is set + if !self.allow_external_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..ae84ba21aa 100644 --- a/node/router/src/heartbeat.rs +++ b/node/router/src/heartbeat.rs @@ -107,6 +107,11 @@ pub trait Heartbeat: Outbound { return; } + // Skip if the node is not requesting peers. + if !self.router().allow_external_peers() { + return; + } + // Retrieve the trusted peers. let trusted = self.router().trusted_peers(); // Retrieve the bootstrap peers. @@ -216,9 +221,11 @@ 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)); + if self.router().allow_external_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 f57933fbea..e99072dfc2 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().allow_external_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..748870deda 100644 --- a/node/router/src/lib.rs +++ b/node/router/src/lib.rs @@ -93,6 +93,8 @@ pub struct InnerRouter { restricted_peers: RwLock>, /// The spawned handles. handles: Mutex>>, + /// If the flag is set, the node will engage in P2P gossip to request more peers. + allow_external_peers: bool, /// The boolean flag for the development mode. is_dev: bool, } @@ -115,6 +117,7 @@ impl Router { account: Account, trusted_peers: &[SocketAddr], max_peers: u16, + allow_external_peers: bool, is_dev: bool, ) -> Result { // Initialize the TCP stack. @@ -132,6 +135,7 @@ impl Router { candidate_peers: Default::default(), restricted_peers: Default::default(), handles: Default::default(), + allow_external_peers, is_dev, }))) } @@ -251,6 +255,11 @@ impl Router { self.is_dev } + /// Returns `true` if the node is engaging in P2P gossip to request more peers. + pub fn allow_external_peers(&self) -> bool { + self.allow_external_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..7aa86c0546 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 { let ledger_service = Arc::new(CoreLedgerService::::new(ledger.clone(), shutdown.clone())); // Initialize the sync module. let sync = BlockSync::new(BlockSyncMode::Router, ledger_service.clone()); + // Determine if the client should allow external peers. + let allow_external_peers = true; // Initialize the node router. let router = Router::new( @@ -116,6 +118,7 @@ impl> Client { account, trusted_peers, Self::MAXIMUM_NUMBER_OF_PEERS as u16, + allow_external_peers, matches!(storage_mode, StorageMode::Development(_)), ) .await?; diff --git a/node/src/node.rs b/node/src/node.rs index d98376d384..9c4ce40299 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_external_peers: bool, dev_txs: bool, ) -> Result { Ok(Self::Validator(Arc::new( @@ -64,6 +65,7 @@ impl Node { genesis, cdn, storage_mode, + allow_external_peers, dev_txs, ) .await?, diff --git a/node/src/prover/mod.rs b/node/src/prover/mod.rs index 0872a35fd7..30b5c04a6d 100644 --- a/node/src/prover/mod.rs +++ b/node/src/prover/mod.rs @@ -101,6 +101,8 @@ impl> Prover { let ledger_service = Arc::new(ProverLedgerService::new()); // Initialize the sync module. let sync = BlockSync::new(BlockSyncMode::Router, ledger_service.clone()); + // Determine if the prover should allow external peers. + let allow_external_peers = true; // Initialize the node router. let router = Router::new( @@ -109,6 +111,7 @@ impl> Prover { account, trusted_peers, Self::MAXIMUM_NUMBER_OF_PEERS as u16, + allow_external_peers, matches!(storage_mode, StorageMode::Development(_)), ) .await?; diff --git a/node/src/validator/mod.rs b/node/src/validator/mod.rs index 1477a51b25..643254f2f9 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_external_peers: bool, dev_txs: bool, ) -> Result { // Prepare the shutdown flag. @@ -125,6 +126,7 @@ impl> Validator { account, trusted_peers, Self::MAXIMUM_NUMBER_OF_PEERS as u16, + allow_external_peers, matches!(storage_mode, StorageMode::Development(_)), ) .await?; @@ -498,6 +500,7 @@ mod tests { genesis, None, storage_mode, + false, dev_txs, ) .await diff --git a/node/tests/common/node.rs b/node/tests/common/node.rs index 81630cc9da..503a6f867e 100644 --- a/node/tests/common/node.rs +++ b/node/tests/common/node.rs @@ -59,6 +59,7 @@ pub async fn validator() -> Validator