feat(nu-p2p): initial Faz 0 scaffold

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mukan Erkin TÖRÜK 2026-04-24 00:00:26 +03:00
commit 9cd556a439
9 changed files with 178 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
target/
*.rs.bk
.env

35
CLAUDE.md Normal file
View file

@ -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
```

28
Cargo.toml Normal file
View file

@ -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"

13
src/behaviour.rs Normal file
View file

@ -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<kad::store::MemoryStore>,
pub identify: identify::Behaviour,
pub ping: ping::Behaviour,
}

20
src/config.rs Normal file
View file

@ -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<String>, // 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,
}
}
}

4
src/lib.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod behaviour;
pub mod messages;
pub mod peer;
pub mod config;

14
src/main.rs Normal file
View file

@ -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(())
}

19
src/messages.rs Normal file
View file

@ -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<u8> },
TxGossip { raw_tx: Vec<u8> },
PeerExchange { peers: Vec<String> }, // multiaddrs
ValidatorHeartbeat { address: String, slot: u32 },
VoteAnnounce { node_id: String, voter: String, approve: bool },
}

42
src/peer.rs Normal file
View file

@ -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<PeerInfo>,
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()
}
}