chat_test_peer/
main.rs

1use chat_core::coordinator::CoordinatorEvent;
2use network_libp2p::NodeOptions;
3use clap::Parser;
4use futures::StreamExt;
5use std::time::Duration;
6
7#[derive(Parser, Debug)]
8#[command(name = "chat-test-peer")]
9#[command(about = "Minimal test peer for P2Pandemonium E2E tests")]
10struct Cli {
11    /// Room to join
12    #[arg(short, long, default_value = "e2e-test-room")]
13    room: String,
14
15    /// WebSocket listen address
16    #[arg(short, long, default_value = "/ip4/127.0.0.1/tcp/40233/ws")]
17    listen: String,
18
19    /// Bootstrap peers (comma-separated)
20    #[arg(short, long)]
21    bootstrap: Option<String>,
22}
23
24#[tokio::main]
25async fn main() -> anyhow::Result<()> {
26    tracing_subscriber::fmt()
27        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
28        .init();
29
30    let cli = Cli::parse();
31
32    let bootstrap_peers = cli
33        .bootstrap
34        .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
35        .unwrap_or_default();
36
37    let options = NodeOptions {
38        listen_addresses: vec![cli.listen.clone()],
39        bootstrap_peers,
40        max_connections: 100,
41        dht_client_mode: false, // server mode so it can answer DHT queries
42        dht_discovery_enabled: true, // enable so it stores/returns provider records
43        stun_servers: None,
44    };
45
46    // Use a deterministic keypair so the peer ID is stable across runs.
47    // This lets E2E tests hardcode the bootstrap address.
48    let mut seed: [u8; 32] = [
49        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
50        17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
51    ];
52    let secret = libp2p_identity::ed25519::SecretKey::try_from_bytes(&mut seed)?;
53    let keypair = libp2p_identity::Keypair::from(libp2p_identity::ed25519::Keypair::from(secret));
54    let local_peer_id = libp2p::PeerId::from(keypair.public());
55    tracing::info!("Test peer deterministic ID: {}", local_peer_id);
56
57    let mut node = network_libp2p::native::build_node_with_keypair(
58        options, keypair.clone(),
59    ).await.map_err(|e| anyhow::anyhow!(e))?;
60    tracing::info!("Test peer started: {}", local_peer_id);
61
62    // --- Relay server swarm (separate from chat node) ---
63    // Browser peers need a relay server to obtain dialable /p2p-circuit
64    // addresses because libp2p-webrtc-websys does not support listen_on.
65    // Use a *different* deterministic keypair so the relay server has its
66    // own peer ID and does not conflict with the chat node.
67    let mut relay_seed: [u8; 32] = [
68        32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17,
69        16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
70    ];
71    let relay_secret = libp2p_identity::ed25519::SecretKey::try_from_bytes(&mut relay_seed)?;
72    let relay_keypair = libp2p_identity::Keypair::from(libp2p_identity::ed25519::Keypair::from(relay_secret));
73    let relay_peer_id = libp2p::PeerId::from(relay_keypair.public());
74    tracing::info!("Relay server deterministic peer ID: {}", relay_peer_id);
75    let relay_listen: libp2p::Multiaddr = "/ip4/127.0.0.1/tcp/40234/ws".parse()?;
76    let mut relay_swarm = build_relay_swarm(&relay_keypair, relay_peer_id).await?;
77    relay_swarm.listen_on(relay_listen.clone())?;
78    relay_swarm.add_external_address(relay_listen.clone());
79    tracing::info!("Relay server listening on: {}", relay_listen);
80
81    tokio::spawn(async move {
82        use futures::StreamExt;
83        while let Some(event) = relay_swarm.next().await {
84            tracing::info!("[relay-server] event: {:?}", event);
85        }
86    });
87
88    // Poll briefly to obtain listen address
89    let mut listen_addrs = Vec::new();
90    let timeout = tokio::time::Duration::from_secs(5);
91    let deadline = tokio::time::Instant::now() + timeout;
92
93    while tokio::time::Instant::now() < deadline {
94        tokio::select! {
95            event = node.swarm.select_next_some() => {
96                if let libp2p::swarm::SwarmEvent::NewListenAddr { address, .. } = event {
97                    tracing::info!("Listening on: {}", address);
98                    listen_addrs.push(address);
99                }
100            }
101            _ = tokio::time::sleep(Duration::from_millis(10)) => {}
102        }
103        if !listen_addrs.is_empty() {
104            break;
105        }
106    }
107
108    let conn = rusqlite::Connection::open_in_memory()?;
109    let storage = chat_core::storage::SqliteStorage::new(conn);
110    storage.init_schema()?;
111
112    let (handle, mut events, loop_fut) = chat_core::coordinator::build(node, storage);
113
114    tokio::spawn(loop_fut.run());
115
116    // Log events
117    tokio::spawn(async move {
118        while let Ok(event) = events.recv().await {
119            match event {
120                CoordinatorEvent::SystemMessage(msg) => {
121                    tracing::info!("[coord] {}", msg);
122                }
123                CoordinatorEvent::ErrorMessage(msg) => {
124                    tracing::error!("[coord] {}", msg);
125                }
126                CoordinatorEvent::PeerConnected { peer_id } => {
127                    tracing::info!("[coord] Peer connected: {}", peer_id);
128                }
129                CoordinatorEvent::PeerDisconnected { peer_id } => {
130                    tracing::info!("[coord] Peer disconnected: {}", peer_id);
131                }
132                _ => {}
133            }
134        }
135    });
136
137    handle.join_room(cli.room.clone()).await;
138    tracing::info!("Joined room: {}", cli.room);
139    println!("TEST_PEER_READY room={} peer={}", cli.room, local_peer_id);
140
141    // Print the multiaddr that browser tabs should dial
142    for addr in &listen_addrs {
143        let dialable = addr.clone().with(libp2p::multiaddr::Protocol::P2p(local_peer_id));
144        println!("TEST_PEER_MULTIADDR={}", dialable);
145        tracing::info!("Browser tabs can dial: {}", dialable);
146    }
147
148    // Print the relay server multiaddr
149    let relay_dialable = relay_listen.clone().with(libp2p::multiaddr::Protocol::P2p(relay_peer_id));
150    println!("RELAY_SERVER_MULTIADDR={}", relay_dialable);
151    tracing::info!("Relay server dialable: {}", relay_dialable);
152
153    // Keep running
154    tokio::signal::ctrl_c().await?;
155    handle.shutdown().await;
156    Ok(())
157}
158
159/// Minimal behaviour for a standalone relay server.
160#[derive(libp2p::swarm::NetworkBehaviour)]
161#[behaviour(prelude = "libp2p_swarm::derive_prelude")]
162struct RelayBehaviour {
163    relay: libp2p::relay::Behaviour,
164    identify: libp2p::identify::Behaviour,
165}
166
167/// Build a minimal libp2p swarm that only acts as a circuit-relay v2 server.
168async fn build_relay_swarm(
169    keypair: &libp2p_identity::Keypair,
170    local_peer_id: libp2p::PeerId,
171) -> anyhow::Result<libp2p::swarm::Swarm<RelayBehaviour>> {
172    let builder = libp2p::SwarmBuilder::with_existing_identity(keypair.clone())
173        .with_tokio()
174        .with_websocket(
175            libp2p::noise::Config::new,
176            libp2p::yamux::Config::default,
177        )
178        .await?;
179
180    let swarm = builder
181        .with_behaviour(|_keypair| {
182            let relay = libp2p::relay::Behaviour::new(
183                local_peer_id,
184                libp2p::relay::Config::default(),
185            );
186            let identify = libp2p::identify::Behaviour::new(
187                libp2p::identify::Config::new(
188                    "/p2pandemonium/identify/1.0.0".into(),
189                    keypair.public(),
190                ),
191            );
192            RelayBehaviour { relay, identify }
193        })?
194        .with_swarm_config(|cfg| {
195            cfg.with_idle_connection_timeout(Duration::from_secs(60))
196        })
197        .build();
198
199    Ok(swarm)
200}
201
202#[cfg(test)]
203mod tests {
204    #[test]
205    fn print_deterministic_peer_id() {
206        let mut seed: [u8; 32] = [
207            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
208            17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
209        ];
210        let secret = libp2p_identity::ed25519::SecretKey::try_from_bytes(&mut seed).unwrap();
211        let keypair = libp2p_identity::Keypair::from(libp2p_identity::ed25519::Keypair::from(secret));
212        let peer_id = libp2p::PeerId::from(keypair.public());
213        println!("DETERMINISTIC_PEER_ID={}", peer_id);
214    }
215}