feat(brahman-handshake): Fase 2 — discovery remoto via DHT por flow type
Tercer paso del plan "el encuentro entre Entes no se restringe a
local". Cuando un Init local acepta una sesion cuya Card declara
outputs, anuncia al DHT (Kademlia, via brahman-net) que el provee
esos flow types. Cualquier nodo conectado al mismo DHT puede
consultar y obtener la lista de PeerId's que sirven el flow.
API nueva en brahman_handshake::network:
- flow_dht_key(flow_name, type_ref) -> [u8; 32]: blake3 hash de
"brahman-flow|v1|{flow}|{type_canon}". Determinista cross-host.
Cambiar la canonicalizacion rompe compatibilidad — el prefijo v1
documenta version del esquema y obliga a bump al modificar.
- announce_outputs(net, card): start_providing por cada flow.output.
Idempotente, fire-and-forget.
- find_remote_providers(net, flow_name, type_ref) -> Vec<PeerId>:
query DHT. Lista vacia si nadie anuncia.
Wire en el server:
- ServerConfig gana pub net: Option<Arc<BrahmanNet>>. Si esta set,
cada Card registrada con outputs se anuncia automaticamente al DHT
desde register_session. None = server "ciego al DHT".
- Debug manual de ServerConfig (BrahmanNet no es Debug).
Canonicalizacion del TypeRef:
- Primitive { name } -> "prim:{name}"
- Wit { package, interface, name } -> "wit:{package}#{interface_or_empty}#{name}"
Tests: 2 nuevos en tests/network_discovery.rs:
- dht_discovery_finds_remote_provider: 2 nodos, A registra Card con
flow.output = monad-list:json, B dial-ea a A, B llama
find_remote_providers y descubre el peer_id de A.
- dht_discovery_negative_unknown_flow: B busca flow inexistente,
devuelve [] sin colgarse.
Callers actualizados con net: None: tests existentes + ente-zero
(arje aun no expone red; pasar Some(Arc<BrahmanNet>) cuando quiera
publicar al DHT remoto).
Lo que esto desbloquea: un nouser daemon en maquina A puede ser
descubierto por nouser-explorer en maquina B sin conocimiento previo
del peer — solo necesitan compartir DHT (via bootstrap inicial).
Pendiente para Fase 3: trust (firma Ed25519 en Cards remotas) +
stop_providing al cleanup de sesion.
This commit is contained in:
@@ -6,6 +6,57 @@ ratio/diff ver `git show <sha>`.
|
||||
|
||||
## 2026-05-09
|
||||
|
||||
### feat(brahman-handshake): Fase 2 — discovery remoto vía DHT por flow type
|
||||
Tercer paso del plan "el encuentro entre Entes no se restringe a
|
||||
local". Cuando un Init local acepta una sesión cuya Card declara
|
||||
outputs, anuncia al DHT (Kademlia, vía `brahman-net`) que él provee
|
||||
esos flow types. Cualquier nodo conectado al mismo DHT puede
|
||||
consultar y obtener la lista de `PeerId`s que sirven el flow.
|
||||
|
||||
API nueva en `brahman_handshake::network`:
|
||||
- `flow_dht_key(flow_name, type_ref) -> [u8; 32]`: blake3 hash de
|
||||
`"brahman-flow|v1|{flow}|{type_canon}"`. Determinístico cross-host.
|
||||
Cambiar la canonicalización rompe compatibilidad — el prefijo `v1`
|
||||
documenta la versión del esquema y obliga a bump al modificar.
|
||||
- `announce_outputs(net, card)`: llama `start_providing` en el DHT
|
||||
por cada `Flow` en `card.flow.output`. Idempotente, fire-and-forget.
|
||||
- `find_remote_providers(net, flow_name, type_ref) -> Vec<PeerId>`:
|
||||
query DHT por la key derivada. Lista vacía si nadie anuncia o si
|
||||
la query no resuelve dentro del timeout interno de Kad.
|
||||
|
||||
Wire en el server:
|
||||
- `ServerConfig` gana `pub net: Option<Arc<BrahmanNet>>`. Si está set,
|
||||
cada Card registrada con outputs se anuncia automáticamente al DHT
|
||||
desde `register_session`. `None` = server "ciego al DHT" (correcto
|
||||
cuando no hay conectividad o el operador no quiere exponer).
|
||||
- `ServerConfig` ahora tiene `Debug` manual (BrahmanNet no implementa
|
||||
Debug; loggeamos sólo presencia/ausencia).
|
||||
|
||||
Canonicalización del TypeRef:
|
||||
- `Primitive { name }` → `prim:{name}`
|
||||
- `Wit { package, interface, name }` → `wit:{package}#{interface_or_empty}#{name}`
|
||||
|
||||
Tests: 2 nuevos en `tests/network_discovery.rs`:
|
||||
- `dht_discovery_finds_remote_provider`: dos nodos, A registra Card
|
||||
con `flow.output = monad-list:json`, B dial-ea a A y descubre el
|
||||
`peer_id` de A vía `find_remote_providers`. Asserts contains.
|
||||
- `dht_discovery_negative_unknown_flow`: B busca un flow que nadie
|
||||
anunció, devuelve lista vacía sin colgarse.
|
||||
|
||||
Lo que esto desbloquea:
|
||||
- Un `nouser daemon` corriendo en máquina A puede ser descubierto por
|
||||
un `nouser-explorer` en máquina B sin conocimiento previo del peer
|
||||
— sólo necesitan compartir DHT (vía bootstrap inicial).
|
||||
- La cadena completa "explorer → daemon → llm-provider" puede cruzar
|
||||
máquinas, no sólo procesos.
|
||||
|
||||
Lo que queda para Fase 3 (trust):
|
||||
- Cards remotas se aceptan hoy sin verificación. Para producción se
|
||||
necesita firma Ed25519 sobre la Card y verificación antes de
|
||||
aceptar el Hello remoto. Local sigue confiando en SO_PEERCRED.
|
||||
- Stop-providing al cleanup de sesión (hoy records DHT viven hasta
|
||||
TTL ~24h aunque la sesión cierre).
|
||||
|
||||
### 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 /
|
||||
|
||||
Generated
+1
@@ -1199,6 +1199,7 @@ name = "brahman-handshake"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"blake3",
|
||||
"brahman-broker",
|
||||
"brahman-card",
|
||||
"brahman-net",
|
||||
|
||||
@@ -12,6 +12,7 @@ description = "Brahman — handshake runtime Init↔módulo. Local sobre Unix so
|
||||
brahman-card = { path = "../brahman-card" }
|
||||
brahman-broker = { path = "../brahman-broker" }
|
||||
brahman-net = { path = "../../shared/brahman-net" }
|
||||
blake3 = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
postcard = { workspace = true }
|
||||
|
||||
@@ -43,11 +43,11 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use brahman_card::{Card, WitInterface};
|
||||
use brahman_card::{Card, TypeRef, WitInterface};
|
||||
use brahman_net::{BrahmanNet, OpenStreamError, PeerId, Stream, StreamProtocol};
|
||||
use futures::StreamExt;
|
||||
use tokio_util::compat::{Compat, FuturesAsyncReadCompatExt};
|
||||
use tracing::warn;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::client::{Client, ClientError};
|
||||
use crate::server::Server;
|
||||
@@ -138,3 +138,92 @@ pub async fn connect_libp2p(
|
||||
let client = Client::connect_with_stream(stream.compat(), card, wit).await?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Discovery remoto via DHT — Fase 2
|
||||
// =====================================================================
|
||||
//
|
||||
// Cuando un Ente registra una Card con outputs en el Init local, el
|
||||
// Init anuncia al DHT (`net.start_providing(key)`) bajo una key
|
||||
// derivada de `(flow_name, TypeRef)`. Cualquier nodo conectado al
|
||||
// mismo DHT puede consultar `find_remote_providers(flow_name, type)`
|
||||
// y obtener la lista de `PeerId`s que dijeron proveer ese flow.
|
||||
//
|
||||
// La key es **estable y libre de colisiones** entre versiones del
|
||||
// monorepo: usa blake3 sobre un canon textual `brahman-flow|{name}|{type_canon}`.
|
||||
// Cambiar la canonicalización rompe el discovery cross-version, así
|
||||
// que cualquier modificación requiere bump de versión documentado.
|
||||
|
||||
/// Prefijo de namespace para todas las keys DHT del subprotocolo
|
||||
/// brahman. Discrimina contra otros usos del mismo DHT (sync minga,
|
||||
/// futuros) — protege contra colisiones accidentales.
|
||||
const FLOW_KEY_PREFIX: &str = "brahman-flow|v1|";
|
||||
|
||||
/// Canonicaliza un `TypeRef` a string estable. Cambios aquí rompen
|
||||
/// la compatibilidad de discovery cross-version; bump documentado
|
||||
/// en `FLOW_KEY_PREFIX` al modificar.
|
||||
fn canonicalize_type(t: &TypeRef) -> String {
|
||||
match t {
|
||||
TypeRef::Primitive { name } => format!("prim:{}", name),
|
||||
TypeRef::Wit {
|
||||
package,
|
||||
interface,
|
||||
name,
|
||||
} => format!(
|
||||
"wit:{}#{}#{}",
|
||||
package,
|
||||
interface.as_deref().unwrap_or(""),
|
||||
name
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Deriva la key del DHT para un `(flow_name, type_ref)` específico.
|
||||
/// blake3-32B determinístico — la misma tupla en cualquier máquina
|
||||
/// produce la misma key.
|
||||
pub fn flow_dht_key(flow_name: &str, type_ref: &TypeRef) -> [u8; 32] {
|
||||
let canon = format!(
|
||||
"{}{}|{}",
|
||||
FLOW_KEY_PREFIX,
|
||||
flow_name,
|
||||
canonicalize_type(type_ref)
|
||||
);
|
||||
*blake3::hash(canon.as_bytes()).as_bytes()
|
||||
}
|
||||
|
||||
/// Anuncia al DHT que este nodo provee cada output flow declarado
|
||||
/// en `card`. Llamarlo tras `register_session` propaga la
|
||||
/// disponibilidad a todos los peers que comparten DHT con éste.
|
||||
///
|
||||
/// Idempotente: re-anunciar la misma key actualiza el TTL del record
|
||||
/// en el DHT. Best-effort: si `start_providing` falla por falta de
|
||||
/// peers cercanos (DHT vacío), el record vive en el store local
|
||||
/// hasta que llegue una conexión.
|
||||
pub fn announce_outputs(net: &BrahmanNet, card: &Card) {
|
||||
for flow in &card.flow.output {
|
||||
let key = flow_dht_key(&flow.name, &flow.ty);
|
||||
debug!(
|
||||
target: "brahman_handshake::network",
|
||||
flow = %flow.name,
|
||||
"announce_output → DHT"
|
||||
);
|
||||
net.start_providing(&key);
|
||||
}
|
||||
}
|
||||
|
||||
/// Consulta el DHT por peers que han anunciado proveer el flow
|
||||
/// `(flow_name, type_ref)`. Devuelve la lista resuelta de `PeerId`s.
|
||||
/// Lista vacía si nadie anuncia, si la query timeout-ea, o si el
|
||||
/// DHT no ha encontrado providers.
|
||||
///
|
||||
/// Para cada `PeerId` devuelto, el caller puede luego dial-ar al
|
||||
/// peer (a sus addrs conocidas vía Identify) y abrir un sub-handshake
|
||||
/// remoto con [`connect_libp2p`].
|
||||
pub async fn find_remote_providers(
|
||||
net: &BrahmanNet,
|
||||
flow_name: &str,
|
||||
type_ref: &TypeRef,
|
||||
) -> Vec<PeerId> {
|
||||
let key = flow_dht_key(flow_name, type_ref);
|
||||
net.find_providers(&key).await
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use brahman_broker::{Broker, Endpoint};
|
||||
use brahman_card::{Card, ResolvedCard, WitInterface, CARD_SCHEMA_VERSION};
|
||||
use brahman_net::BrahmanNet;
|
||||
use tokio::io::{split, AsyncRead, AsyncWrite, WriteHalf};
|
||||
use tokio::net::{UnixListener, UnixStream};
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
@@ -39,7 +40,7 @@ type LastMatches = Arc<Mutex<HashMap<SessionId, HashMap<String, Endpoint>>>>;
|
||||
const PUSH_CHANNEL_CAPACITY: usize = 32;
|
||||
|
||||
/// Configuración del servidor.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ServerConfig {
|
||||
/// `true` si el Init está atado al servidor (se reporta en `HelloAck`).
|
||||
pub init_attached: bool,
|
||||
@@ -47,6 +48,27 @@ pub struct ServerConfig {
|
||||
/// `register` tras un Hello aceptado y `unregister` al cerrar la
|
||||
/// sesión (Farewell o EOF). Si es `None`, el broker no se usa.
|
||||
pub broker: Option<SharedBroker>,
|
||||
/// Capa P2P compartida. Si está presente, cada Card registrada
|
||||
/// con outputs se anuncia automáticamente al DHT vía
|
||||
/// [`brahman_handshake::network::announce_outputs`], permitiendo
|
||||
/// que un consumer remoto los descubra con
|
||||
/// [`brahman_handshake::network::find_remote_providers`]. Si es
|
||||
/// `None`, el server queda "ciego al DHT" — sólo matchea sesiones
|
||||
/// locales (lo cual es correcto cuando no hay conectividad o no
|
||||
/// se desea exponer al exterior).
|
||||
pub net: Option<Arc<BrahmanNet>>,
|
||||
}
|
||||
|
||||
// Manual Debug porque BrahmanNet no implementa Debug (libp2p Swarm
|
||||
// no es Debug). Sólo loggeamos los campos relevantes para tracing.
|
||||
impl std::fmt::Debug for ServerConfig {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ServerConfig")
|
||||
.field("init_attached", &self.init_attached)
|
||||
.field("broker", &self.broker.as_ref().map(|_| "<broker>"))
|
||||
.field("net", &self.net.as_ref().map(|_| "<net>"))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Servidor de handshake escuchando en un Unix socket.
|
||||
@@ -508,6 +530,12 @@ async fn register_session(
|
||||
.await
|
||||
.register(session_id, &card, wit.clone());
|
||||
}
|
||||
// Si el server tiene net configurado, anunciar los outputs al
|
||||
// DHT para que peers remotos puedan descubrirlos. Idempotente
|
||||
// y best-effort — fallos de Kad no propagan al handshake.
|
||||
if let Some(net) = &config.net {
|
||||
crate::network::announce_outputs(net, &card);
|
||||
}
|
||||
let resolved = match wit {
|
||||
Some(w) => ResolvedCard::from_conscious(card, w),
|
||||
None => ResolvedCard::from_agnostic(card),
|
||||
|
||||
@@ -58,7 +58,7 @@ fn sock_path(name: &str) -> std::path::PathBuf {
|
||||
#[tokio::test]
|
||||
async fn full_handshake_roundtrip() {
|
||||
let path = sock_path("happy");
|
||||
let server = Server::bind(&path, ServerConfig { init_attached: true, broker: None }).unwrap();
|
||||
let server = Server::bind(&path, ServerConfig { init_attached: true, broker: None, net: None }).unwrap();
|
||||
|
||||
let session_handle = tokio::spawn({
|
||||
async move {
|
||||
@@ -193,6 +193,7 @@ async fn broker_registers_and_unregisters_with_session() {
|
||||
ServerConfig {
|
||||
init_attached: false,
|
||||
broker: Some(broker.clone()),
|
||||
net: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
@@ -235,6 +236,7 @@ async fn broker_matches_two_live_modules() {
|
||||
ServerConfig {
|
||||
init_attached: false,
|
||||
broker: Some(broker.clone()),
|
||||
net: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
@@ -310,6 +312,7 @@ async fn match_event_pushed_on_producer_arrival() {
|
||||
ServerConfig {
|
||||
init_attached: false,
|
||||
broker: Some(broker.clone()),
|
||||
net: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
//! Test E2E de Fase 2: discovery remoto vía DHT.
|
||||
//!
|
||||
//! Pipeline:
|
||||
//! 1. **Provider node (A)**: arma server con `BrahmanNet` configurado;
|
||||
//! listen TCP; un cliente local registra una Card con un output
|
||||
//! flow. El server llama `announce_outputs` automáticamente, lo
|
||||
//! que hace `start_providing` en el DHT bajo la key derivada del
|
||||
//! flow.
|
||||
//! 2. **Consumer node (B)**: arma su propio `BrahmanNet`; dial-ea al
|
||||
//! multiaddr del provider para que ambos se conozcan vía Identify
|
||||
//! (esto popula sus respectivos routing tables de Kademlia).
|
||||
//! 3. **B llama `find_remote_providers(flow_name, type)`**: la query
|
||||
//! DHT propaga vía Kad, y eventually el provider responde con su
|
||||
//! `PeerId`.
|
||||
//! 4. **Verificación**: el `PeerId` que B descubre coincide con el
|
||||
//! de A.
|
||||
//!
|
||||
//! Notas:
|
||||
//! - Kademlia replication factor por defecto es 20; con 2 nodos no
|
||||
//! hay propagación material — A es el único provider, B llega a A
|
||||
//! vía la conexión directa establecida en step 2 y obtiene el record
|
||||
//! del store local de A.
|
||||
//! - El test usa flow `monad-list:json` por familiaridad (es el flow
|
||||
//! real que `nouser daemon` declara). Sirve también como prueba de
|
||||
//! que el sistema completo (daemon + DHT) funcionaría con cero
|
||||
//! cambios en la Card.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use brahman_broker::{Broker, BrokerConfig};
|
||||
use brahman_card::{
|
||||
ulid::Ulid, Card, CardKind, Flow, Flows, Lifecycle, Payload, Priority, Supervision, TypeRef,
|
||||
CARD_SCHEMA_VERSION,
|
||||
};
|
||||
use brahman_handshake::network::{find_remote_providers, run_libp2p_accept_loop};
|
||||
use brahman_handshake::server::{Server, ServerConfig};
|
||||
use brahman_net::{BrahmanNet, Multiaddr, Protocol};
|
||||
use tempfile::TempDir;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
fn provider_card(label: &str, flow_name: &str, type_name: &str) -> Card {
|
||||
Card {
|
||||
schema_version: CARD_SCHEMA_VERSION,
|
||||
id: Ulid::new(),
|
||||
label: label.into(),
|
||||
provides: BTreeSet::new(),
|
||||
requires: BTreeSet::new(),
|
||||
permissions: Default::default(),
|
||||
soma: Default::default(),
|
||||
payload: Payload::Virtual,
|
||||
supervision: Supervision::Delegate,
|
||||
lifecycle: Lifecycle::Daemon,
|
||||
priority: Priority::Normal,
|
||||
kind: CardKind::Ente,
|
||||
flow: Flows {
|
||||
input: vec![],
|
||||
output: vec![Flow {
|
||||
name: flow_name.into(),
|
||||
ty: TypeRef::Primitive {
|
||||
name: type_name.into(),
|
||||
},
|
||||
pin_to: None,
|
||||
}],
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn dht_discovery_finds_remote_provider() {
|
||||
// ---- Node A (provider): server + libp2p net + Card con output ----
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let a_unix = tmp.path().join("a.sock");
|
||||
|
||||
let a_broker = Arc::new(Mutex::new(Broker::new(BrokerConfig::default())));
|
||||
let a_net = Arc::new(BrahmanNet::new().unwrap());
|
||||
let a_peer = a_net.peer_id;
|
||||
|
||||
let a_server = Arc::new(
|
||||
Server::bind(
|
||||
&a_unix,
|
||||
ServerConfig {
|
||||
init_attached: true,
|
||||
broker: Some(a_broker.clone()),
|
||||
net: Some(a_net.clone()), // ← clave Fase 2: anuncia al DHT
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let listen_addr: Multiaddr = "/ip4/127.0.0.1/tcp/0".parse().unwrap();
|
||||
let a_addr = a_net.listen(listen_addr).await;
|
||||
let mut a_full_addr = a_addr.clone();
|
||||
a_full_addr.push(Protocol::P2p(a_peer));
|
||||
|
||||
tokio::spawn(run_libp2p_accept_loop(a_server.clone(), a_net.clone()));
|
||||
|
||||
// Unix accept loop: necesario para que Client::connect al socket
|
||||
// local no cuelgue (Server no se auto-accepta; el caller arma el
|
||||
// loop). Cada session entrante corre en su propia task.
|
||||
{
|
||||
let s = a_server.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match s.accept_one().await {
|
||||
Ok(session) => {
|
||||
tokio::spawn(async move {
|
||||
let _ = session.handle().await;
|
||||
});
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Registrar la Card local en A con un flow output.
|
||||
let card = provider_card("test.engine_remote", "monad-list", "json");
|
||||
let mut local_client = brahman_handshake::client::Client::connect(&a_unix, card)
|
||||
.await
|
||||
.expect("registro local en A");
|
||||
|
||||
// ---- Node B (consumer): otro net que dial-a a A ----
|
||||
let b_net = BrahmanNet::new().unwrap();
|
||||
b_net.dial(a_full_addr.clone());
|
||||
|
||||
// Esperar a que la conexión se establezca y Identify popule el
|
||||
// routing table de Kad. En localhost con 2 peers, ~250ms es de
|
||||
// sobra; sumamos margen para CI.
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
|
||||
// ---- Discovery: B busca providers de "monad-list:json" ----
|
||||
let providers = find_remote_providers(
|
||||
&b_net,
|
||||
"monad-list",
|
||||
&TypeRef::Primitive {
|
||||
name: "json".into(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
providers.contains(&a_peer),
|
||||
"B debería descubrir a A vía DHT. Encontrados: {:?}, esperado: {}",
|
||||
providers,
|
||||
a_peer
|
||||
);
|
||||
|
||||
// Sanidad: el cliente local sigue vivo durante todo el test (lo
|
||||
// que mantiene la Card registrada y por tanto el record DHT vivo).
|
||||
local_client.farewell().await.ok();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn dht_discovery_negative_unknown_flow() {
|
||||
// Mismo setup que el test happy-path, pero B busca un flow que A
|
||||
// NO ofrece. Debe devolver lista vacía dentro del timeout
|
||||
// razonable (no colgarse).
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let a_unix = tmp.path().join("a.sock");
|
||||
let a_broker = Arc::new(Mutex::new(Broker::new(BrokerConfig::default())));
|
||||
let a_net = Arc::new(BrahmanNet::new().unwrap());
|
||||
let a_peer = a_net.peer_id;
|
||||
|
||||
let a_server = Arc::new(
|
||||
Server::bind(
|
||||
&a_unix,
|
||||
ServerConfig {
|
||||
init_attached: true,
|
||||
broker: Some(a_broker),
|
||||
net: Some(a_net.clone()),
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let a_addr = a_net.listen("/ip4/127.0.0.1/tcp/0".parse().unwrap()).await;
|
||||
let mut a_full = a_addr.clone();
|
||||
a_full.push(Protocol::P2p(a_peer));
|
||||
|
||||
tokio::spawn(run_libp2p_accept_loop(a_server.clone(), a_net.clone()));
|
||||
|
||||
// Unix accept loop: necesario para que Client::connect al socket
|
||||
// local no cuelgue (Server no se auto-accepta; el caller arma el
|
||||
// loop). Cada session entrante corre en su propia task.
|
||||
{
|
||||
let s = a_server.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match s.accept_one().await {
|
||||
Ok(session) => {
|
||||
tokio::spawn(async move {
|
||||
let _ = session.handle().await;
|
||||
});
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let card = provider_card("test.engine_other", "monad-list", "json");
|
||||
let mut local = brahman_handshake::client::Client::connect(&a_unix, card)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let b_net = BrahmanNet::new().unwrap();
|
||||
b_net.dial(a_full);
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
|
||||
// Buscamos un flow que NADIE anunció.
|
||||
let providers = find_remote_providers(
|
||||
&b_net,
|
||||
"flow-que-no-existe",
|
||||
&TypeRef::Primitive {
|
||||
name: "json".into(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
providers.is_empty(),
|
||||
"no debería haber providers para un flow inexistente, got: {:?}",
|
||||
providers
|
||||
);
|
||||
|
||||
local.farewell().await.ok();
|
||||
}
|
||||
@@ -55,6 +55,7 @@ async fn libp2p_handshake_roundtrip() {
|
||||
ServerConfig {
|
||||
init_attached: true,
|
||||
broker: Some(broker.clone()),
|
||||
net: None,
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
|
||||
@@ -164,6 +164,10 @@ async fn primordial_loop(
|
||||
brahman_handshake::server::ServerConfig {
|
||||
init_attached: true,
|
||||
broker: Some(brahman_broker.clone()),
|
||||
// Fase 2: el Init aún no expone red P2P por default. Cuando
|
||||
// arje quiera publicar Cards al DHT remoto, pasar aquí un
|
||||
// `Some(Arc<BrahmanNet>)` con la malla configurada.
|
||||
net: None,
|
||||
},
|
||||
) {
|
||||
Ok(server) => {
|
||||
|
||||
Reference in New Issue
Block a user