open-standards/smart-byte/smart-byte-rs/crates/smart-byte-libp2p/src/behaviour.rs
Transaction Science 3fe109d58f ingest batch 4: 9 crates · table-stakes completers across all three workspaces
OpenPay (3 crates, ~13k LOC, 175 tests)
  op-connect           sub-merchant onboarding + KYB + split-payments + payouts + 1099-K
  op-3ds2              3DS2.3 SCA processor (AReq/ARes/CReq/CRes/RReq/RRes + exemptions)
  op-tax               US/EU/UK/AU/CA/IN tax with Avalara/Stripe/CCH/Vertex/TaxJar adapters

Smart Byte (3 crates, ~7k LOC, 57 tests)
  smart-byte-libp2p    libp2p transport (kad/gossipsub/mdns/autonat/relay/dcutr/identify/ping/req_resp)
  smart-byte-crdt      LWW/GCounter/PnCounter/OrSet/LwwMap/RgaList/2P-Set + HLC + vector clock + Automerge/Yjs interop
  smart-byte-keri-witness  KERI key-rotation cycle + witness layer + watchers + recovery + delegation

EOC (3 crates, ~8k LOC, 108 tests)
  eoc-tools            function-calling across Anthropic/OpenAI/Google/Mistral/Cohere + parallel exec + sandbox
  eoc-multimodal       vision (CLIP/SigLIP/ColPali) + audio (Whisper/MMS) + video sampling + modality router
  eoc-spec-decode      vanilla + SpS-with-temp + lookahead speculative decoding + EAGLE/Medusa stubs

All three workspaces compile cleanly. Forbidden-unsafe, no unwrap outside tests.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-24 04:00:04 -04:00

168 lines
5.8 KiB
Rust

