synor/apps/synord/src/services/network.rs
Gulshan Yadav 48949ebb3f Initial commit: Synor blockchain monorepo
A complete blockchain implementation featuring:
- synord: Full node with GHOSTDAG consensus
- explorer-web: Modern React blockchain explorer with 3D DAG visualization
- CLI wallet and tools
- Smart contract SDK and example contracts (DEX, NFT, token)
- WASM crypto library for browser/mobile
2026-01-08 05:22:17 +05:30

504 lines
17 KiB
Rust

//! Network service.
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use libp2p::{Multiaddr, PeerId};
use tokio::sync::{broadcast, RwLock};
use tracing::{debug, error, info, warn};
use synor_network::{
BlockAnnouncement, ChainId, NetworkConfig, NetworkEvent, NetworkHandle,
NetworkService as SynorNetworkService, SyncStatus, TransactionAnnouncement,
};
use synor_types::{BlockHeader, BlockId};
use crate::config::NodeConfig;
/// Peer connection info.
#[derive(Clone, Debug)]
pub struct PeerInfo {
/// Peer ID.
pub id: String,
/// Remote address.
pub address: Option<SocketAddr>,
/// Is inbound connection.
pub inbound: bool,
/// Protocol version.
pub version: u32,
/// User agent.
pub user_agent: String,
/// Last seen timestamp.
pub last_seen: u64,
/// Ping latency in ms.
pub latency_ms: u32,
/// Is syncing.
pub syncing: bool,
}
/// Network message types.
#[derive(Clone, Debug)]
pub enum NetworkMessage {
/// Block announcement.
BlockAnnounce { hash: [u8; 32] },
/// Transaction announcement.
TxAnnounce { hash: [u8; 32] },
/// Block request.
GetBlocks { hashes: Vec<[u8; 32]> },
/// Block response.
Blocks { data: Vec<Vec<u8>> },
/// Headers request.
GetHeaders { locator: Vec<[u8; 32]>, stop: [u8; 32] },
/// Headers response.
Headers { headers: Vec<Vec<u8>> },
}
/// Network service manages P2P connections.
pub struct NetworkService {
/// Network handle from synor-network (interior mutability for start()).
handle: RwLock<Option<NetworkHandle>>,
/// Configuration.
listen_addr: String,
/// Seed nodes.
seeds: Vec<String>,
/// Maximum inbound connections.
#[allow(dead_code)]
max_inbound: usize,
/// Maximum outbound connections.
#[allow(dead_code)]
max_outbound: usize,
/// Connected peers (cached locally).
peers: RwLock<HashMap<String, PeerInfo>>,
/// Is running.
running: RwLock<bool>,
/// Shutdown sender for the network task.
#[allow(dead_code)]
shutdown_tx: Option<broadcast::Sender<()>>,
/// Shutdown receiver.
#[allow(dead_code)]
shutdown_rx: RwLock<Option<broadcast::Receiver<()>>>,
/// Message broadcast channel.
message_tx: broadcast::Sender<(String, NetworkMessage)>,
/// Network configuration for synor-network.
network_config: NetworkConfig,
}
impl NetworkService {
/// Creates a new network service.
pub async fn new(
config: &NodeConfig,
shutdown_rx: broadcast::Receiver<()>,
) -> anyhow::Result<Self> {
let (message_tx, _) = broadcast::channel(1000);
let (shutdown_tx, _) = broadcast::channel(1);
// Build synor-network configuration from node config
let chain_id = match config.network.as_str() {
"mainnet" => ChainId::Mainnet,
"testnet" => ChainId::Testnet,
_ => ChainId::Devnet,
};
// Parse listen address
let listen_addr_parsed: Multiaddr = config
.p2p
.listen_addr
.parse()
.unwrap_or_else(|_| format!("/ip4/0.0.0.0/tcp/{}", synor_network::DEFAULT_PORT).parse().unwrap());
// Parse seed/bootstrap peers
let bootstrap_peers: Vec<Multiaddr> = config
.p2p
.seeds
.iter()
.filter_map(|s| s.parse().ok())
.collect();
let network_config = NetworkConfig {
chain_id,
listen_addresses: vec![listen_addr_parsed],
bootstrap_peers,
max_inbound: config.p2p.max_inbound,
max_outbound: config.p2p.max_outbound,
enable_mdns: config.network == "devnet",
enable_kad: config.network != "devnet",
idle_timeout: Duration::from_secs(30),
ping_interval: Duration::from_secs(15),
gossipsub: synor_network::config::GossipsubConfig::default(),
sync: synor_network::config::SyncConfig::default(),
external_address: None,
node_name: Some(format!("synord-{}", &config.network)),
};
Ok(NetworkService {
handle: RwLock::new(None),
listen_addr: config.p2p.listen_addr.clone(),
seeds: config.p2p.seeds.clone(),
max_inbound: config.p2p.max_inbound,
max_outbound: config.p2p.max_outbound,
peers: RwLock::new(HashMap::new()),
running: RwLock::new(false),
shutdown_tx: Some(shutdown_tx),
shutdown_rx: RwLock::new(Some(shutdown_rx)),
message_tx,
network_config,
})
}
/// Starts the network service.
pub async fn start(&self) -> anyhow::Result<()> {
info!(addr = %self.listen_addr, "Starting network service");
// Create the synor-network service
let (network_service, handle) = SynorNetworkService::new(self.network_config.clone())
.await
.map_err(|e| anyhow::anyhow!("Failed to create network service: {}", e))?;
// Store the handle
*self.handle.write().await = Some(handle.clone());
// Subscribe to network events
let mut event_rx = handle.subscribe();
let message_tx = self.message_tx.clone();
let peers = Arc::new(RwLock::new(HashMap::<String, PeerInfo>::new()));
let peers_clone = peers.clone();
// Spawn event handler
tokio::spawn(async move {
while let Ok(event) = event_rx.recv().await {
match event {
NetworkEvent::NewBlock(announcement) => {
debug!("Received block announcement: {}", announcement.hash);
let msg = NetworkMessage::BlockAnnounce {
hash: *announcement.hash.as_bytes(),
};
let _ = message_tx.send(("network".to_string(), msg));
}
NetworkEvent::NewTransaction(announcement) => {
debug!("Received transaction announcement: {}", announcement.txid);
let msg = NetworkMessage::TxAnnounce {
hash: *announcement.txid.as_bytes(),
};
let _ = message_tx.send(("network".to_string(), msg));
}
NetworkEvent::PeerConnected(peer_id) => {
info!("Peer connected: {}", peer_id);
let info = PeerInfo {
id: peer_id.to_string(),
address: None,
inbound: false,
version: 1,
user_agent: String::new(),
last_seen: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
latency_ms: 0,
syncing: false,
};
peers_clone.write().await.insert(peer_id.to_string(), info);
}
NetworkEvent::PeerDisconnected(peer_id) => {
info!("Peer disconnected: {}", peer_id);
peers_clone.write().await.remove(&peer_id.to_string());
}
NetworkEvent::SyncStatusChanged(status) => {
info!("Sync status changed: {:?}", status);
}
NetworkEvent::BlocksReceived(blocks) => {
debug!("Received {} blocks", blocks.len());
}
NetworkEvent::HeadersReceived(headers) => {
debug!("Received {} headers", headers.len());
}
}
}
});
// Spawn the network service runner
tokio::spawn(async move {
if let Err(e) = network_service.run().await {
error!("Network service error: {}", e);
}
});
*self.running.write().await = true;
// Connect to seed nodes
let handle_guard = self.handle.read().await;
if let Some(ref handle) = *handle_guard {
for seed in &self.seeds {
if let Ok(addr) = seed.parse::<Multiaddr>() {
info!(seed = %seed, "Connecting to seed node");
if let Err(e) = handle.dial(addr).await {
warn!("Failed to connect to seed node {}: {}", seed, e);
}
}
}
}
info!("Network service started");
Ok(())
}
/// Stops the network service.
pub async fn stop(&self) -> anyhow::Result<()> {
info!("Stopping network service");
*self.running.write().await = false;
// Shutdown the network service via handle
let handle_guard = self.handle.read().await;
if let Some(ref handle) = *handle_guard {
if let Err(e) = handle.shutdown().await {
warn!("Error during network shutdown: {}", e);
}
}
// Clear peers
self.peers.write().await.clear();
info!("Network service stopped");
Ok(())
}
/// Returns the number of connected peers.
pub async fn peer_count(&self) -> usize {
let handle_guard = self.handle.read().await;
if let Some(ref handle) = *handle_guard {
handle.peer_count().await.unwrap_or(0)
} else {
self.peers.read().await.len()
}
}
/// Returns all peer info.
pub async fn peers(&self) -> Vec<PeerInfo> {
self.peers.read().await.values().cloned().collect()
}
/// Gets a specific peer.
pub async fn get_peer(&self, id: &str) -> Option<PeerInfo> {
self.peers.read().await.get(id).cloned()
}
/// Connects to a peer.
pub async fn connect_peer(&self, address: &str) -> anyhow::Result<String> {
info!(address = %address, "Connecting to peer");
let handle_guard = self.handle.read().await;
if let Some(ref handle) = *handle_guard {
let addr: Multiaddr = address
.parse()
.map_err(|e| anyhow::anyhow!("Invalid address: {}", e))?;
handle
.dial(addr)
.await
.map_err(|e| anyhow::anyhow!("Failed to dial: {}", e))?;
Ok(format!("dialing-{}", address))
} else {
Err(anyhow::anyhow!("Network service not started"))
}
}
/// Disconnects a peer.
pub async fn disconnect_peer(&self, id: &str) {
info!(peer = %id, "Disconnecting peer");
let handle_guard = self.handle.read().await;
if let Some(ref handle) = *handle_guard {
if let Ok(peer_id) = id.parse::<PeerId>() {
if let Err(e) = handle.disconnect(peer_id).await {
warn!("Failed to disconnect peer {}: {}", id, e);
}
}
}
self.peers.write().await.remove(id);
}
/// Bans a peer.
pub async fn ban_peer(&self, id: &str, reason: &str) {
warn!(peer = %id, reason = %reason, "Banning peer");
let handle_guard = self.handle.read().await;
if let Some(ref handle) = *handle_guard {
if let Ok(peer_id) = id.parse::<PeerId>() {
if let Err(e) = handle.ban(peer_id).await {
warn!("Failed to ban peer {}: {}", id, e);
}
}
}
self.disconnect_peer(id).await;
}
/// Broadcasts a message to all peers.
pub async fn broadcast(&self, message: NetworkMessage) {
let handle_guard = self.handle.read().await;
if let Some(ref handle) = *handle_guard {
match message {
NetworkMessage::BlockAnnounce { hash } => {
// Create a minimal BlockAnnouncement
// In practice, you'd get this from the actual block
let header = BlockHeader::default();
let announcement = BlockAnnouncement::new(header, 0, 0);
// Note: The hash from the message won't match the header hash
// This is a placeholder - real implementation should use actual block data
let _ = hash; // Suppress unused warning
if let Err(e) = handle.broadcast_block(announcement).await {
warn!("Failed to broadcast block: {}", e);
}
}
NetworkMessage::TxAnnounce { hash } => {
let announcement = TransactionAnnouncement::id_only(
synor_types::TransactionId::from_bytes(hash)
);
if let Err(e) = handle.broadcast_transaction(announcement).await {
warn!("Failed to broadcast transaction: {}", e);
}
}
_ => {
debug!("Broadcast not implemented for this message type");
}
}
}
}
/// Sends a message to a specific peer.
pub async fn send(&self, peer_id: &str, _message: NetworkMessage) -> anyhow::Result<()> {
debug!(peer = %peer_id, "Sending message");
// For now, direct sends would be handled via request/response
// This would need to be implemented based on the message type
Ok(())
}
/// Subscribes to network messages.
pub fn subscribe(&self) -> broadcast::Receiver<(String, NetworkMessage)> {
self.message_tx.subscribe()
}
/// Returns the network handle for advanced operations.
pub async fn handle(&self) -> Option<NetworkHandle> {
self.handle.read().await.clone()
}
/// Announces a new block.
pub async fn announce_block(&self, hash: [u8; 32]) {
self.broadcast(NetworkMessage::BlockAnnounce { hash }).await;
}
/// Announces a new transaction.
pub async fn announce_tx(&self, hash: [u8; 32]) {
self.broadcast(NetworkMessage::TxAnnounce { hash }).await;
}
/// Requests blocks from a peer.
pub async fn request_blocks(
&self,
peer_id: &str,
hashes: Vec<[u8; 32]>,
) -> anyhow::Result<()> {
let handle_guard = self.handle.read().await;
if let Some(ref handle) = *handle_guard {
let peer: PeerId = peer_id
.parse()
.map_err(|_| anyhow::anyhow!("Invalid peer ID"))?;
let block_ids: Vec<BlockId> = hashes.iter().map(|h| BlockId::from_bytes(*h)).collect();
handle
.request_blocks(peer, block_ids)
.await
.map_err(|e| anyhow::anyhow!("Request failed: {}", e))?;
}
Ok(())
}
/// Requests headers from a peer.
pub async fn request_headers(
&self,
peer_id: &str,
locator: Vec<[u8; 32]>,
_stop: [u8; 32],
) -> anyhow::Result<()> {
let handle_guard = self.handle.read().await;
if let Some(ref handle) = *handle_guard {
let peer: PeerId = peer_id
.parse()
.map_err(|_| anyhow::anyhow!("Invalid peer ID"))?;
let start = if locator.is_empty() {
BlockId::from_bytes([0u8; 32])
} else {
BlockId::from_bytes(locator[0])
};
handle
.request_headers(peer, start, 500)
.await
.map_err(|e| anyhow::anyhow!("Request failed: {}", e))?;
}
Ok(())
}
/// Gets the sync status.
pub async fn sync_status(&self) -> Option<SyncStatus> {
let handle_guard = self.handle.read().await;
if let Some(ref handle) = *handle_guard {
handle.sync_status().await.ok()
} else {
None
}
}
/// Starts synchronization.
pub async fn start_sync(&self) -> anyhow::Result<()> {
let handle_guard = self.handle.read().await;
if let Some(ref handle) = *handle_guard {
handle
.start_sync()
.await
.map_err(|e| anyhow::anyhow!("Failed to start sync: {}", e))?;
}
Ok(())
}
}
/// Network statistics.
#[derive(Clone, Debug)]
pub struct NetworkStats {
pub total_peers: usize,
pub inbound_peers: usize,
pub outbound_peers: usize,
pub bytes_sent: u64,
pub bytes_received: u64,
pub messages_sent: u64,
pub messages_received: u64,
}
impl NetworkService {
/// Gets network statistics.
pub async fn stats(&self) -> NetworkStats {
let peers = self.peers.read().await;
let inbound = peers.values().filter(|p| p.inbound).count();
NetworkStats {
total_peers: peers.len(),
inbound_peers: inbound,
outbound_peers: peers.len() - inbound,
bytes_sent: 0,
bytes_received: 0,
messages_sent: 0,
messages_received: 0,
}
}
}