soulprint-network 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
- "codeHash": "f950501263286e0f1beb6035756d5b2bd64e39a786fea55be1cdfff475d11c70",
3
- "codeHashHex": "0xf950501263286e0f1beb6035756d5b2bd64e39a786fea55be1cdfff475d11c70",
4
- "computedAt": "2026-03-01T03:42:20.739Z",
2
+ "codeHash": "3e63098b6a2421a17bad6792cbc231982327b32c2e77d0ff2be0eda630690f8c",
3
+ "codeHashHex": "0x3e63098b6a2421a17bad6792cbc231982327b32c2e77d0ff2be0eda630690f8c",
4
+ "computedAt": "2026-03-01T03:57:58.085Z",
5
5
  "fileCount": 25,
6
6
  "files": [
7
7
  "blockchain/NullifierRegistryClient.ts",
package/dist/index.d.ts CHANGED
@@ -1,4 +1,2 @@
1
- export { startValidatorNode, setP2PNode, submitToNode, attestBot, getBotReputation, getNodeInfo, BOOTSTRAP_NODES, } from "./validator.js";
1
+ export { startValidatorNode, submitToNode, attestBot, getBotReputation, getNodeInfo, BOOTSTRAP_NODES, } from "./validator.js";
2
2
  export type { NodeVerifyResult } from "./validator.js";
3
- export { createSoulprintP2PNode, publishAttestationP2P, onAttestationReceived, getP2PStats, stopP2PNode, TOPIC_ATTESTATIONS, TOPIC_NULLIFIERS, MAINNET_BOOTSTRAP, } from "./p2p.js";
4
- export type { P2PConfig, P2PStats, SoulprintP2PNode } from "./p2p.js";
package/dist/index.js CHANGED
@@ -1,2 +1 @@
1
- export { startValidatorNode, setP2PNode, submitToNode, attestBot, getBotReputation, getNodeInfo, BOOTSTRAP_NODES, } from "./validator.js";
2
- export { createSoulprintP2PNode, publishAttestationP2P, onAttestationReceived, getP2PStats, stopP2PNode, TOPIC_ATTESTATIONS, TOPIC_NULLIFIERS, MAINNET_BOOTSTRAP, } from "./p2p.js";
1
+ export { startValidatorNode, submitToNode, attestBot, getBotReputation, getNodeInfo, BOOTSTRAP_NODES, } from "./validator.js";
package/dist/p2p.d.ts CHANGED
@@ -1,38 +1,33 @@
1
1
  /**
2
- * Soulprint P2P Layer Phase 5
3
- *
4
- * libp2p con:
5
- * - TCP transport
6
- * - Noise encryption
7
- * - Yamux multiplexing
8
- * - Kademlia DHT (peer discovery internet)
9
- * - GossipSub (attestation pub/sub)
10
- * - mDNS (descubrimiento en LAN)
11
- * - Bootstrap nodes (entry points a la red)
2
+ * p2p.tsstub (v0.6.0)
3
+ * libp2p removed. The blockchain IS the network.
4
+ * This file exists only for backward compatibility with any external imports.
5
+ * @deprecated Use PeerRegistryClient for peer discovery.
12
6
  */
13
- import type { Libp2p } from "libp2p";
14
- import type { BotAttestation } from "soulprint-core";
15
7
  export declare const TOPIC_ATTESTATIONS = "soulprint:attestations:v1";
16
8
  export declare const TOPIC_NULLIFIERS = "soulprint:nullifiers:v1";
17
9
  export declare const MAINNET_BOOTSTRAP: string[];
18
- export interface P2PConfig {
19
- /** Puerto TCP para libp2p (default: HTTP_PORT + 2000, e.g. 6888) */
20
- port: number;
21
- /** Multiaddrs de nodos bootstrap (para conectar a la red principal) */
22
- bootstraps: string[];
23
- /** true = solo mDNS (desarrollo local). false = DHT público activado */
24
- localOnly: boolean;
25
- }
26
- export interface P2PStats {
10
+ export type P2PConfig = {
11
+ port?: number;
12
+ bootstraps?: string[];
13
+ localOnly?: boolean;
14
+ };
15
+ export type P2PStats = {
27
16
  peerId: string;
28
17
  peers: number;
29
- multiaddrs: string[];
30
18
  pubsubPeers: number;
31
- }
32
- export type SoulprintP2PNode = Libp2p;
33
- export declare function createSoulprintP2PNode(config: P2PConfig): Promise<SoulprintP2PNode>;
34
- export declare function publishAttestationP2P(node: SoulprintP2PNode, att: BotAttestation): Promise<number>;
35
- export declare function onAttestationReceived(node: SoulprintP2PNode, handler: (att: BotAttestation, fromPeer: string) => void): void;
36
- export declare function getP2PStats(node: SoulprintP2PNode): P2PStats;
37
- export declare function stopP2PNode(node: SoulprintP2PNode): Promise<void>;
38
- export declare function dialP2PPeer(node: SoulprintP2PNode, maddrStr: string, timeoutMs?: number): Promise<boolean>;
19
+ multiaddrs: string[];
20
+ };
21
+ export type SoulprintP2PNode = never;
22
+ /** @deprecated No-op stub */
23
+ export declare function createSoulprintP2PNode(_cfg?: P2PConfig): Promise<never>;
24
+ /** @deprecated No-op stub */
25
+ export declare function publishAttestationP2P(_node: any, _att: any): Promise<number>;
26
+ /** @deprecated No-op stub */
27
+ export declare function onAttestationReceived(_node: any, _cb: any): void;
28
+ /** @deprecated No-op stub */
29
+ export declare function getP2PStats(_node: any): P2PStats;
30
+ /** @deprecated No-op stub */
31
+ export declare function stopP2PNode(_node: any): Promise<void>;
32
+ /** @deprecated No-op stub */
33
+ export declare function dialP2PPeer(_node: any, _ma: string): Promise<boolean>;
package/dist/p2p.js CHANGED
@@ -1,148 +1,29 @@
1
1
  /**
2
- * Soulprint P2P Layer Phase 5
3
- *
4
- * libp2p con:
5
- * - TCP transport
6
- * - Noise encryption
7
- * - Yamux multiplexing
8
- * - Kademlia DHT (peer discovery internet)
9
- * - GossipSub (attestation pub/sub)
10
- * - mDNS (descubrimiento en LAN)
11
- * - Bootstrap nodes (entry points a la red)
2
+ * p2p.tsstub (v0.6.0)
3
+ * libp2p removed. The blockchain IS the network.
4
+ * This file exists only for backward compatibility with any external imports.
5
+ * @deprecated Use PeerRegistryClient for peer discovery.
12
6
  */
13
- import { createLibp2p } from "libp2p";
14
- import { tcp } from "@libp2p/tcp";
15
- import { noise } from "@chainsafe/libp2p-noise";
16
- import { yamux } from "@chainsafe/libp2p-yamux";
17
- import { kadDHT } from "@libp2p/kad-dht";
18
- import { gossipsub } from "@chainsafe/libp2p-gossipsub";
19
- import { mdns } from "@libp2p/mdns";
20
- import { bootstrap } from "@libp2p/bootstrap";
21
- import { identify } from "@libp2p/identify";
22
- import { ping } from "@libp2p/ping";
23
- import { fromString, toString } from "uint8arrays";
24
- // ─── Topics ──────────────────────────────────────────────────────────────────
25
7
  export const TOPIC_ATTESTATIONS = "soulprint:attestations:v1";
26
8
  export const TOPIC_NULLIFIERS = "soulprint:nullifiers:v1";
27
- // ─── Bootstrap nodes públicos (mainnet) ──────────────────────────────────────
28
- // Vacíos hasta que desplegamos nodos públicos.
29
- // Se pueden pasar vía SOULPRINT_BOOTSTRAP=multiaddr1,multiaddr2
30
9
  export const MAINNET_BOOTSTRAP = [];
31
- // ─── Crear nodo libp2p ────────────────────────────────────────────────────────
32
- export async function createSoulprintP2PNode(config) {
33
- // Peer discovery: siempre mDNS (LAN) + bootstrap si hay nodos configurados
34
- const peerDiscovery = [mdns()];
35
- if (config.bootstraps.length > 0) {
36
- peerDiscovery.push(bootstrap({ list: config.bootstraps }));
37
- }
38
- const node = await createLibp2p({
39
- addresses: {
40
- listen: [`/ip4/0.0.0.0/tcp/${config.port}`],
41
- },
42
- transports: [tcp()],
43
- connectionEncrypters: [noise()],
44
- streamMuxers: [yamux()],
45
- peerDiscovery,
46
- services: {
47
- // Kademlia DHT — descubrimiento de peers en internet
48
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
- dht: kadDHT({ clientMode: false }),
50
- // GossipSub — broadcast de attestations
51
- pubsub: gossipsub({
52
- allowPublishToZeroTopicPeers: true, // publica aunque no haya peers aún
53
- emitSelf: false,
54
- // Thresholds permisivos para redes pequeñas
55
- scoreThresholds: {
56
- gossipThreshold: -Infinity,
57
- publishThreshold: -Infinity,
58
- graylistThreshold: -Infinity,
59
- },
60
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
- }),
62
- // Identify — intercambio de metadatos entre peers
63
- identify: identify(),
64
- // Ping — requerido por KadDHT para health checks
65
- ping: ping(),
66
- },
67
- });
68
- await node.start();
69
- // Suscribirse a los topics de Soulprint
70
- node.services.pubsub.subscribe(TOPIC_ATTESTATIONS);
71
- node.services.pubsub.subscribe(TOPIC_NULLIFIERS);
72
- return node;
10
+ /** @deprecated No-op stub */
11
+ export async function createSoulprintP2PNode(_cfg) {
12
+ throw new Error("libp2p removed in v0.6.0 use blockchain peer discovery");
73
13
  }
74
- // ─── Publicar attestation via P2P ────────────────────────────────────────────
75
- export async function publishAttestationP2P(node, att) {
76
- try {
77
- const data = fromString(JSON.stringify(att), "utf8");
78
- const result = await node.services.pubsub.publish(TOPIC_ATTESTATIONS, data);
79
- return result?.recipients?.length ?? 0;
80
- }
81
- catch {
82
- return 0; // sin peers aún — no es error
83
- }
14
+ /** @deprecated No-op stub */
15
+ export async function publishAttestationP2P(_node, _att) {
16
+ return 0;
84
17
  }
85
- // ─── Recibir attestations via P2P ─────────────────────────────────────────────
86
- export function onAttestationReceived(node, handler) {
87
- node.services.pubsub.addEventListener("message", (evt) => {
88
- if (evt.detail?.topic !== TOPIC_ATTESTATIONS)
89
- return;
90
- try {
91
- const att = JSON.parse(toString(evt.detail.data, "utf8"));
92
- const fromPeer = evt.detail?.from?.toString() ?? "unknown";
93
- handler(att, fromPeer);
94
- }
95
- catch {
96
- // mensaje malformado — ignorar
97
- }
98
- });
18
+ /** @deprecated No-op stub */
19
+ export function onAttestationReceived(_node, _cb) { }
20
+ /** @deprecated No-op stub */
21
+ export function getP2PStats(_node) {
22
+ return { peerId: "", peers: 0, pubsubPeers: 0, multiaddrs: [] };
99
23
  }
100
- // ─── Stats ───────────────────────────────────────────────────────────────────
101
- export function getP2PStats(node) {
102
- const pubsub = node.services.pubsub;
103
- return {
104
- peerId: node.peerId.toString(),
105
- peers: node.getPeers().length,
106
- multiaddrs: node.getMultiaddrs().map((m) => m.toString()),
107
- pubsubPeers: pubsub.getPeers?.()?.length ?? 0,
108
- };
109
- }
110
- // ─── Graceful shutdown ────────────────────────────────────────────────────────
111
- export async function stopP2PNode(node) {
112
- try {
113
- await node.stop();
114
- }
115
- catch { /* ignorar */ }
116
- }
117
- // ── dial a peer by multiaddr string (best-effort — HTTP gossip is the primary mechanism) ──
118
- // Note: in WSL2/NAT environments mDNS multicast doesn't work.
119
- // This function attempts libp2p TCP dial using the peer's advertised multiaddrs.
120
- // Falls back gracefully — HTTP gossip layer (known_peers) handles message propagation.
121
- export async function dialP2PPeer(node, maddrStr, timeoutMs = 5_000) {
122
- try {
123
- // Extract PeerId from the multiaddr string and add multiaddr to peer store
124
- // so libp2p can use its own internal Multiaddr class (avoids version mismatch)
125
- const peerIdStr = maddrStr.split("/p2p/")[1];
126
- if (!peerIdStr)
127
- return false;
128
- // Use peer store to register the address, then dial by PeerId
129
- // This lets libp2p use its own Multiaddr parsing internally
130
- const { peerIdFromString } = await import("@libp2p/peer-id");
131
- const peerId = peerIdFromString(peerIdStr);
132
- // Register the address in the peer store
133
- await node.peerStore.merge(peerId, {
134
- multiaddrs: [maddrStr],
135
- }).catch(() => {
136
- // peerStore.merge signature varies by libp2p version — try patch
137
- return node.peerStore.patch(peerId, { multiaddrs: [maddrStr] }).catch(() => { });
138
- });
139
- await Promise.race([
140
- node.dial(peerId),
141
- new Promise((_, rej) => setTimeout(() => rej(new Error("dial timeout")), timeoutMs)),
142
- ]);
143
- return true;
144
- }
145
- catch {
146
- return false;
147
- }
24
+ /** @deprecated No-op stub */
25
+ export async function stopP2PNode(_node) { }
26
+ /** @deprecated No-op stub */
27
+ export async function dialP2PPeer(_node, _ma) {
28
+ return false;
148
29
  }
package/dist/server.js CHANGED
@@ -1,129 +1,77 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Soulprint Validator Node — entrypoint
3
+ * Soulprint Validator Node — entrypoint v0.6.0
4
4
  *
5
- * Arranca:
6
- * 1. HTTP server (port 4888) — clientes y legado
7
- * 2. libp2p P2P node (port 6888) — Kademlia DHT + GossipSub + mDNS
5
+ * Pure blockchain architecture — no libp2p.
6
+ * The blockchain IS the network.
8
7
  */
9
- import { startValidatorNode, setP2PNode, setPeerRegistryClient, getNodeState, setLastSyncTs } from "./validator.js";
8
+ import { startValidatorNode, setPeerRegistryClient, setNullifierRegistry, setReputationRegistry, getNodeState, setLastSyncTs } from "./validator.js";
10
9
  import { computeHash, saveState } from "./state/StateStore.js";
11
- import { createSoulprintP2PNode, MAINNET_BOOTSTRAP, stopP2PNode } from "./p2p.js";
12
10
  import { PeerRegistryClient } from "./blockchain/PeerRegistryClient.js";
11
+ import { NullifierRegistryClient } from "./blockchain/NullifierRegistryClient.js";
12
+ import { ReputationRegistryClient } from "./blockchain/ReputationRegistryClient.js";
13
13
  // ─── Config ──────────────────────────────────────────────────────────────────
14
- const HTTP_PORT = parseInt(process.env.SOULPRINT_PORT ?? "4888");
15
- const P2P_PORT = parseInt(process.env.SOULPRINT_P2P_PORT ?? String(HTTP_PORT + 2000));
14
+ const HTTP_PORT = parseInt(process.env.PORT ?? process.env.SOULPRINT_PORT ?? "4888");
15
+ const adminPrivateKey = process.env.ADMIN_PRIVATE_KEY;
16
+ const adminToken = process.env.ADMIN_TOKEN;
16
17
  globalThis._startTime = Date.now();
17
- // Bootstrap nodes: variables de entorno o mainnet predefinidos
18
- const bootstrapEnv = (process.env.SOULPRINT_BOOTSTRAP ?? "")
19
- .split(",")
20
- .map(s => s.trim())
21
- .filter(Boolean);
22
- const bootstraps = bootstrapEnv.length > 0 ? bootstrapEnv : MAINNET_BOOTSTRAP;
23
- const localOnly = bootstraps.length === 0;
24
- // ─── Arranque ────────────────────────────────────────────────────────────────
18
+ // ─── Start HTTP server ────────────────────────────────────────────────────────
25
19
  const httpServer = startValidatorNode(HTTP_PORT);
26
- let p2pNode = null;
27
- // Intentar arrancar P2P (no fatal si falla)
28
- try {
29
- console.log(`\n🔗 Arrancando nodo P2P en puerto ${P2P_PORT}...`);
30
- console.log(` Modo: ${localOnly ? "local (mDNS only)" : `red principal + ${bootstraps.length} bootstrap(s)`}`);
31
- p2pNode = await createSoulprintP2PNode({ port: P2P_PORT, bootstraps, localOnly });
32
- setP2PNode(p2pNode);
33
- console.log(`✅ P2P activo`);
34
- console.log(` Peer ID: ${p2pNode.peerId.toString()}`);
35
- console.log(` Multiaddrs: ${p2pNode.getMultiaddrs().map((m) => m.toString()).join(", ") || "(pendiente)"}`);
36
- console.log(`\n Gossip: HTTP fallback + GossipSub P2P`);
37
- console.log(` Discovery: mDNS${bootstraps.length > 0 ? " + DHT + Bootstrap" : " (LAN only)"}\n`);
38
- if (localOnly) {
39
- console.log(` 💡 Para conectarte a la red principal, configura SOULPRINT_BOOTSTRAP`);
40
- console.log(` con multiaddrs de nodos conocidos y reinicia.\n`);
41
- }
42
- }
43
- catch (err) {
44
- console.warn(`⚠️ P2P no disponible — solo HTTP gossip activo`);
45
- console.warn(` Error: ${err?.message ?? String(err)}\n`);
46
- }
47
- // ─── On-chain PeerRegistry (auto-registro + bootstrap desde chain) ────────────
48
- const adminPrivateKey = process.env.ADMIN_PRIVATE_KEY;
49
- const peerRegistry = new PeerRegistryClient({
50
- privateKey: adminPrivateKey,
51
- });
20
+ // ─── Init blockchain clients ──────────────────────────────────────────────────
21
+ const peerRegistry = new PeerRegistryClient({ privateKey: adminPrivateKey });
22
+ const nullifierRegistry = new NullifierRegistryClient({ privateKey: adminPrivateKey });
23
+ const reputationRegistry = new ReputationRegistryClient({ privateKey: adminPrivateKey });
52
24
  setPeerRegistryClient(peerRegistry);
53
- // Bootstrap P2P + register self after node is ready (non-blocking)
25
+ setNullifierRegistry(nullifierRegistry);
26
+ setReputationRegistry(reputationRegistry);
27
+ // ─── Bootstrap: read on-chain peers, register self ────────────────────────────
54
28
  setTimeout(async () => {
55
29
  try {
56
- // 1. Leer peers on-chain y hacer dial P2P a sus multiaddrs
57
30
  const chainPeers = await peerRegistry.getAllPeers().catch(() => []);
58
- if (chainPeers.length > 0) {
59
- console.log(`[peer-registry] 🔗 ${chainPeers.length} peer(s) encontrados on-chain — conectando...`);
60
- for (const peer of chainPeers) {
61
- try {
62
- if (!peer.multiaddr)
63
- continue;
64
- // Si es multiaddr P2P (/ip4/.../tcp/.../p2p/...) intentamos dial
65
- if (peer.multiaddr.startsWith("/ip4") || peer.multiaddr.startsWith("/dns")) {
66
- // Agregar como peer conocido via HTTP bootstrap (más estable que dial directo)
67
- const httpUrl = peer.multiaddr.replace(/\/ip4\/([^/]+)\/tcp\/(\d+).*/, "http://$1:$2").replace("/p2p/", "");
68
- if (httpUrl.startsWith("http")) {
69
- const PROTOCOL_HASH = "dfe1ccca1270ec86f93308dc4b981bab1d6bd74bdcc334059f4380b407ca07ca";
70
- await fetch(`http://localhost:${HTTP_PORT}/peers/register`, {
71
- method: "POST",
72
- headers: { "Content-Type": "application/json" },
73
- body: JSON.stringify({ url: httpUrl, protocol_hash: PROTOCOL_HASH }),
74
- signal: AbortSignal.timeout(5_000),
75
- }).catch(() => null);
76
- }
77
- console.log(`[peer-registry] ✅ Peer P2P registrado: ${peer.multiaddr.slice(0, 40)}`);
78
- }
79
- else if (peer.multiaddr.startsWith("http")) {
80
- // HTTP peer — registrar vía /peers/register
81
- const PROTOCOL_HASH = "dfe1ccca1270ec86f93308dc4b981bab1d6bd74bdcc334059f4380b407ca07ca";
82
- await fetch(`http://localhost:${HTTP_PORT}/peers/register`, {
83
- method: "POST",
84
- headers: { "Content-Type": "application/json" },
85
- body: JSON.stringify({ url: peer.multiaddr, protocol_hash: PROTOCOL_HASH }),
86
- signal: AbortSignal.timeout(5_000),
87
- }).catch(() => null);
88
- console.log(`[peer-registry] 🌐 Peer HTTP registrado: ${peer.multiaddr}`);
89
- }
90
- }
91
- catch (e) {
92
- console.warn(`[peer-registry] ⚠️ No se pudo conectar a ${peer.multiaddr}: ${e.message}`);
31
+ console.log(`[peer-registry] ${chainPeers.length} peer(s) on-chain`);
32
+ // Register HTTP peers
33
+ for (const peer of chainPeers) {
34
+ try {
35
+ if (!peer.multiaddr)
36
+ continue;
37
+ if (peer.multiaddr.startsWith("http")) {
38
+ const PROTOCOL_HASH = "dfe1ccca1270ec86f93308dc4b981bab1d6bd74bdcc334059f4380b407ca07ca";
39
+ await fetch(`http://localhost:${HTTP_PORT}/peers/register`, {
40
+ method: "POST",
41
+ headers: { "Content-Type": "application/json" },
42
+ body: JSON.stringify({ url: peer.multiaddr, protocol_hash: PROTOCOL_HASH }),
43
+ signal: AbortSignal.timeout(5_000),
44
+ }).catch(() => null);
45
+ console.log(`[peer-registry] 🌐 Peer HTTP registrado: ${peer.multiaddr}`);
93
46
  }
94
47
  }
48
+ catch (e) {
49
+ console.warn(`[peer-registry] ⚠️ No se pudo conectar a ${peer.multiaddr}: ${e.message}`);
50
+ }
95
51
  }
96
- else {
97
- console.log("[peer-registry] ℹ️ No hay peers registrados on-chain aún — primer nodo de la red");
98
- }
99
- // 2. Registrar self on-chain
52
+ // Register self on-chain
100
53
  if (!adminPrivateKey) {
101
54
  console.warn("[peer-registry] ⚠️ ADMIN_PRIVATE_KEY not set — skipping on-chain registration");
102
55
  return;
103
56
  }
104
- let multiaddr = `http://localhost:${HTTP_PORT}`;
105
- if (p2pNode) {
106
- const addrs = p2pNode.getMultiaddrs().map((m) => m.toString());
107
- const publicAddr = addrs.find(a => !a.includes("127.0.0.1") && !a.includes("/ip4/0.0.0.0"));
108
- multiaddr = publicAddr ?? addrs[0] ?? multiaddr;
109
- }
110
57
  const nodeDid = globalThis._nodeDid ?? `did:soulprint:node:${Date.now()}`;
111
- const nodePeer = p2pNode?.peerId?.toString() ?? "";
112
- await peerRegistry.registerSelf({ peerDid: nodeDid, peerId: nodePeer, multiaddr, score: 0 });
113
- console.log(`[peer-registry] ✅ Registrado on-chain: ${nodeDid.slice(0, 30)}…`);
58
+ await peerRegistry.registerSelf({
59
+ peerDid: nodeDid,
60
+ peerId: "",
61
+ multiaddr: `http://localhost:${HTTP_PORT}`,
62
+ score: 0,
63
+ });
64
+ console.log(`[peer-registry] ✅ Registered on-chain: ${nodeDid.slice(0, 30)}…`);
114
65
  }
115
66
  catch (e) {
116
- console.warn(`[peer-registry] ⚠️ Error en bootstrap on-chain: ${e.message}`);
67
+ console.warn(`[peer-registry] ⚠️ Bootstrap error: ${e.message}`);
117
68
  }
118
69
  }, 3_000);
119
- // ─── HTTP Bootstrap Peers (auto-registro) ─────────────────────────────────────
120
- // SOULPRINT_BOOTSTRAP_HTTP=http://node1:4888,http://node2:4888
121
- // Registra peers HTTP automáticamente al arrancar (útil en WSL2 / Docker / cloud)
70
+ // ─── HTTP Bootstrap Peers ─────────────────────────────────────────────────────
122
71
  const httpBootstraps = (process.env.SOULPRINT_BOOTSTRAP_HTTP ?? "")
123
72
  .split(",").map(s => s.trim()).filter(s => s.startsWith("http"));
124
73
  if (httpBootstraps.length > 0) {
125
74
  console.log(`🔗 Bootstrap HTTP: ${httpBootstraps.length} peer(s) configurados`);
126
- // Esperar 2s a que el HTTP server esté listo antes de registrar
127
75
  setTimeout(async () => {
128
76
  const PROTOCOL_HASH = process.env.SOULPRINT_PROTOCOL_HASH
129
77
  ?? "dfe1ccca1270ec86f93308dc4b981bab1d6bd74bdcc334059f4380b407ca07ca";
@@ -149,9 +97,19 @@ if (httpBootstraps.length > 0) {
149
97
  }
150
98
  }, 2_000);
151
99
  }
152
- // ─── Anti-entropy sync loop (v0.4.4) ─────────────────────────────────────────
153
- // Every 60 seconds: compare state hash with each known peer.
154
- // If diverged, fetch full state and merge locally.
100
+ // ─── HTTP peer sync every 5 min (simple HTTP gossip — no libp2p) ─────────────
101
+ setInterval(async () => {
102
+ try {
103
+ const peers = await peerRegistry.getAllPeers().catch(() => []);
104
+ for (const peer of peers) {
105
+ if (peer.multiaddr?.startsWith("http")) {
106
+ await fetch(`${peer.multiaddr}/state/hash`, { signal: AbortSignal.timeout(3_000) }).catch(() => null);
107
+ }
108
+ }
109
+ }
110
+ catch { }
111
+ }, 5 * 60 * 1000);
112
+ // ─── Anti-entropy sync loop ───────────────────────────────────────────────────
155
113
  setInterval(async () => {
156
114
  const { nullifiers, repStore, peers: knownPeers } = getNodeState();
157
115
  if (knownPeers.length === 0)
@@ -163,17 +121,11 @@ setInterval(async () => {
163
121
  if (!hashRes.ok)
164
122
  continue;
165
123
  const hashData = await hashRes.json();
166
- const peerHash = hashData.hash;
167
- if (peerHash === localHash) {
168
- console.log(`[sync] peer ${peerUrl}: hash match ✅`);
124
+ if (hashData.hash === localHash)
169
125
  continue;
170
- }
171
- // Hashes differ — fetch full state and merge
172
126
  const exportRes = await fetch(`${peerUrl}/state/export`, { signal: AbortSignal.timeout(10_000) });
173
- if (!exportRes.ok) {
174
- console.warn(`[sync] peer ${peerUrl}: export failed (${exportRes.status})`);
127
+ if (!exportRes.ok)
175
128
  continue;
176
- }
177
129
  const peerState = await exportRes.json();
178
130
  const mergeRes = await fetch(`http://localhost:${HTTP_PORT}/state/merge`, {
179
131
  method: "POST",
@@ -182,8 +134,7 @@ setInterval(async () => {
182
134
  signal: AbortSignal.timeout(5_000),
183
135
  });
184
136
  const merged = await mergeRes.json();
185
- console.log(`[sync] peer ${peerUrl}: diverged → merged ${merged.new_nullifiers ?? 0} nullifiers, ${merged.new_attestations ?? 0} attestations`);
186
- // Persist updated state
137
+ console.log(`[sync] peer ${peerUrl}: merged ${merged.new_nullifiers ?? 0} nullifiers, ${merged.new_attestations ?? 0} attestations`);
187
138
  const { nullifiers: n2, repStore: r2, peers: p2 } = getNodeState();
188
139
  const ts = Date.now();
189
140
  saveState({
@@ -204,10 +155,6 @@ setInterval(async () => {
204
155
  // ─── Graceful shutdown ────────────────────────────────────────────────────────
205
156
  async function shutdown(signal) {
206
157
  console.log(`\n${signal} recibido — cerrando...`);
207
- if (p2pNode) {
208
- await stopP2PNode(p2pNode);
209
- console.log(" ✓ Nodo P2P cerrado");
210
- }
211
158
  httpServer.close(() => {
212
159
  console.log(" ✓ HTTP server cerrado");
213
160
  process.exit(0);
@@ -1,6 +1,5 @@
1
1
  import { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { BotAttestation, BotReputation } from "soulprint-core";
3
- import { type SoulprintP2PNode } from "./p2p.js";
4
3
  import { PeerRegistryClient } from "./blockchain/PeerRegistryClient.js";
5
4
  import { NullifierRegistryClient } from "./blockchain/NullifierRegistryClient.js";
6
5
  import { ReputationRegistryClient } from "./blockchain/ReputationRegistryClient.js";
@@ -20,13 +19,6 @@ export declare function setNullifierRegistry(client: NullifierRegistryClient): v
20
19
  export declare function setReputationRegistry(client: ReputationRegistryClient): void;
21
20
  export declare function getNullifierRegistry(): NullifierRegistryClient | null;
22
21
  export declare function getReputationRegistry(): ReputationRegistryClient | null;
23
- /**
24
- * Inyecta el nodo libp2p al validador.
25
- * Cuando se llama:
26
- * 1. Se registra el handler de attestations entrantes por GossipSub
27
- * 2. Desde ese momento, gossipAttestation() también publica por P2P
28
- */
29
- export declare function setP2PNode(node: SoulprintP2PNode): void;
30
22
  /**
31
23
  * Per-DID reputation: score (0-20) + attestation history.
32
24
  * Persisted to disk - survives node restarts.
package/dist/validator.js CHANGED
@@ -16,7 +16,6 @@ import { SoulprintBlockchainClient, loadBlockchainConfig, } from "./blockchain/b
16
16
  import { thresholdsClient, PROTOCOL_THRESHOLDS_ADDRESS, PROTOCOL_THRESHOLDS_CHAIN, } from "./blockchain/protocol-thresholds-client.js";
17
17
  import { getCodeIntegrity, logCodeIntegrity, computeRuntimeHash } from "./code-integrity.js";
18
18
  import { getMCPEntry, getVerifiedMCPEntries, getAllMCPEntries, getRegistryInfo, verifyMCPOnChain, revokeMCPOnChain, registerMCPOnChain, } from "./mcp-registry-client.js";
19
- import { publishAttestationP2P, onAttestationReceived, getP2PStats, dialP2PPeer, } from "./p2p.js";
20
19
  // ── Config ────────────────────────────────────────────────────────────────────
21
20
  const PORT = parseInt(process.env.SOULPRINT_PORT ?? String(PROTOCOL.DEFAULT_HTTP_PORT));
22
21
  const NODE_DIR = join(homedir(), ".soulprint", "node");
@@ -25,7 +24,7 @@ const NULLIFIER_DB = join(NODE_DIR, "nullifiers.json");
25
24
  const REPUTE_DB = join(NODE_DIR, "reputation.json");
26
25
  const PEERS_DB = join(NODE_DIR, "peers.json");
27
26
  const AUDIT_DB = join(NODE_DIR, "audit.json");
28
- const VERSION = "0.5.0";
27
+ const VERSION = "0.6.0";
29
28
  const MAX_BODY_BYTES = 64 * 1024;
30
29
  // ── Protocol constants (inamovibles - no cambiar directamente aquí) ───────────
31
30
  const RATE_LIMIT_MS = PROTOCOL.RATE_LIMIT_WINDOW_MS;
@@ -70,8 +69,6 @@ async function refreshThresholds() {
70
69
  }
71
70
  catch { /* usa los valores actuales */ }
72
71
  }
73
- // ── P2P Node (Phase 5) ────────────────────────────────────────────────────────
74
- let p2pNode = null;
75
72
  // ── PeerRegistry Client ───────────────────────────────────────────────────────
76
73
  let peerRegistryClient = null;
77
74
  export function setPeerRegistryClient(client) {
@@ -94,27 +91,6 @@ export function getNullifierRegistry() {
94
91
  export function getReputationRegistry() {
95
92
  return reputationRegistry;
96
93
  }
97
- /**
98
- * Inyecta el nodo libp2p al validador.
99
- * Cuando se llama:
100
- * 1. Se registra el handler de attestations entrantes por GossipSub
101
- * 2. Desde ese momento, gossipAttestation() también publica por P2P
102
- */
103
- export function setP2PNode(node) {
104
- p2pNode = node;
105
- // Recibir attestations de otros nodos via GossipSub
106
- onAttestationReceived(node, (att, fromPeer) => {
107
- // Validar firma antes de aplicar
108
- if (!verifyAttestation(att)) {
109
- console.warn(`[p2p] Attestation inválida de peer ${fromPeer.slice(0, 16)}... - descartada`);
110
- return;
111
- }
112
- // Anti-replay ya está dentro de applyAttestation()
113
- applyAttestation(att);
114
- console.log(`[p2p] Attestation recibida de peer ${fromPeer.slice(0, 16)}... → ${att.target_did.slice(0, 20)}... (${att.value > 0 ? "+" : ""}${att.value})`);
115
- });
116
- console.log(`[p2p] P2P integrado → ${node.peerId.toString().slice(0, 16)}...`);
117
- }
118
94
  // ── Rate limiter ──────────────────────────────────────────────────────────────
119
95
  const rateLimits = new Map();
120
96
  // ── DPoP Nonce Store — anti-replay para request signing ──────────────────────
@@ -248,25 +224,10 @@ function loadPeers() {
248
224
  }
249
225
  function savePeers() { writeFileSync(PEERS_DB, JSON.stringify(peers, null, 2)); }
250
226
  /**
251
- * Gossip: propaga la attestation a la red.
252
- *
253
- * Estrategia:
254
- * 1. P2P GossipSub (Phase 5) - si el nodo libp2p está activo
255
- * 2. HTTP fire-and-forget (Phase 3) - fallback para nodos legacy sin libp2p
256
- *
257
- * Ambos canales son fire-and-forget: no bloquean la respuesta al cliente.
227
+ * Gossip: propaga la attestation a la red via HTTP fire-and-forget.
258
228
  */
259
229
  async function gossipAttestation(att, excludeUrl) {
260
- // ── Canal 1: libp2p GossipSub ─────────────────────────────────────────────
261
- if (p2pNode) {
262
- const recipients = await publishAttestationP2P(p2pNode, att);
263
- if (recipients > 0) {
264
- console.log(`[p2p] Attestation publicada → ${recipients} peer(s) via GossipSub`);
265
- }
266
- }
267
- // ── Canal 2: HTTP gossip con cifrado AES-256-GCM + XOR routing ────────────
268
- // Selección de peers: XOR routing hacia el DID objetivo → O(log n)
269
- // Con ≤10 peers: broadcast total. Con más: solo K=6 más cercanos.
230
+ // HTTP gossip con cifrado AES-256-GCM + XOR routing
270
231
  const targets = selectGossipPeers(peers, att.target_did, excludeUrl);
271
232
  if (targets.length < peers.length - (excludeUrl ? 1 : 0)) {
272
233
  console.log(routingStats(peers.length, targets.length, att.target_did));
@@ -340,26 +301,32 @@ function getIP(req) {
340
301
  }
341
302
  // ── GET /info ─────────────────────────────────────────────────────────────────
342
303
  function handleInfo(res, nodeKeypair) {
343
- const p2pStats = p2pNode ? getP2PStats(p2pNode) : null;
304
+ const addresses = (() => {
305
+ try {
306
+ const { default: addrs } = require("./blockchain/addresses.json");
307
+ return addrs;
308
+ }
309
+ catch {
310
+ return {};
311
+ }
312
+ })();
344
313
  json(res, 200, {
345
314
  node_did: nodeKeypair.did,
346
315
  version: VERSION,
347
316
  protocol: PROTOCOL.VERSION,
348
- protocol_hash: PROTOCOL_HASH, // ← cualquier modificación cambia este hash
317
+ protocol_hash: PROTOCOL_HASH,
318
+ network: "base-sepolia",
319
+ contracts: {
320
+ PeerRegistry: "0x452fb66159dFCfC13f2fD9627aA4c56886BfB15b",
321
+ NullifierRegistry: addresses?.NullifierRegistry ?? "",
322
+ ReputationRegistry: addresses?.ReputationRegistry ?? "",
323
+ },
349
324
  total_verified: Object.keys(nullifiers).length,
350
325
  total_reputation: Object.keys(repStore).length,
351
326
  known_peers: peers.length,
352
327
  supported_countries: ["CO"],
353
- capabilities: ["zk-verify", "anti-sybil", "co-sign", "bot-reputation", "p2p-gossipsub", "credential-validators", "anti-farming"],
328
+ capabilities: ["zk-verify", "anti-sybil", "co-sign", "bot-reputation", "registraduria-validation", "on-chain-state"],
354
329
  rate_limit: `${PROTOCOL.RATE_LIMIT_MAX} req/min per IP`,
355
- // P2P stats (Phase 5)
356
- p2p: p2pStats ? {
357
- enabled: true,
358
- peer_id: p2pStats.peerId,
359
- peers: p2pStats.peers,
360
- pubsub_peers: p2pStats.pubsubPeers,
361
- multiaddrs: p2pStats.multiaddrs,
362
- } : { enabled: false },
363
330
  });
364
331
  }
365
332
  // ── GET /protocol ──────────────────────────────────────────────────────────────
@@ -640,32 +607,6 @@ async function handlePeerRegister(req, res) {
640
607
  }
641
608
  peers.push(url);
642
609
  savePeers();
643
- // ── Auto-dial libp2p layer ──────────────────────────────────────────────────
644
- // WSL2 / NAT: mDNS no funciona → al registrar un peer HTTP, intentamos
645
- // conectar también vía libp2p usando sus multiaddrs del /info endpoint.
646
- if (p2pNode) {
647
- setImmediate(async () => {
648
- try {
649
- const infoRes = await fetch(`${url}/info`, { signal: AbortSignal.timeout(3_000) });
650
- if (infoRes.ok) {
651
- const info = await infoRes.json();
652
- const addrs = info?.p2p?.multiaddrs ?? [];
653
- let dialed = false;
654
- for (const ma of addrs) {
655
- const ok = await dialP2PPeer(p2pNode, ma);
656
- if (ok) {
657
- console.log(`[peer] 🔗 P2P dial OK: ${ma}`);
658
- dialed = true;
659
- break;
660
- }
661
- }
662
- if (!dialed)
663
- console.log(`[peer] ℹ️ P2P dial failed for ${url} (mDNS fallback)`);
664
- }
665
- }
666
- catch { /* non-critical — HTTP gossip is the fallback */ }
667
- });
668
- }
669
610
  json(res, 200, { ok: true, peers: peers.length, protocol_hash: PROTOCOL_HASH });
670
611
  }
671
612
  // ── GET /peers ─────────────────────────────────────────────────────────────────
@@ -1089,9 +1030,7 @@ export function startValidatorNode(port = PORT) {
1089
1030
  }
1090
1031
  // GET /network/stats — stats públicas para la landing page
1091
1032
  if (cleanUrl === "/network/stats" && req.method === "GET") {
1092
- const p2pStats = p2pNode ? getP2PStats(p2pNode) : null;
1093
1033
  const httpPeers = peers.length;
1094
- const libp2pPeers = p2pStats?.peers ?? 0;
1095
1034
  let registeredPeers = 0;
1096
1035
  let nullifiersOnchain = 0;
1097
1036
  let reputationOnchain = 0;
@@ -1116,25 +1055,24 @@ export function startValidatorNode(port = PORT) {
1116
1055
  node_did: nodeKeypair.did.slice(0, 20) + "...",
1117
1056
  version: VERSION,
1118
1057
  protocol_hash: PROTOCOL_HASH.slice(0, 16) + "...",
1058
+ network: "base-sepolia",
1059
+ contracts: {
1060
+ PeerRegistry: "0x452fb66159dFCfC13f2fD9627aA4c56886BfB15b",
1061
+ },
1119
1062
  // identidades y reputación (in-memory cache)
1120
1063
  verified_identities: Object.keys(nullifiers).length,
1121
1064
  reputation_profiles: Object.keys(repStore).length,
1122
- // on-chain state (v0.5.0) — blockchain IS the shared state
1065
+ // on-chain state (v0.6.0) — blockchain IS the shared state
1123
1066
  nullifiers_onchain: nullifiersOnchain,
1124
1067
  reputation_onchain: reputationOnchain,
1125
- // peers — HTTP gossip
1068
+ // peers
1126
1069
  known_peers: httpPeers,
1127
- // peers — libp2p P2P
1128
- p2p_peers: libp2pPeers,
1129
- p2p_pubsub_peers: p2pStats?.pubsubPeers ?? 0,
1130
- p2p_enabled: !!p2pNode,
1131
- total_peers: Math.max(httpPeers, libp2pPeers),
1132
- // on-chain registered peers (PeerRegistry)
1133
1070
  registered_peers: registeredPeers,
1134
- // state sync (v0.5.0 — blockchain is source of truth)
1071
+ total_peers: Math.max(httpPeers, registeredPeers),
1072
+ // state sync
1135
1073
  state_hash: computeHash(Object.keys(nullifiers)).slice(0, 16) + "...",
1136
1074
  last_sync: lastSyncTs,
1137
- // estado general
1075
+ // general
1138
1076
  uptime_ms: Date.now() - (globalThis._startTime ?? Date.now()),
1139
1077
  timestamp: Date.now(),
1140
1078
  mcps_verified: null,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "soulprint-network",
3
- "version": "0.5.1",
4
- "description": "Soulprint validator node \u2014 HTTP server that verifies ZK proofs, co-signs SPTs, anti-Sybil registry",
3
+ "version": "0.6.0",
4
+ "description": "Soulprint validator node HTTP server that verifies ZK proofs, co-signs SPTs, anti-Sybil registry",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {
@@ -35,25 +35,12 @@
35
35
  ],
36
36
  "license": "MIT",
37
37
  "dependencies": {
38
- "@chainsafe/libp2p-gossipsub": "14.1.2",
39
- "@chainsafe/libp2p-noise": "16.1.5",
40
- "@chainsafe/libp2p-yamux": "7.0.4",
41
- "@libp2p/bootstrap": "11.0.47",
42
- "@libp2p/identify": "3.0.39",
43
- "@libp2p/kad-dht": "16.1.3",
44
- "@libp2p/mdns": "11.0.47",
45
- "@libp2p/peer-id": "^6.0.4",
46
- "@libp2p/ping": "2.0.37",
47
- "@libp2p/tcp": "10.1.19",
48
- "@multiformats/multiaddr": "^13.0.1",
49
38
  "ethers": "^6.16.0",
50
- "libp2p": "2.10.0",
51
39
  "nodemailer": "^8.0.1",
52
40
  "otpauth": "^9.5.0",
53
41
  "otplib": "^13.3.0",
54
42
  "soulprint-core": "0.1.11",
55
- "soulprint-zkp": "0.1.6",
56
- "uint8arrays": "5.1.0"
43
+ "soulprint-zkp": "0.1.6"
57
44
  },
58
45
  "devDependencies": {
59
46
  "@types/node": "^20.0.0",
@@ -70,4 +57,4 @@
70
57
  "types": "./dist/index.d.ts"
71
58
  }
72
59
  }
73
- }
60
+ }