refactor: v0.8 Iteration B — cruft purge + ALPN bump to itsgoin/4
Zero-users ruling removes all wire-compat obligations. Net -1,443 LOC of Rust. Protocol break: - ALPN_V2 b"itsgoin/3" -> ALPN b"itsgoin/4" (renamed: the versioned name was what rotted). Old nodes are now refused at the QUIC handshake instead of half-interoperating with the v0.8 manifest/digest formats. Deploy gate from Iteration A is discharged. Dead wire surface removed (MessageType 46 -> 40): - DeleteRecord 0x51 (deletes ride ControlOp::DeletePost + InitialExchange) - VisibilityUpdate 0x52 + its two dead senders (ControlOp::UpdateVisibility) - SocialDisconnectNotice 0x71 (zero senders; checkin timeouts carry the signal) - MeshPrefer 0xB3 + request_prefer + handler (preferred peers are gone) - Legacy dual pull-matching half (have_post_ids) — behavior-identical - PullSyncResponse.visibility_updates + counters (control posts cover it) - serde(default) stripped from wire-only payloads (kept where skip_serializing_if makes it load-bearing; persisted-row defaults untouched) Preferred-peer subsystem eliminated (design ruling: CDN file_holders replaced the N+10 direct push/pull it served): slot tier, preferred_peers table (dropped), preferred_tree semantics, rebalance Priority 0, find_relays_for preferred tiers, 7-day prune + 30-day watcher, dead FromStr impl. Slots are now Local/Wide only (91 desktop / 12 mobile) pending Iteration C's single ~20-slot pool. Other dead code: hostlist encoder + base64url helper, always-empty downstream_addrs parameter chain, start_upnp_renewal_cycle no-op + 4 callers, RELAY_TARGET_RATE_LIMIT, GetSecretSeed, stale comments swept. Preserved deliberately: EDM scanner corpse (awaiting raw-UDP refactor), PortScanHeartbeat, Iteration A startup migrations, session-relay opt-in gating. Docs: design.html + tech.html synced (message counts, ALPN, purge status, Rework asides flipped past-tense). 190 core tests pass; CLI + desktop build; A3 integration 9/9 (independently re-run post-purge). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGiPD2cF75mnvneSCjdDC5
This commit is contained in:
parent
17dbf076cb
commit
36e3871c4b
15 changed files with 152 additions and 1476 deletions
|
|
@ -31,6 +31,11 @@ use crate::types::{
|
|||
/// with "UnknownIssuer" because they pin the wrong cert identity.
|
||||
const DEFAULT_ANCHOR: &str = "ab2b7258ef0b75b2c6ee8bf6595232055f6199d584d3c0fc10b15a1ed549aa13@itsgoin.net:4433";
|
||||
|
||||
/// Cooldown between relay-introduction attempts toward the same target (5 min)
|
||||
const RELAY_COOLDOWN_MS: i64 = 300_000;
|
||||
/// Timeout for a single relay-introduction round trip
|
||||
const RELAY_INTRO_TIMEOUT_SECS: u64 = 15;
|
||||
|
||||
/// A distsoc node: ties together identity, storage, and networking
|
||||
pub struct Node {
|
||||
pub data_dir: PathBuf,
|
||||
|
|
@ -342,7 +347,7 @@ impl Node {
|
|||
// Load or generate identity key (network secret — QUIC endpoint only,
|
||||
// never used as content author under the v0.6.1+ clean model).
|
||||
let key_path = data_dir.join("identity.key");
|
||||
let (mut secret_key, mut secret_seed) = if key_path.exists() {
|
||||
let (mut secret_key, secret_seed) = if key_path.exists() {
|
||||
let key_bytes = std::fs::read(&key_path)?;
|
||||
let bytes: [u8; 32] = key_bytes
|
||||
.try_into()
|
||||
|
|
@ -415,7 +420,6 @@ impl Node {
|
|||
std::fs::write(&key_path, new_seed)?;
|
||||
info!("v0.6.1 migration: rotated network key to decouple from default posting key");
|
||||
secret_key = new_key;
|
||||
secret_seed = new_seed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -429,9 +433,9 @@ impl Node {
|
|||
let last_rebalance_ms = Arc::new(AtomicU64::new(0));
|
||||
let last_anchor_register_ms = Arc::new(AtomicU64::new(0));
|
||||
|
||||
// Start network (v2: single ALPN, connection manager)
|
||||
// Start network (single ALPN, connection manager)
|
||||
let network = Arc::new(
|
||||
Network::new(secret_key, Arc::clone(&storage), bind_addr, secret_seed, Arc::clone(&blob_store), profile, Arc::clone(&activity_log)).await?,
|
||||
Network::new(secret_key, Arc::clone(&storage), bind_addr, Arc::clone(&blob_store), profile, Arc::clone(&activity_log)).await?,
|
||||
);
|
||||
let node_id = network.node_id_bytes();
|
||||
|
||||
|
|
@ -492,7 +496,7 @@ impl Node {
|
|||
// NETWORK id may still carry persona fields from the unified-id
|
||||
// era. Strip them in place so no code path can ever re-broadcast
|
||||
// persona data bound to the device network id. Topology fields
|
||||
// (anchors/recent_peers/preferred_peers) are preserved.
|
||||
// (anchors/recent_peers) are preserved.
|
||||
//
|
||||
// (b) The manifest signature digest dropped author_addresses, so
|
||||
// rows signed by pre-v0.8 builds no longer verify. Re-sign our
|
||||
|
|
@ -1302,7 +1306,7 @@ impl Node {
|
|||
continue;
|
||||
}
|
||||
match kind {
|
||||
PeerSlotKind::Preferred | PeerSlotKind::Local => social.push(nid),
|
||||
PeerSlotKind::Local => social.push(nid),
|
||||
PeerSlotKind::Wide => wide.push(nid),
|
||||
}
|
||||
}
|
||||
|
|
@ -1982,7 +1986,6 @@ impl Node {
|
|||
let peer_addresses = storage.build_peer_addresses_for(node_id)?;
|
||||
let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default().as_millis() as u64;
|
||||
let preferred_tree = storage.build_preferred_tree_for(node_id).unwrap_or_default();
|
||||
storage.upsert_social_route(&SocialRouteEntry {
|
||||
node_id: *node_id,
|
||||
addresses,
|
||||
|
|
@ -1992,7 +1995,6 @@ impl Node {
|
|||
last_connected_ms: 0,
|
||||
last_seen_ms: now,
|
||||
reach_method: ReachMethod::Direct,
|
||||
preferred_tree,
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -2161,7 +2163,6 @@ impl Node {
|
|||
updated_at: timestamp_ms,
|
||||
anchors: vec![],
|
||||
recent_peers: vec![],
|
||||
preferred_peers: vec![],
|
||||
public_visible: true,
|
||||
avatar_cid,
|
||||
})
|
||||
|
|
@ -2183,10 +2184,9 @@ impl Node {
|
|||
let recent_peers = self.current_recent_peers().await;
|
||||
let profile = {
|
||||
let storage = self.storage.get().await;
|
||||
let preferred_peers = storage.list_preferred_peers().unwrap_or_default();
|
||||
|
||||
// v0.8: the network-id-keyed profile row is TOPOLOGY ONLY
|
||||
// (anchors, recent_peers, preferred_peers). Persona fields
|
||||
// (anchors, recent_peers). Persona fields
|
||||
// (display_name, bio, avatar_cid, public_visible) live
|
||||
// exclusively on posting-id-keyed rows written by profile
|
||||
// posts — never copy them onto the network row, or a legacy
|
||||
|
|
@ -2198,7 +2198,6 @@ impl Node {
|
|||
updated_at: now,
|
||||
anchors,
|
||||
recent_peers,
|
||||
preferred_peers,
|
||||
public_visible: true,
|
||||
avatar_cid: None,
|
||||
};
|
||||
|
|
@ -3438,7 +3437,6 @@ impl Node {
|
|||
updated_at: now,
|
||||
anchors: vec![],
|
||||
recent_peers: vec![],
|
||||
preferred_peers: vec![],
|
||||
public_visible: visible,
|
||||
avatar_cid: None,
|
||||
},
|
||||
|
|
@ -3630,8 +3628,7 @@ impl Node {
|
|||
let now = control_post.timestamp_ms;
|
||||
|
||||
// Clean up blob storage local-side. Blobs in remote holders become
|
||||
// orphans and get evicted naturally via LRU — BlobDeleteNotice is
|
||||
// gone in v0.6.2.
|
||||
// orphans and get evicted naturally via LRU.
|
||||
let blob_cids = {
|
||||
let storage = self.storage.get().await;
|
||||
let cids = storage.delete_blobs_for_post(post_id)?;
|
||||
|
|
@ -3805,7 +3802,7 @@ impl Node {
|
|||
storage.store_post_with_visibility(&new_post_id, &new_post, &new_vis)?;
|
||||
}
|
||||
|
||||
// delete_post already pushes the DeleteRecord.
|
||||
// delete_post propagates the deletion as a signed control post.
|
||||
// Replacement post propagates via the CDN to remaining recipients.
|
||||
self.delete_post(post_id).await?;
|
||||
|
||||
|
|
@ -4021,7 +4018,7 @@ impl Node {
|
|||
{
|
||||
let on_cooldown = {
|
||||
let storage = self.storage.get().await;
|
||||
storage.is_relay_cooldown(&peer_id, 300_000).unwrap_or(false)
|
||||
storage.is_relay_cooldown(&peer_id, RELAY_COOLDOWN_MS).unwrap_or(false)
|
||||
};
|
||||
|
||||
if !on_cooldown {
|
||||
|
|
@ -4041,7 +4038,7 @@ impl Node {
|
|||
);
|
||||
|
||||
let intro_result = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(15),
|
||||
std::time::Duration::from_secs(RELAY_INTRO_TIMEOUT_SECS),
|
||||
self.network.send_relay_introduce_standalone(relay_peer, &peer_id, *ttl),
|
||||
).await;
|
||||
|
||||
|
|
@ -4278,7 +4275,7 @@ impl Node {
|
|||
|
||||
/// Start pull cycle: Protocol v4 tiered pull — 60s ticks, full pull on first tick,
|
||||
/// then only pull for stale authors (last_sync_ms > 4 hours old).
|
||||
pub fn start_pull_cycle(self: &Arc<Self>, _interval_secs: u64) -> tokio::task::JoinHandle<()> {
|
||||
pub fn start_pull_cycle(self: &Arc<Self>) -> tokio::task::JoinHandle<()> {
|
||||
let node = Arc::clone(self);
|
||||
tokio::spawn(async move {
|
||||
let mut interval =
|
||||
|
|
@ -4404,7 +4401,7 @@ impl Node {
|
|||
})
|
||||
}
|
||||
|
||||
/// Start recovery loop: triggered when mesh drops below 2 connections.
|
||||
/// Start recovery loop: triggered when the mesh empties completely.
|
||||
/// Immediately reconnects to anchors and requests referrals.
|
||||
pub fn start_recovery_loop(&self) -> tokio::task::JoinHandle<()> {
|
||||
let network = Arc::clone(&self.network);
|
||||
|
|
@ -5029,18 +5026,6 @@ impl Node {
|
|||
})
|
||||
}
|
||||
|
||||
/// No-op since v0.7.2: the `portmapper::Client` held inside the active
|
||||
/// `PortMapping` auto-renews internally in its own background service.
|
||||
/// Retained for API compatibility with callers in CLI and Tauri until
|
||||
/// they're cleaned up. Returns `None`.
|
||||
///
|
||||
/// TODO(v0.7.x): wire a watcher on `mapping.watch_external()` to clear
|
||||
/// anchor mode if the external address stays `None` for more than ~5min
|
||||
/// (parity with the old "3 renewal failures" behavior).
|
||||
pub fn start_upnp_renewal_cycle(&self) -> Option<tokio::task::JoinHandle<()>> {
|
||||
None
|
||||
}
|
||||
|
||||
// --- HTTP Post Delivery ---
|
||||
|
||||
/// Start the HTTP server for serving public posts to browsers.
|
||||
|
|
@ -5056,9 +5041,6 @@ impl Node {
|
|||
}
|
||||
let storage = Arc::clone(&self.storage);
|
||||
let blob_store = Arc::clone(&self.blob_store);
|
||||
let downstream_addrs = Arc::new(tokio::sync::Mutex::new(
|
||||
std::collections::HashMap::<[u8; 32], Vec<std::net::SocketAddr>>::new(),
|
||||
));
|
||||
|
||||
// Advertise HTTP capability to peers
|
||||
let http_addr = self.network.http_addr();
|
||||
|
|
@ -5076,7 +5058,7 @@ impl Node {
|
|||
|
||||
info!("Starting HTTP server on TCP port {}", port);
|
||||
Some(tokio::spawn(async move {
|
||||
if let Err(e) = crate::http::run_http_server(port, storage, blob_store, downstream_addrs).await {
|
||||
if let Err(e) = crate::http::run_http_server(port, storage, blob_store).await {
|
||||
warn!("HTTP server stopped: {}", e);
|
||||
}
|
||||
}))
|
||||
|
|
@ -6963,7 +6945,8 @@ pub fn compute_blob_priority_standalone(
|
|||
|
||||
impl Node {
|
||||
/// Start the active replication cycle: periodically ask peers to hold our
|
||||
/// under-replicated recent content. Only Available/Persistent devices initiate.
|
||||
/// under-replicated recent content. All devices initiate — phones need
|
||||
/// their content replicated before they go to sleep.
|
||||
pub fn start_replication_cycle(self: &Arc<Self>, interval_secs: u64) -> tokio::task::JoinHandle<()> {
|
||||
let node = Arc::clone(self);
|
||||
tokio::spawn(async move {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue