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
504 lines
17 KiB
Rust
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,
|
|
}
|
|
}
|
|
}
|