feat(brahman-handshake): Fase 1 — handshake brahman sobre stream libp2p
Segundo paso del plan "el encuentro entre Entes no se restringe a
local". El protocolo brahman (Hello / HelloAck / Ping / Pong /
MatchEvent / Farewell, frames postcard length-prefixed) ahora tambien
viaja sobre streams libp2p de la malla brahman-net — el mismo Init
acepta sesiones por Unix socket Y por libp2p indistintamente, y un
consumer remoto puede dial-ar al multiaddr y completar handshake.
Cambios:
- Session<S> y Client<S> genericos: ambos dejan de estar atados a
UnixStream y pasan a ser genericos sobre S: AsyncRead + AsyncWrite
+ Unpin + Send + 'static. El path Unix queda como
Client = Client<UnixStream> (default generico). Constructores
nuevos: Server::session_from_stream(stream),
Client::connect_with_stream(stream, card, wit).
- Refactor del post-handshake con split: tokio::select! sobre
&mut self.stream requeria S: Sync indirectamente, y libp2p::Stream
no es Sync. Reemplazado por tokio::io::split(stream) -> reader loop
principal + writer task separada que drena el push channel. Writer
compartido bajo Arc<Mutex<WriteHalf<S>>> para serializar Pong/Error
inline con los MatchEvents pusheados. Cleanup garantizado en todas
las ramas. La logica del post-handshake migra a funciones libres
(run_post_handshake, handle_inbound_frame, cleanup,
broadcast_match_diffs, do_handshake, register_session,
validate_hello).
- Nuevo modulo brahman-handshake::network:
- BRAHMAN_HANDSHAKE_PROTOCOL = "/brahman/handshake/1.0.0"
- LibP2pHandshakeStream = Compat<libp2p::Stream>
- run_libp2p_accept_loop(server, net): accept loop que delega cada
stream entrante a session_from_stream(stream.compat()). Sesiones
libp2p y Unix conviven en el mismo Server — comparten broker,
push table, last_matches.
- connect_libp2p(net, peer, card, wit): abre stream libp2p al peer
y arranca handshake.
- NetworkError tipado.
Deps: brahman-handshake gana brahman-net, futures, tokio-util.
brahman-net re-exporta Multiaddr, PeerId, Stream, StreamProtocol,
Protocol, OpenStreamError para que callers no necesiten dep directa
a libp2p.
Tests: 9 verdes en el path Unix (sin regresion). Nuevo
tests/network_libp2p.rs E2E que arma server con BrahmanNet, hace
listen TCP, monta accept loop; cliente con su propio BrahmanNet
dial-ea al peer_id, completa handshake remoto, ping, farewell.
Verifica que la sesion se registro durante la conversacion y se
removio tras farewell.
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
//! Backend libp2p del handshake brahman: el mismo protocolo (Hello /
|
||||
//! HelloAck / Ping / Pong / MatchEvent / Farewell, frames postcard
|
||||
//! length-prefixed) ahora también viaja sobre streams libp2p de la
|
||||
//! malla `brahman-net`.
|
||||
//!
|
||||
//! El servidor expone el bucle [`run_libp2p_accept_loop`] que acepta
|
||||
//! streams del protocolo `BRAHMAN_HANDSHAKE_PROTOCOL` y los delega al
|
||||
//! mismo `Server` que ya escucha por Unix socket — la `Session` es
|
||||
//! genérica sobre el transporte, así que ambas vías comparten broker,
|
||||
//! tablas de sesiones, push de MatchEvents, todo.
|
||||
//!
|
||||
//! El cliente se conecta vía [`connect_libp2p`]: abre un stream
|
||||
//! libp2p hacia un `PeerId` ya conocido y arranca el handshake como
|
||||
//! cualquier `Client`.
|
||||
//!
|
||||
//! Identidad: cada nodo libp2p tiene su `PeerId` (ed25519 derivado).
|
||||
//! La identidad del Ente (Card.id ULID + futura firma) viaja en el
|
||||
//! Hello, como en el path Unix. Trust remoto (verificación de firma
|
||||
//! antes de aceptar el Hello) es Fase 3.
|
||||
//!
|
||||
//! Ejemplo (servidor — Arje):
|
||||
//! ```ignore
|
||||
//! let server = Arc::new(Server::bind("/run/brahman-init.sock", config)?);
|
||||
//! let net = Arc::new(BrahmanNet::new()?);
|
||||
//! net.listen("/ip4/0.0.0.0/tcp/4101".parse()?).await;
|
||||
//!
|
||||
//! tokio::spawn(brahman_handshake::network::run_libp2p_accept_loop(
|
||||
//! server.clone(),
|
||||
//! net.clone(),
|
||||
//! ));
|
||||
//! // Server::run sigue escuchando Unix en paralelo.
|
||||
//! ```
|
||||
//!
|
||||
//! Ejemplo (cliente — sidecar de un Ente remoto):
|
||||
//! ```ignore
|
||||
//! let net = BrahmanNet::new()?;
|
||||
//! net.dial(remote_multiaddr);
|
||||
//! let mut client = brahman_handshake::network::connect_libp2p(
|
||||
//! &net, peer_id, my_card, None,
|
||||
//! ).await?;
|
||||
//! client.ping().await?;
|
||||
//! ```
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use brahman_card::{Card, WitInterface};
|
||||
use brahman_net::{BrahmanNet, OpenStreamError, PeerId, Stream, StreamProtocol};
|
||||
use futures::StreamExt;
|
||||
use tokio_util::compat::{Compat, FuturesAsyncReadCompatExt};
|
||||
use tracing::warn;
|
||||
|
||||
use crate::client::{Client, ClientError};
|
||||
use crate::server::Server;
|
||||
|
||||
/// Sub-protocolo del handshake brahman sobre streams libp2p.
|
||||
pub const BRAHMAN_HANDSHAKE_PROTOCOL: StreamProtocol =
|
||||
StreamProtocol::new("/brahman/handshake/1.0.0");
|
||||
|
||||
/// Tipo del stream que ve la lógica del handshake una vez convertido
|
||||
/// del mundo `futures::AsyncRead/Write` (libp2p) al mundo
|
||||
/// `tokio::io::AsyncRead/Write` (resto del crate).
|
||||
pub type LibP2pHandshakeStream = Compat<Stream>;
|
||||
|
||||
/// Errores específicos del backend libp2p.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum NetworkError {
|
||||
#[error("abrir stream libp2p: {0}")]
|
||||
OpenStream(#[from] OpenStreamError),
|
||||
|
||||
#[error("handshake: {0}")]
|
||||
Handshake(#[from] ClientError),
|
||||
|
||||
#[error("aceptar stream libp2p: {0}")]
|
||||
AcceptStream(String),
|
||||
}
|
||||
|
||||
/// Loop de aceptación de streams libp2p del protocolo handshake.
|
||||
/// Cada stream entrante se construye como `Session` reutilizando las
|
||||
/// tablas compartidas del `Server`, así que conviven con sesiones
|
||||
/// Unix indistinguibles.
|
||||
///
|
||||
/// Vive hasta que el control libp2p se cierre o el caller drop el
|
||||
/// future. Errores por sesión se loggean (no tumban el loop).
|
||||
pub async fn run_libp2p_accept_loop(
|
||||
server: Arc<Server>,
|
||||
net: Arc<BrahmanNet>,
|
||||
) -> Result<(), NetworkError> {
|
||||
let mut control = net.control.clone();
|
||||
let mut incoming = control
|
||||
.accept(BRAHMAN_HANDSHAKE_PROTOCOL)
|
||||
.map_err(|e| NetworkError::AcceptStream(e.to_string()))?;
|
||||
|
||||
while let Some((peer, stream)) = incoming.next().await {
|
||||
let server = server.clone();
|
||||
// .compat() debe pasar al spawn ADENTRO; si lo hacemos afuera
|
||||
// y capturamos `Compat<Stream>` en la closure, el future
|
||||
// resultante hereda traits que dyn AsyncReadWrite no satisface
|
||||
// (compatibility con thread-safety de tokio::spawn).
|
||||
tokio::spawn(handle_libp2p_session(server, stream, peer));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_libp2p_session(
|
||||
server: Arc<Server>,
|
||||
stream: Stream,
|
||||
peer: PeerId,
|
||||
) {
|
||||
let session = server.session_from_stream(stream.compat());
|
||||
if let Err(e) = session.handle().await {
|
||||
warn!(
|
||||
target: "brahman_handshake::network",
|
||||
peer = %peer,
|
||||
error = %e,
|
||||
"sesión libp2p terminó con error"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Conecta como cliente a un Ente remoto vía libp2p y completa el
|
||||
/// handshake. Requiere que `net` ya tenga conexión (o pueda dial-ar)
|
||||
/// al `peer`; típicamente el caller hace `net.dial(multiaddr)` antes.
|
||||
///
|
||||
/// Devuelve un `Client` típico — los métodos `ping`, `await_event`,
|
||||
/// `farewell` funcionan idéntico al path Unix. El stream subyacente
|
||||
/// es libp2p convertido vía `tokio_util::compat`.
|
||||
pub async fn connect_libp2p(
|
||||
net: &BrahmanNet,
|
||||
peer: PeerId,
|
||||
card: Card,
|
||||
wit: Option<WitInterface>,
|
||||
) -> Result<Client<LibP2pHandshakeStream>, NetworkError> {
|
||||
let mut control = net.control.clone();
|
||||
let stream = control
|
||||
.open_stream(peer, BRAHMAN_HANDSHAKE_PROTOCOL)
|
||||
.await?;
|
||||
let client = Client::connect_with_stream(stream.compat(), card, wit).await?;
|
||||
Ok(client)
|
||||
}
|
||||
Reference in New Issue
Block a user