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:
+18
-16
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user