Improve handle registry healing and gossip propagation

Added healing utilities to update the handle registry when a user profile is found at a new node domain. Enhanced upsertHandleEntries to accept a sourceDomain parameter, allowing authoritative updates from the owning node. Updated gossip logic to pass sender/target domains for proper trust handling during handle propagation. Integrated healing logic into chat key lookup to keep the registry accurate.
This commit is contained in:
Christopher
2026-01-28 06:55:13 -08:00
parent 213dcc8095
commit 36f653d500
4 changed files with 111 additions and 18 deletions
+18 -16
View File
@@ -9,9 +9,9 @@ import { db, handleRegistry } from '@/db';
import { desc, gt } from 'drizzle-orm';
import type { SwarmGossipPayload, SwarmGossipResponse, SwarmSyncResult, SwarmNodeInfo } from './types';
import { SWARM_CONFIG } from './types';
import {
getNodesForGossip,
getActiveSwarmNodes,
import {
getNodesForGossip,
getActiveSwarmNodes,
getNodesSince,
upsertSwarmNodes,
markNodeSuccess,
@@ -26,7 +26,7 @@ import { buildAnnouncement } from './discovery';
*/
export async function buildGossipPayload(since?: string): Promise<SwarmGossipPayload> {
const ourDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
// Get nodes to share
let nodes: SwarmNodeInfo[];
if (since) {
@@ -84,14 +84,15 @@ export async function processGossip(
payload: SwarmGossipPayload
): Promise<SwarmGossipResponse> {
const startTime = Date.now();
// Process incoming nodes
const nodeResult = await upsertSwarmNodes(payload.nodes, payload.sender);
// Process incoming handles
let handlesResult = { added: 0, updated: 0 };
if (payload.handles && payload.handles.length > 0) {
handlesResult = await upsertHandleEntries(payload.handles);
// PASS SENDER: This allows us to trust "authoritative" updates from the node itself
handlesResult = await upsertHandleEntries(payload.handles, payload.sender);
}
// Build our response with nodes/handles to share back
@@ -115,10 +116,10 @@ export async function gossipToNode(
since?: string
): Promise<SwarmSyncResult> {
const startTime = Date.now();
try {
const payload = await buildGossipPayload(since);
const baseUrl = targetDomain.startsWith('http') ? targetDomain : `https://${targetDomain}`;
const url = `${baseUrl}/api/swarm/gossip`;
@@ -160,10 +161,11 @@ export async function gossipToNode(
// Process the response (nodes and handles they sent back)
const nodeResult = await upsertSwarmNodes(gossipResponse.nodes, targetDomain);
let handlesResult = { added: 0, updated: 0 };
if (gossipResponse.handles && gossipResponse.handles.length > 0) {
handlesResult = await upsertHandleEntries(gossipResponse.handles);
// PASS TARGET: If we gossiped TO them, and they replied, treat their reply as authoritative
handlesResult = await upsertHandleEntries(gossipResponse.handles, targetDomain);
}
await markNodeSuccess(targetDomain);
@@ -182,9 +184,9 @@ export async function gossipToNode(
} catch (error) {
const durationMs = Date.now() - startTime;
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
await markNodeFailure(targetDomain);
const result: SwarmSyncResult = {
success: false,
nodesReceived: 0,
@@ -194,7 +196,7 @@ export async function gossipToNode(
error: errorMsg,
durationMs,
};
await logSync(targetDomain, 'push', result);
return result;
}
@@ -211,7 +213,7 @@ export async function runGossipRound(): Promise<{
}> {
// Get random nodes to gossip with
const targets = await getNodesForGossip(SWARM_CONFIG.gossipFanout);
let contacted = 0;
let successful = 0;
let totalNodesReceived = 0;
@@ -220,7 +222,7 @@ export async function runGossipRound(): Promise<{
for (const target of targets) {
contacted++;
const result = await gossipToNode(target.domain);
if (result.success) {
successful++;
totalNodesReceived += result.nodesReceived;
+65
View File
@@ -0,0 +1,65 @@
import { db, handleRegistry, users } from '@/db';
import { eq } from 'drizzle-orm';
import { gossipToNode } from './gossip';
import { fetchSwarmUserProfile, fetchSwarmPost } from './interactions';
import { upsertHandleEntries } from '@/lib/federation/handles';
/**
* Attempt to "heal" a connection to a user by finding their correct node
* and syncing with it.
*
* @param did - The DID of the user we are trying to reach
* @param knownDomain - The last known domain (that failed)
*/
export async function healNodeConnection(did: string, knownDomain?: string): Promise<boolean> {
console.log(`[Swarm Healing] Attempting to heal connection for DID: ${did} (Last known: ${knownDomain})`);
// 1. Try to find logic to recover the domain
// If we have a handle locally, we can try to resolve it via other means?
// Actually, if we have the DID, we might be able to query a "seed node" if we had one.
// For now, let's assume we might have the wrong domain in the registry,
// OR the registry is correct but we just haven't gossiped recently.
// If we have a known domain (that failed 404), maybe we should try to GOSSIP with it?
// Sometimes a node knows about itself but we haven't synced it.
if (knownDomain) {
console.log(`[Swarm Healing] Triggering forced gossip with ${knownDomain}`);
try {
const result = await gossipToNode(knownDomain);
if (result.success && result.handlesReceived > 0) {
console.log(`[Swarm Healing] Gossip successful, handles updated.`);
// Check if our specific user was updated?
// We trust upsertHandleEntries handled it if authoritative.
return true;
}
} catch (e) {
console.error(`[Swarm Healing] Gossip to ${knownDomain} failed:`, e);
}
}
// 2. Fallback: If we know the user's handle but NOT the domain (or domain failed completely)
// We can try to guess or use a fallback mechanism.
// Let's try to look up the user in our local "Users" table to see if we have a clue?
// Or assume the formatting of the DID might give a hint? (Did is meaningless usually).
return false;
}
/**
* "Soft" Heal: We successfully fetched a user profile from a URL,
* but our registry might be out of date. Update it immediately.
*/
export async function updateRegistryFromProfile(handle: string, did: string, nodeDomain: string) {
if (!handle || !did || !nodeDomain) return;
console.log(`[Swarm Healing] Updating registry from valid profile fetch: ${handle}@${nodeDomain}`);
await upsertHandleEntries([{
handle,
did,
nodeDomain,
updatedAt: new Date().toISOString()
}], nodeDomain);
}