//! Composed `NetworkBehaviour` for a Smart Byte libp2p node.
//!
//! Combines all the protocols Smart Byte exercises:
//!
//! * `kad` — Kademlia DHT for peer + content discovery.
//! * `gossipsub` — topic publish / subscribe.
//! * `mdns` — LAN discovery (toggleable).
//! * `autonat` — NAT detection.
//! * `identify` — peer-info exchange.
//! * `ping` — liveness.
//! * `relay` — Circuit Relay v2 server role (toggleable).
//! * `dcutr` — Direct Connection Upgrade through Relay (toggleable).
//! * `req_resp` — point-to-point envelope fetch.
use libp2p::kad::store::MemoryStore;
use libp2p::swarm::NetworkBehaviour;
use libp2p::swarm::behaviour::toggle::Toggle;
use libp2p::{StreamProtocol, autonat, dcutr, gossipsub, identify, kad, mdns, ping, relay};
use libp2p::request_response;
use crate::codec::EnvelopeCodec;
/// The Smart Byte libp2p protocol identifier for point-to-point
/// envelope fetch.
pub const ENVELOPE_PROTOCOL: &str = "/smart-byte/envelope/1.0.0";
/// The Smart Byte libp2p protocol identifier emitted via `identify`.
pub const IDENTIFY_PROTOCOL: &str = "/smart-byte/identify/1.0.0";
/// Composed network behaviour driving a Smart Byte libp2p node.
#[derive(NetworkBehaviour)]
pub struct SmartByteBehaviour {
/// Kademlia DHT.
pub kad: kad::Behaviour<MemoryStore>,
/// Gossipsub publish/subscribe.
pub gossipsub: gossipsub::Behaviour,
/// mDNS LAN discovery (optional).
pub mdns: Toggle<mdns::tokio::Behaviour>,
/// AutoNAT for inferring our own reachability.
pub autonat: autonat::Behaviour,
/// Identify protocol — peer-info exchange.
pub identify: identify::Behaviour,
/// Liveness ping.
pub ping: ping::Behaviour,
/// Circuit Relay v2 server role (optional).
pub relay: Toggle<relay::Behaviour>,
/// DCUtR hole-punching (optional).
pub dcutr: Toggle<dcutr::Behaviour>,
/// Point-to-point envelope fetch.
pub req_resp: request_response::Behaviour<EnvelopeCodec>,
}
/// Configuration toggles consumed by [`SmartByteBehaviour::build`].
pub struct BehaviourConfig {
/// Local PeerId; needed for Kademlia and other protocols.
pub local_peer_id: libp2p::PeerId,
/// Local public key; needed for `identify`.
pub local_public_key: libp2p::identity::PublicKey,
/// Whether to enable mDNS.
pub enable_mdns: bool,
/// Whether to enable relay server role.
pub enable_relay: bool,
/// Whether to enable DCUtR hole-punching.
pub enable_dcutr: bool,
/// Gossipsub configuration.
pub gossipsub_config: gossipsub::Config,
}
impl SmartByteBehaviour {
/// Build the composed behaviour from a configuration.
pub fn build(cfg: BehaviourConfig) -> crate::Result<Self> {
// Kademlia.
let store = MemoryStore::new(cfg.local_peer_id);
let kad_cfg = kad::Config::default();
let mut kad = kad::Behaviour::with_config(cfg.local_peer_id, store, kad_cfg);
// Use server mode so peers in tests can respond to queries.
kad.set_mode(Some(kad::Mode::Server));
// Gossipsub.
let gossipsub = gossipsub::Behaviour::new(
gossipsub::MessageAuthenticity::Signed(libp2p::identity::Keypair::generate_ed25519()),
cfg.gossipsub_config.clone(),
)
.map_err(|e| crate::Error::Gossipsub(e.to_string()))?;
// mDNS.
let mdns = if cfg.enable_mdns {
let m = mdns::tokio::Behaviour::new(mdns::Config::default(), cfg.local_peer_id)
.map_err(|e| crate::Error::Build(format!("mdns: {e}")))?;
Toggle::from(Some(m))
} else {
Toggle::from(None)
};
// AutoNAT.
let autonat = autonat::Behaviour::new(cfg.local_peer_id, autonat::Config::default());
// Identify.
let identify = identify::Behaviour::new(identify::Config::new(
IDENTIFY_PROTOCOL.to_string(),
cfg.local_public_key.clone(),
));
// Ping.
let ping = ping::Behaviour::new(ping::Config::new());
// Relay server role.
let relay = if cfg.enable_relay {
Toggle::from(Some(relay::Behaviour::new(
cfg.local_peer_id,
relay::Config::default(),
)))
} else {
Toggle::from(None)
};
// DCUtR.
let dcutr = if cfg.enable_dcutr {
Toggle::from(Some(dcutr::Behaviour::new(cfg.local_peer_id)))
} else {
Toggle::from(None)
};
// Request-response for envelope fetch.
let req_resp = request_response::Behaviour::with_codec(
EnvelopeCodec,
[(
StreamProtocol::new(ENVELOPE_PROTOCOL),
request_response::ProtocolSupport::Full,
)],
request_response::Config::default(),
);
Ok(Self {
kad,
gossipsub,
mdns,
autonat,
identify,
ping,
relay,
dcutr,
req_resp,
})
}
}
/// Build a sensible default Gossipsub config: signed messages, IDs from
/// content (so identical envelopes deduplicate), short heartbeat.
pub fn default_gossipsub_config() -> gossipsub::Config {
use std::time::Duration;
gossipsub::ConfigBuilder::default()
.heartbeat_interval(Duration::from_secs(1))
.validation_mode(gossipsub::ValidationMode::Strict)
.message_id_fn(|m: &gossipsub::Message| {
let h = blake3_hash_message(m);
gossipsub::MessageId::from(h.to_vec())
})
.build()
.expect("default gossipsub config is statically valid")
}
fn blake3_hash_message(m: &gossipsub::Message) -> [u8; 32] {
let mut h = blake3::Hasher::new();
h.update(m.topic.as_str().as_bytes());
h.update(&m.data);
*h.finalize().as_bytes()
}