Added randezvous

This commit is contained in:
Ultradesu
2026-07-16 16:04:05 +03:00
parent 52865470b6
commit 747ed7a3e9
14 changed files with 788 additions and 81 deletions
+32 -19
View File
@@ -21,7 +21,10 @@ running peers itself.
* Each peer has a stable 256-bit `NodeId` derived from its persistent
`federation-net` endpoint id; records live on the `K = 8` XOR-closest
nodes.
* Peers discover each other through automatic Hello/PeerExchange gossip;
* A peer joins the network knowing **only the network id**: on startup it
finds other peers through a rendezvous record in the public BitTorrent
Mainline DHT (see `federation-net`'s rendezvous). Within the network peers
then discover each other through automatic Hello/PeerExchange gossip;
connections to newly learned peers are opened **on demand** from stored
tickets.
* Deletions propagate as **tombstones** (revision-based, they beat active
@@ -33,16 +36,29 @@ running peers itself.
Out of scope (by design): fuzzy search, content transfer, CRDTs, consensus,
signatures on DHT records, Sybil protection, accounts, GUI.
## Bootstrapping limitation
## Bootstrapping
There is deliberately **no bootstrap server**, so a peer cannot join a
network it has no contact in: you must pass the ticket of *any* already
running peer via `--connect`. That first peer has no special role — once
contacts have spread through peer exchange, it can be switched off.
There is deliberately **no bootstrap server**. A peer joins a network knowing
only its `--network-id`: peers of the same network find each other through a
shared rendezvous record in the public BitTorrent Mainline DHT (the record's
signing key is derived from the network id). The first peer of a new network
simply publishes itself and waits; every later peer with the same id finds it
within a rendezvous round or two (typically well under a minute). No peer has
a special role.
Because the rendezvous record is world-readable, the network id acts as a
public rendezvous token: anyone who knows it can discover and join the
network. Pick a unique, hard-to-guess name for a private network (e.g.
`myband-artists-prod-7f3a`).
Discovery can be turned off with `--no-bootstrap`; then a peer can only join
through the ticket of *any* already running peer passed via `--connect`
(tickets also work in addition to discovery, e.g. on isolated networks
without internet access).
## Running the demo (three peers)
Start Peer A (alice):
Start the peers in any order — they only share the network id:
```bash
cargo run -p artist-dht-cli -- \
@@ -51,30 +67,25 @@ cargo run -p artist-dht-cli -- \
--name alice
```
Copy the printed `Ticket: fnet...`. Start Peer B (bob) with it:
```bash
cargo run -p artist-dht-cli -- \
--data-dir ./peer-b \
--network-id demo-artists \
--name bob \
--connect 'fnet...'
--name bob
```
Start Peer C (charlie), connected **only to A** — it will learn about B via
peer exchange:
```bash
cargo run -p artist-dht-cli -- \
--data-dir ./peer-c \
--network-id demo-artists \
--name charlie \
--connect 'fnet...'
--name charlie
```
`--connect` may be repeated to dial several peers. All three processes must
use the same `--network-id`; a peer from another network is rejected during
the transport handshake.
Within a minute `Peer connected: ...` lines appear on all three. All
processes must use the same `--network-id`; a peer from another network is
rejected during the transport handshake. (With `--no-bootstrap`, pass the
ticket printed by a running peer via `--connect 'fnet...'` instead;
`--connect` may be repeated.)
### Demo scenario
@@ -130,6 +141,8 @@ use federation_net::NetworkId;
let config = ArtistDhtConfig::builder()
.data_dir("./peer-a")
.network_id(NetworkId::from_name("demo-artists"))
// Optional: discover peers of this network via the mainline DHT.
.rendezvous(artist_dht::RendezvousConfig::default())
.build()?;
let (service, mut events) = ArtistDhtService::start(config).await?;
+13 -1
View File
@@ -3,7 +3,7 @@
use std::path::PathBuf;
use std::time::Duration;
use federation_net::NetworkId;
use federation_net::{NetworkId, RendezvousConfig};
use crate::error::{ArtistDhtError, Result};
@@ -40,6 +40,10 @@ pub struct ArtistDhtConfig {
/// establishing a connection through relays can take much longer than a
/// request over an existing one.
pub transport_timeout: Duration,
/// Automatic peer discovery over the mainline DHT: peers of the same
/// network find each other knowing nothing but the network id. `None`
/// disables it; peers are then connected via tickets only.
pub rendezvous: Option<RendezvousConfig>,
}
impl ArtistDhtConfig {
@@ -62,6 +66,7 @@ pub struct ArtistDhtConfigBuilder {
request_timeout: Option<Duration>,
lookup_timeout: Option<Duration>,
transport_timeout: Option<Duration>,
rendezvous: Option<RendezvousConfig>,
}
impl ArtistDhtConfigBuilder {
@@ -107,6 +112,12 @@ impl ArtistDhtConfigBuilder {
self
}
/// Enables automatic peer discovery over the mainline DHT.
pub fn rendezvous(mut self, rendezvous: RendezvousConfig) -> Self {
self.rendezvous = Some(rendezvous);
self
}
/// Validates and builds the configuration.
pub fn build(self) -> Result<ArtistDhtConfig> {
let data_dir = self
@@ -131,6 +142,7 @@ impl ArtistDhtConfigBuilder {
request_timeout: self.request_timeout.unwrap_or(DEFAULT_REQUEST_TIMEOUT),
lookup_timeout: self.lookup_timeout.unwrap_or(DEFAULT_LOOKUP_TIMEOUT),
transport_timeout: self.transport_timeout.unwrap_or(DEFAULT_TRANSPORT_TIMEOUT),
rendezvous: self.rendezvous,
};
for (name, value) in [
("republish_interval", config.republish_interval),
+1 -1
View File
@@ -89,4 +89,4 @@ pub use service::{
};
// Re-exported types from the transport layer that appear in this API.
pub use federation_net::{EndpointId, NetworkId, PeerTicket};
pub use federation_net::{EndpointId, NetworkId, PeerTicket, RendezvousConfig};
+6 -2
View File
@@ -106,11 +106,15 @@ impl ArtistDhtService {
/// Starts the service: opens the database, starts the network engine,
/// loads persisted contacts and spawns the maintenance tasks.
pub async fn start(config: ArtistDhtConfig) -> Result<(Self, ArtistDhtEventReceiver)> {
let engine_config = NetworkConfig::builder()
let mut engine_builder = NetworkConfig::builder()
.data_dir(&config.data_dir)
.network_id(config.network_id)
.schema_id(SchemaId::from_name(SCHEMA_NAME))
.request_timeout(config.transport_timeout)
.request_timeout(config.transport_timeout);
if let Some(rendezvous) = config.rendezvous.clone() {
engine_builder = engine_builder.rendezvous(rendezvous);
}
let engine_config = engine_builder
.build()
.map_err(|err| ArtistDhtError::Network(err.to_string()))?;
let (engine, net_events) = NetworkEngine::start(engine_config).await?;