From 9cd556a4395466eb176b242aaad21b095d1b58801e2f0cc83f83589b9907ebe3 Mon Sep 17 00:00:00 2001 From: Mukan Erkin Date: Fri, 24 Apr 2026 00:00:26 +0300 Subject: [PATCH] feat(nu-p2p): initial Faz 0 scaffold Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 3 +++ CLAUDE.md | 35 +++++++++++++++++++++++++++++++++++ Cargo.toml | 28 ++++++++++++++++++++++++++++ src/behaviour.rs | 13 +++++++++++++ src/config.rs | 20 ++++++++++++++++++++ src/lib.rs | 4 ++++ src/main.rs | 14 ++++++++++++++ src/messages.rs | 19 +++++++++++++++++++ src/peer.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 178 insertions(+) create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 Cargo.toml create mode 100644 src/behaviour.rs create mode 100644 src/config.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/messages.rs create mode 100644 src/peer.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd5c496 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target/ +*.rs.bk +.env diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..aed1c26 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,35 @@ +# nu-p2p — CLAUDE.md + +P2P ağ katmanı. `nu-node`'dan ayrı tutulur — test ederken network mock'lanabilir. + +## Stack + +- `libp2p 0.54` — Gossipsub, Kademlia DHT, Identify, Ping +- Tokio async runtime + +## Modüller + +| Dosya | Sorumluluk | +|-------|-----------| +| `behaviour.rs` | libp2p `NetworkBehaviour` kompozisyonu | +| `messages.rs` | Gossip topic sabitleri + mesaj tipleri | +| `peer.rs` | PeerRegistry (max 50 bağlantı) | +| `config.rs` | Listen addr, bootstrap peers, max peers | + +## Gossip Topics + +``` +nu/blocks/1 → BlockAnnounce, BlockRequest, BlockResponse +nu/txs/1 → TxGossip +nu/votes/1 → VoteAnnounce +nu/validators/1 → ValidatorHeartbeat +``` + +## Geliştirme + +```bash +cargo run --bin nu-p2p + +# Faz 1: iki node arası gossip testi +RUST_LOG=debug cargo run --bin nu-p2p -- --bootstrap /ip4/127.0.0.1/tcp/30333 +``` diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a9ed51a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "nu-p2p" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "nu-p2p" +path = "src/main.rs" + +[dependencies] +tokio = { version = "1", features = ["full"] } +libp2p = { version = "0.54", features = [ + "tokio", + "tcp", + "noise", + "yamux", + "gossipsub", + "kad", + "identify", + "ping", +] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +anyhow = "1" +thiserror = "1" +tracing = "1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +futures = "0.3" diff --git a/src/behaviour.rs b/src/behaviour.rs new file mode 100644 index 0000000..ac07f63 --- /dev/null +++ b/src/behaviour.rs @@ -0,0 +1,13 @@ +// libp2p combined behaviour: Gossipsub + Kademlia DHT + Identify + Ping +// Full implementation in Faz 1; scaffold defines the composited behaviour struct. + +use libp2p::{gossipsub, identify, kad, ping}; +use libp2p::swarm::NetworkBehaviour; + +#[derive(NetworkBehaviour)] +pub struct NuBehaviour { + pub gossipsub: gossipsub::Behaviour, + pub kademlia: kad::Behaviour, + pub identify: identify::Behaviour, + pub ping: ping::Behaviour, +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..0d9b78b --- /dev/null +++ b/src/config.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Serialize}; + +pub const MAX_PEERS: usize = 50; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct P2pConfig { + pub listen_addr: String, // e.g. "/ip4/0.0.0.0/tcp/30333" + pub bootstrap_peers: Vec, // multiaddrs from genesis.toml + pub max_peers: usize, +} + +impl Default for P2pConfig { + fn default() -> Self { + Self { + listen_addr: "/ip4/0.0.0.0/tcp/30333".into(), + bootstrap_peers: vec![], + max_peers: MAX_PEERS, + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..fd4930c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,4 @@ +pub mod behaviour; +pub mod messages; +pub mod peer; +pub mod config; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..09443d5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,14 @@ +use anyhow::Result; +use tracing_subscriber::EnvFilter; + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .init(); + + tracing::info!("nu-p2p starting..."); + + // TODO Faz 1: build libp2p swarm with NuBehaviour, connect bootstrap peers, run event loop + Ok(()) +} diff --git a/src/messages.rs b/src/messages.rs new file mode 100644 index 0000000..1f5c4de --- /dev/null +++ b/src/messages.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +/// All gossip topics on the network. +pub const TOPIC_BLOCKS: &str = "nu/blocks/1"; +pub const TOPIC_TXS: &str = "nu/txs/1"; +pub const TOPIC_VOTES: &str = "nu/votes/1"; +pub const TOPIC_VALIDATORS: &str = "nu/validators/1"; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", content = "data")] +pub enum NetworkMessage { + BlockAnnounce { height: u64, hash: String }, + BlockRequest { height: u64 }, + BlockResponse { raw_block: Vec }, + TxGossip { raw_tx: Vec }, + PeerExchange { peers: Vec }, // multiaddrs + ValidatorHeartbeat { address: String, slot: u32 }, + VoteAnnounce { node_id: String, voter: String, approve: bool }, +} diff --git a/src/peer.rs b/src/peer.rs new file mode 100644 index 0000000..a370a47 --- /dev/null +++ b/src/peer.rs @@ -0,0 +1,42 @@ +use std::collections::HashSet; + +#[derive(Debug, Clone)] +pub struct PeerInfo { + pub peer_id: String, + pub addr: String, + pub is_validator: bool, +} + +pub struct PeerRegistry { + peers: Vec, + max: usize, +} + +impl PeerRegistry { + pub fn new(max: usize) -> Self { + Self { peers: vec![], max } + } + + pub fn add(&mut self, peer: PeerInfo) -> bool { + if self.peers.len() >= self.max { + return false; + } + if !self.peers.iter().any(|p| p.peer_id == peer.peer_id) { + self.peers.push(peer); + return true; + } + false + } + + pub fn remove(&mut self, peer_id: &str) { + self.peers.retain(|p| p.peer_id != peer_id); + } + + pub fn count(&self) -> usize { + self.peers.len() + } + + pub fn validator_peers(&self) -> Vec<&PeerInfo> { + self.peers.iter().filter(|p| p.is_validator).collect() + } +}