yakmesh 2.8.2 → 3.0.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.
- package/CHANGELOG.md +637 -0
- package/CONTRIBUTING.md +42 -0
- package/Caddyfile +77 -0
- package/README.md +119 -29
- package/adapters/adapter-mlv-bible/README.md +124 -0
- package/adapters/adapter-mlv-bible/index.js +400 -0
- package/adapters/chat-mod-adapter.js +532 -0
- package/adapters/content-adapter.js +273 -0
- package/content/api.js +50 -41
- package/content/index.js +2 -2
- package/content/store.js +355 -173
- package/dashboard/index.html +19 -3
- package/database/replication.js +117 -37
- package/docs/CRYPTO-AGILITY.md +204 -0
- package/docs/MTLS-RESEARCH.md +367 -0
- package/docs/NAMCHE-SPEC.md +681 -0
- package/docs/PEERQUANTA-YAKMESH-INTEGRATION.md +407 -0
- package/docs/PRECISION-DISCLOSURE.md +96 -0
- package/docs/README.md +76 -0
- package/docs/ROADMAP-2.4.0.md +447 -0
- package/docs/ROADMAP-2.5.0.md +244 -0
- package/docs/SECURITY-AUDIT-REPORT.md +306 -0
- package/docs/SST-INTEGRATION.md +712 -0
- package/docs/STEADYWATCH-IMPLEMENTATION.md +303 -0
- package/docs/TERNARY-AUDIT-REPORT.md +247 -0
- package/docs/TME-FAQ.md +221 -0
- package/docs/WHITEPAPER.md +623 -0
- package/docs/adapters.html +1001 -0
- package/docs/advanced-systems.html +1045 -0
- package/docs/annex.html +1046 -0
- package/docs/api.html +970 -0
- package/docs/business/response-templates.md +160 -0
- package/docs/c2c.html +1225 -0
- package/docs/cli.html +1332 -0
- package/docs/configuration.html +1248 -0
- package/docs/darshan.html +1085 -0
- package/docs/dharma.html +966 -0
- package/docs/docs-bundle.html +1075 -0
- package/docs/docs.css +3120 -0
- package/docs/docs.js +556 -0
- package/docs/doko.html +969 -0
- package/docs/geo-proof.html +858 -0
- package/docs/getting-started.html +840 -0
- package/docs/gumba-tutorial.html +1144 -0
- package/docs/gumba.html +1098 -0
- package/docs/index.html +914 -0
- package/docs/jhilke.html +1312 -0
- package/docs/karma.html +1100 -0
- package/docs/katha.html +1037 -0
- package/docs/lama.html +978 -0
- package/docs/mandala.html +1067 -0
- package/docs/mani.html +964 -0
- package/docs/mantra.html +967 -0
- package/docs/mesh.html +1409 -0
- package/docs/nakpak.html +869 -0
- package/docs/namche.html +928 -0
- package/docs/nav-order.json +53 -0
- package/docs/prahari.html +1043 -0
- package/docs/prism-bash.min.js +1 -0
- package/docs/prism-javascript.min.js +1 -0
- package/docs/prism-json.min.js +1 -0
- package/docs/prism-tomorrow.min.css +1 -0
- package/docs/prism.min.js +1 -0
- package/docs/privacy.html +699 -0
- package/docs/quick-reference.html +1181 -0
- package/docs/sakshi.html +1402 -0
- package/docs/sandboxing.md +386 -0
- package/docs/seva.html +911 -0
- package/docs/sherpa.html +871 -0
- package/docs/studio.html +860 -0
- package/docs/stupa.html +995 -0
- package/docs/tailwind.min.css +2 -0
- package/docs/tattva.html +1332 -0
- package/docs/terms.html +686 -0
- package/docs/time-server-deployment.md +166 -0
- package/docs/time-sources.html +1392 -0
- package/docs/tivra.html +1127 -0
- package/docs/trademark-policy.html +686 -0
- package/docs/tribhuj.html +1183 -0
- package/docs/trust-security.html +1029 -0
- package/docs/tutorials/backup-recovery.html +654 -0
- package/docs/tutorials/dashboard.html +604 -0
- package/docs/tutorials/domain-setup.html +605 -0
- package/docs/tutorials/host-website.html +456 -0
- package/docs/tutorials/mesh-network.html +505 -0
- package/docs/tutorials/mobile-access.html +445 -0
- package/docs/tutorials/privacy.html +467 -0
- package/docs/tutorials/raspberry-pi.html +600 -0
- package/docs/tutorials/security-basics.html +539 -0
- package/docs/tutorials/share-files.html +431 -0
- package/docs/tutorials/troubleshooting.html +637 -0
- package/docs/tutorials/trust-karma.html +419 -0
- package/docs/tutorials/yak-protocol.html +456 -0
- package/docs/tutorials.html +1034 -0
- package/docs/vani.html +1270 -0
- package/docs/webserver.html +809 -0
- package/docs/yak-protocol.html +940 -0
- package/docs/yak-timeserver-design.md +475 -0
- package/docs/yakapp.html +1015 -0
- package/docs/ypc27.html +1069 -0
- package/docs/yurt.html +1344 -0
- package/embedded-docs/bundle.js +334 -74
- package/gossip/protocol.js +247 -27
- package/identity/key-resolver.js +262 -0
- package/identity/machine-seed.js +632 -0
- package/identity/node-key.js +669 -368
- package/identity/tribhuj-ratchet.js +506 -0
- package/knowledge-base.js +37 -8
- package/launcher/yakmesh.bat +62 -0
- package/launcher/yakmesh.sh +70 -0
- package/mesh/annex.js +462 -108
- package/mesh/beacon-broadcast.js +113 -1
- package/mesh/darshan.js +1718 -0
- package/mesh/gumba.js +1567 -0
- package/mesh/jhilke.js +651 -0
- package/mesh/katha.js +1012 -0
- package/mesh/nakpak-routing.js +8 -5
- package/mesh/network.js +724 -34
- package/mesh/pulse-sync.js +4 -1
- package/mesh/rate-limiter.js +127 -15
- package/mesh/seva.js +526 -0
- package/mesh/sherpa-discovery.js +89 -8
- package/mesh/sybil-defense.js +19 -5
- package/mesh/temporal-encoder.js +4 -3
- package/mesh/vani.js +1364 -0
- package/mesh/yurt.js +1340 -0
- package/models/entropy-sentinel.onnx +0 -0
- package/models/karma-trust.onnx +0 -0
- package/models/manifest.json +43 -0
- package/models/sakshi-anomaly.onnx +0 -0
- package/oracle/code-proof-protocol.js +7 -6
- package/oracle/codebase-lock.js +257 -28
- package/oracle/index.js +74 -15
- package/oracle/ma902-snmp.js +678 -0
- package/oracle/module-sealer.js +5 -3
- package/oracle/network-identity.js +16 -0
- package/oracle/packet-checksum.js +201 -0
- package/oracle/sst.js +579 -0
- package/oracle/ternary-144t.js +714 -0
- package/oracle/ternary-ml.js +481 -0
- package/oracle/time-api.js +239 -0
- package/oracle/time-source.js +137 -47
- package/oracle/validation-oracle-hardened.js +1111 -1071
- package/oracle/validation-oracle.js +4 -2
- package/oracle/ypc27.js +211 -0
- package/package.json +20 -3
- package/protocol/yak-handler.js +35 -9
- package/protocol/yak-protocol.js +28 -13
- package/reference/cpp/yakmesh_mceliece_shard.cpp +168 -0
- package/reference/cpp/yakmesh_ypc27.cpp +179 -0
- package/sbom.json +87 -0
- package/scripts/security-audit.mjs +264 -0
- package/scripts/update-docs-nav.js +194 -0
- package/scripts/update-docs-sidebar.cjs +164 -0
- package/security/crypto-config.js +4 -3
- package/security/dharma-moderation.js +517 -0
- package/security/doko-identity.js +193 -143
- package/security/domain-consensus.js +86 -85
- package/security/fs-hardening.js +620 -0
- package/security/hardware-attestation.js +5 -3
- package/security/hybrid-trust.js +227 -87
- package/security/karma-rate-limiter.js +692 -0
- package/security/khata-protocol.js +22 -21
- package/security/khata-trust-integration.js +277 -150
- package/security/memory-safety.js +635 -0
- package/security/mesh-auth.js +11 -10
- package/security/mesh-revocation.js +373 -5
- package/security/namche-gateway.js +298 -69
- package/security/sakshi.js +460 -3
- package/security/sangha.js +770 -0
- package/security/secure-config.js +473 -0
- package/security/silicon-parity.js +13 -10
- package/security/steadywatch.js +1142 -0
- package/security/strike-system.js +32 -3
- package/security/temporal-signing.js +488 -0
- package/security/trit-commitment.js +464 -0
- package/server/crypto/annex.js +247 -0
- package/server/darshan-api.js +343 -0
- package/server/index.js +3259 -362
- package/server/komm-api.js +668 -0
- package/utils/accel.js +2273 -0
- package/utils/ternary-id.js +79 -0
- package/utils/verify-worker.js +57 -0
- package/webserver/index.js +95 -5
- package/assets/yakmesh-logo.png +0 -0
- package/assets/yakmesh-logo.svg +0 -80
- package/assets/yakmesh-logo2.png +0 -0
- package/assets/yakmesh-logo2sm.png +0 -0
- package/assets/ymsm.png +0 -0
- package/website/assets/silhouettes/adapters.svg +0 -107
- package/website/assets/silhouettes/api-endpoints.svg +0 -115
- package/website/assets/silhouettes/atomic-clock.svg +0 -83
- package/website/assets/silhouettes/base-camp.svg +0 -81
- package/website/assets/silhouettes/bridge.svg +0 -69
- package/website/assets/silhouettes/docs-bundle.svg +0 -113
- package/website/assets/silhouettes/doko-basket.svg +0 -70
- package/website/assets/silhouettes/fortress.svg +0 -93
- package/website/assets/silhouettes/gateway.svg +0 -54
- package/website/assets/silhouettes/gears.svg +0 -93
- package/website/assets/silhouettes/globe-satellite.svg +0 -67
- package/website/assets/silhouettes/karma-wheel.svg +0 -137
- package/website/assets/silhouettes/lama-council.svg +0 -141
- package/website/assets/silhouettes/mandala-network.svg +0 -169
- package/website/assets/silhouettes/mani-stones.svg +0 -149
- package/website/assets/silhouettes/mantra-wheel.svg +0 -116
- package/website/assets/silhouettes/mesh-nodes.svg +0 -113
- package/website/assets/silhouettes/nakpak.svg +0 -56
- package/website/assets/silhouettes/peak-lightning.svg +0 -73
- package/website/assets/silhouettes/sherpa.svg +0 -69
- package/website/assets/silhouettes/stupa-tower.svg +0 -119
- package/website/assets/silhouettes/tattva-eye.svg +0 -78
- package/website/assets/silhouettes/terminal.svg +0 -74
- package/website/assets/silhouettes/webserver.svg +0 -145
- package/website/assets/silhouettes/yak.svg +0 -78
- package/website/assets/yakmesh-logo.png +0 -0
- package/website/assets/yakmesh-logo.webp +0 -0
- package/website/assets/yakmesh-logo128x140.webp +0 -0
- package/website/assets/yakmesh-logo2.png +0 -0
- package/website/assets/yakmesh-logo2.svg +0 -51
- package/website/assets/yakmesh-logo40x44.webp +0 -0
- package/website/assets/yakmesh.gif +0 -0
- package/website/assets/yakmesh.ico +0 -0
- package/website/assets/yakmesh.jpg +0 -0
- package/website/assets/yakmesh.pdf +0 -0
- package/website/assets/yakmesh.png +0 -0
- package/website/assets/yakmesh.svg +0 -70
- package/website/assets/yakmesh128.webp +0 -0
- package/website/assets/yakmesh32.png +0 -0
- package/website/assets/yakmesh32.svg +0 -65
- package/website/assets/yakmesh32o.ico +0 -2
- package/website/assets/yakmesh32o.svg +0 -65
- package/website/assets/yakmesh32o.svgz +0 -0
package/mesh/seva.js
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEVA Mesh Handler — NPU Compute Sharing for Yakmesh Mesh
|
|
3
|
+
*
|
|
4
|
+
* सेवा (seva) = selfless service
|
|
5
|
+
*
|
|
6
|
+
* Enables nodes with NPU hardware to serve compute requests from
|
|
7
|
+
* nodes without it. Work is math-only — no executable code, no files.
|
|
8
|
+
* All transport via ANNEX-encrypted mesh channels.
|
|
9
|
+
*
|
|
10
|
+
* PROTOCOL FLOW:
|
|
11
|
+
* 1. Node with NPU opts in: config.seva.enabled = true
|
|
12
|
+
* 2. Node broadcasts capability ad via GOSSIP (seva:capability)
|
|
13
|
+
* 3. Requesting node sends seva:request to a capable peer
|
|
14
|
+
* 4. Receiving node validates (math-only), executes, returns result
|
|
15
|
+
* 5. Optional: third-party verification via re-execution
|
|
16
|
+
*
|
|
17
|
+
* MESSAGE TYPES:
|
|
18
|
+
* - seva:capability — broadcast: what this node can compute
|
|
19
|
+
* - seva:request — directed: work request (math-only params)
|
|
20
|
+
* - seva:response — directed: work result (numbers only)
|
|
21
|
+
* - seva:verify — directed: request verification of a result
|
|
22
|
+
* - seva:verify:ack — directed: verification result (match/mismatch)
|
|
23
|
+
*
|
|
24
|
+
* SECURITY:
|
|
25
|
+
* - All params validated to be numbers-only (no strings, no code)
|
|
26
|
+
* - Pre-defined model slots only (no custom computation)
|
|
27
|
+
* - Rate-limited per requesting peer
|
|
28
|
+
* - Reputation-gated: low-karma peers get lower priority
|
|
29
|
+
* - Results are deterministic → verifiable by any third peer
|
|
30
|
+
*
|
|
31
|
+
* @module mesh/seva
|
|
32
|
+
* @license MIT
|
|
33
|
+
* @copyright 2026 YAKMESH™ Contributors
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { createLogger } from '../utils/logger.js';
|
|
37
|
+
import EventEmitter from 'events';
|
|
38
|
+
|
|
39
|
+
const log = createLogger('mesh:seva');
|
|
40
|
+
|
|
41
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
42
|
+
// CONFIGURATION
|
|
43
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
44
|
+
|
|
45
|
+
export const SEVA_CONFIG = Object.freeze({
|
|
46
|
+
version: 1,
|
|
47
|
+
|
|
48
|
+
// Capacity management
|
|
49
|
+
defaultMaxConcurrent: 10,
|
|
50
|
+
maxRequestsPerPeerPerMinute: 30,
|
|
51
|
+
requestTimeout: 5000, // 5s timeout for work responses
|
|
52
|
+
|
|
53
|
+
// Verification
|
|
54
|
+
verifyProbability: 0.05, // 5% of results get spot-checked
|
|
55
|
+
verifyTimeout: 10000, // 10s for verification round-trip
|
|
56
|
+
|
|
57
|
+
// Capability broadcast
|
|
58
|
+
capabilityBroadcastInterval: 60000, // Advertise every 60s
|
|
59
|
+
capabilityTTL: 180000, // Expire after 3 minutes
|
|
60
|
+
|
|
61
|
+
// Model slots (must match c2c server SEVA_SLOTS)
|
|
62
|
+
validSlots: new Set([
|
|
63
|
+
'planet-variation',
|
|
64
|
+
'planet-heightmap',
|
|
65
|
+
'planet-superres',
|
|
66
|
+
'faction-brain',
|
|
67
|
+
'combat-predict',
|
|
68
|
+
]),
|
|
69
|
+
|
|
70
|
+
// Message types
|
|
71
|
+
messageTypes: {
|
|
72
|
+
CAPABILITY: 'seva:capability',
|
|
73
|
+
REQUEST: 'seva:request',
|
|
74
|
+
RESPONSE: 'seva:response',
|
|
75
|
+
VERIFY: 'seva:verify',
|
|
76
|
+
VERIFY_ACK: 'seva:verify:ack',
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
81
|
+
// SEVA MESH HANDLER
|
|
82
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
83
|
+
|
|
84
|
+
export class SevaMeshHandler extends EventEmitter {
|
|
85
|
+
/**
|
|
86
|
+
* @param {Object} opts
|
|
87
|
+
* @param {Object} opts.identity — node identity (nodeId, publicKey)
|
|
88
|
+
* @param {Object} opts.network — MandalaNetwork instance for sending messages
|
|
89
|
+
* @param {boolean} opts.enabled — whether this node offers NPU compute (opt-in)
|
|
90
|
+
* @param {Object} opts.hardware — { npu: bool, npuTops: number, gpu: bool, ... }
|
|
91
|
+
* @param {Function} opts.executor — async function(slot, params) => result
|
|
92
|
+
* @param {number} opts.maxConcurrent — max parallel jobs (default 10)
|
|
93
|
+
*/
|
|
94
|
+
constructor(opts = {}) {
|
|
95
|
+
super();
|
|
96
|
+
this.identity = opts.identity;
|
|
97
|
+
this.network = opts.network;
|
|
98
|
+
this.enabled = opts.enabled ?? false;
|
|
99
|
+
this.hardware = opts.hardware || {};
|
|
100
|
+
this.executor = opts.executor || null;
|
|
101
|
+
this.maxConcurrent = opts.maxConcurrent || SEVA_CONFIG.defaultMaxConcurrent;
|
|
102
|
+
|
|
103
|
+
// State
|
|
104
|
+
this.activeJobs = 0;
|
|
105
|
+
this.totalServed = 0;
|
|
106
|
+
this.peerCapabilities = new Map(); // peerId → { slots, capacity, lastSeen }
|
|
107
|
+
this.peerRateLimits = new Map(); // peerId → { timestamps: number[] }
|
|
108
|
+
this.pendingRequests = new Map(); // requestId → { resolve, reject, timer }
|
|
109
|
+
|
|
110
|
+
// Broadcast timer
|
|
111
|
+
this._capBroadcastTimer = null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── LIFECYCLE ────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
start() {
|
|
117
|
+
if (this.enabled) {
|
|
118
|
+
// Broadcast our capabilities immediately, then periodically
|
|
119
|
+
this._broadcastCapability();
|
|
120
|
+
this._capBroadcastTimer = setInterval(
|
|
121
|
+
() => this._broadcastCapability(),
|
|
122
|
+
SEVA_CONFIG.capabilityBroadcastInterval
|
|
123
|
+
);
|
|
124
|
+
log.info({
|
|
125
|
+
npu: !!this.hardware.npu,
|
|
126
|
+
npuTops: this.hardware.npuTops || 0,
|
|
127
|
+
maxConcurrent: this.maxConcurrent,
|
|
128
|
+
}, 'SEVA mesh handler started (serving NPU compute)');
|
|
129
|
+
} else {
|
|
130
|
+
log.info('SEVA mesh handler started (consumer only — no NPU sharing)');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Always listen for capabilities and responses (even if not serving)
|
|
134
|
+
// The network adapter will call handleMessage() for seva: messages
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
stop() {
|
|
138
|
+
if (this._capBroadcastTimer) clearInterval(this._capBroadcastTimer);
|
|
139
|
+
this._capBroadcastTimer = null;
|
|
140
|
+
|
|
141
|
+
// Reject all pending requests
|
|
142
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
143
|
+
clearTimeout(pending.timer);
|
|
144
|
+
pending.reject(new Error('SEVA handler stopped'));
|
|
145
|
+
}
|
|
146
|
+
this.pendingRequests.clear();
|
|
147
|
+
|
|
148
|
+
log.info('SEVA mesh handler stopped');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ─── INCOMING MESSAGE HANDLER ─────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Handle an incoming SEVA mesh message.
|
|
155
|
+
* Called by MandalaNetwork when a seva: message arrives.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} type — message type (seva:*)
|
|
158
|
+
* @param {Object} payload — message payload
|
|
159
|
+
* @param {string} peerId — sender peer ID
|
|
160
|
+
*/
|
|
161
|
+
async handleMessage(type, payload, peerId) {
|
|
162
|
+
switch (type) {
|
|
163
|
+
case SEVA_CONFIG.messageTypes.CAPABILITY:
|
|
164
|
+
this._handleCapability(payload, peerId);
|
|
165
|
+
break;
|
|
166
|
+
case SEVA_CONFIG.messageTypes.REQUEST:
|
|
167
|
+
await this._handleRequest(payload, peerId);
|
|
168
|
+
break;
|
|
169
|
+
case SEVA_CONFIG.messageTypes.RESPONSE:
|
|
170
|
+
this._handleResponse(payload, peerId);
|
|
171
|
+
break;
|
|
172
|
+
case SEVA_CONFIG.messageTypes.VERIFY:
|
|
173
|
+
await this._handleVerify(payload, peerId);
|
|
174
|
+
break;
|
|
175
|
+
case SEVA_CONFIG.messageTypes.VERIFY_ACK:
|
|
176
|
+
this._handleVerifyAck(payload, peerId);
|
|
177
|
+
break;
|
|
178
|
+
default:
|
|
179
|
+
log.debug({ type }, 'Unknown SEVA message type');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ─── CAPABILITY MANAGEMENT ────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
_broadcastCapability() {
|
|
186
|
+
if (!this.enabled || !this.network) return;
|
|
187
|
+
|
|
188
|
+
const ad = {
|
|
189
|
+
nodeId: this.identity?.nodeId,
|
|
190
|
+
version: SEVA_CONFIG.version,
|
|
191
|
+
slots: this._getActiveSlots(),
|
|
192
|
+
capacity: {
|
|
193
|
+
maxConcurrent: this.maxConcurrent,
|
|
194
|
+
currentLoad: this.activeJobs,
|
|
195
|
+
accelerated: !!this.hardware.npu,
|
|
196
|
+
npuTops: this.hardware.npuTops || 0,
|
|
197
|
+
},
|
|
198
|
+
ts: Date.now(),
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
this.network.broadcast({ type: SEVA_CONFIG.messageTypes.CAPABILITY, ...ad });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
_getActiveSlots() {
|
|
205
|
+
const slots = [];
|
|
206
|
+
for (const slotId of SEVA_CONFIG.validSlots) {
|
|
207
|
+
slots.push({
|
|
208
|
+
id: slotId,
|
|
209
|
+
accelerated: !!this.hardware.npu,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return slots;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
_handleCapability(payload, peerId) {
|
|
216
|
+
if (!payload || !payload.slots) return;
|
|
217
|
+
|
|
218
|
+
this.peerCapabilities.set(peerId, {
|
|
219
|
+
slots: new Set(payload.slots.map(s => s.id)),
|
|
220
|
+
capacity: payload.capacity || {},
|
|
221
|
+
accelerated: payload.capacity?.accelerated || false,
|
|
222
|
+
lastSeen: Date.now(),
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
this.emit('peerCapability', peerId, payload);
|
|
226
|
+
|
|
227
|
+
// Evict stale peer capabilities
|
|
228
|
+
this._cleanStalePeers();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
_cleanStalePeers() {
|
|
232
|
+
const now = Date.now();
|
|
233
|
+
for (const [peerId, cap] of this.peerCapabilities) {
|
|
234
|
+
if (now - cap.lastSeen > SEVA_CONFIG.capabilityTTL) {
|
|
235
|
+
this.peerCapabilities.delete(peerId);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─── WORK REQUEST (CONSUMER SIDE) ─────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Submit a work request to the mesh.
|
|
244
|
+
* Finds the best capable peer and sends the request.
|
|
245
|
+
*
|
|
246
|
+
* @param {string} slot — model slot ID
|
|
247
|
+
* @param {Object} params — numeric parameters
|
|
248
|
+
* @returns {Promise<Object>} — work result
|
|
249
|
+
*/
|
|
250
|
+
async requestWork(slot, params) {
|
|
251
|
+
if (!SEVA_CONFIG.validSlots.has(slot)) {
|
|
252
|
+
throw new Error(`Invalid SEVA slot: ${slot}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Validate params are math-only
|
|
256
|
+
if (!this._validateNumericOnly(params)) {
|
|
257
|
+
throw new Error('SEVA params must be numbers only');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Find best peer for this slot
|
|
261
|
+
const peer = this._findBestPeer(slot);
|
|
262
|
+
if (!peer) {
|
|
263
|
+
throw new Error('No SEVA-capable peers available');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Generate request ID
|
|
267
|
+
const reqId = this._generateId();
|
|
268
|
+
|
|
269
|
+
// Send request and wait for response
|
|
270
|
+
return new Promise((resolve, reject) => {
|
|
271
|
+
const timer = setTimeout(() => {
|
|
272
|
+
this.pendingRequests.delete(reqId);
|
|
273
|
+
reject(new Error('SEVA request timeout'));
|
|
274
|
+
}, SEVA_CONFIG.requestTimeout);
|
|
275
|
+
|
|
276
|
+
this.pendingRequests.set(reqId, { resolve, reject, timer, slot, params });
|
|
277
|
+
|
|
278
|
+
this.network.sendTo(peer, {
|
|
279
|
+
type: SEVA_CONFIG.messageTypes.REQUEST,
|
|
280
|
+
id: reqId,
|
|
281
|
+
slot,
|
|
282
|
+
params,
|
|
283
|
+
ts: Date.now(),
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Find the best peer for a given slot.
|
|
290
|
+
* Prefers: accelerated > lowest load > most recent capability ad.
|
|
291
|
+
*/
|
|
292
|
+
_findBestPeer(slot) {
|
|
293
|
+
let bestPeer = null;
|
|
294
|
+
let bestScore = -1;
|
|
295
|
+
|
|
296
|
+
for (const [peerId, cap] of this.peerCapabilities) {
|
|
297
|
+
if (!cap.slots.has(slot)) continue;
|
|
298
|
+
|
|
299
|
+
// Score: NPU acceleration = 100, low load bonus = 0-50, freshness = 0-10
|
|
300
|
+
let score = 0;
|
|
301
|
+
if (cap.accelerated) score += 100;
|
|
302
|
+
const loadRatio = (cap.capacity.currentLoad || 0) / (cap.capacity.maxConcurrent || 10);
|
|
303
|
+
score += (1 - loadRatio) * 50;
|
|
304
|
+
const ageMs = Date.now() - cap.lastSeen;
|
|
305
|
+
score += Math.max(0, 10 - ageMs / 10000);
|
|
306
|
+
|
|
307
|
+
if (score > bestScore) {
|
|
308
|
+
bestScore = score;
|
|
309
|
+
bestPeer = peerId;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return bestPeer;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ─── WORK REQUEST (SERVER SIDE) ───────────────────────────────────────────
|
|
317
|
+
|
|
318
|
+
async _handleRequest(payload, peerId) {
|
|
319
|
+
if (!this.enabled || !this.executor) {
|
|
320
|
+
this.network.sendTo(peerId, {
|
|
321
|
+
type: SEVA_CONFIG.messageTypes.RESPONSE,
|
|
322
|
+
id: payload.id,
|
|
323
|
+
status: 'rejected',
|
|
324
|
+
error: 'SEVA not enabled on this node',
|
|
325
|
+
});
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Rate limit check
|
|
330
|
+
if (!this._checkPeerRateLimit(peerId)) {
|
|
331
|
+
this.network.sendTo(peerId, {
|
|
332
|
+
type: SEVA_CONFIG.messageTypes.RESPONSE,
|
|
333
|
+
id: payload.id,
|
|
334
|
+
status: 'rejected',
|
|
335
|
+
error: 'Rate limit exceeded',
|
|
336
|
+
});
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Validate slot
|
|
341
|
+
if (!SEVA_CONFIG.validSlots.has(payload.slot)) {
|
|
342
|
+
this.network.sendTo(peerId, {
|
|
343
|
+
type: SEVA_CONFIG.messageTypes.RESPONSE,
|
|
344
|
+
id: payload.id,
|
|
345
|
+
status: 'error',
|
|
346
|
+
error: 'Invalid slot',
|
|
347
|
+
});
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Validate math-only params
|
|
352
|
+
if (!this._validateNumericOnly(payload.params)) {
|
|
353
|
+
this.network.sendTo(peerId, {
|
|
354
|
+
type: SEVA_CONFIG.messageTypes.RESPONSE,
|
|
355
|
+
id: payload.id,
|
|
356
|
+
status: 'error',
|
|
357
|
+
error: 'Params must be numbers only',
|
|
358
|
+
});
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Concurrency check
|
|
363
|
+
if (this.activeJobs >= this.maxConcurrent) {
|
|
364
|
+
this.network.sendTo(peerId, {
|
|
365
|
+
type: SEVA_CONFIG.messageTypes.RESPONSE,
|
|
366
|
+
id: payload.id,
|
|
367
|
+
status: 'rejected',
|
|
368
|
+
error: 'Node at capacity',
|
|
369
|
+
});
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Execute
|
|
374
|
+
this.activeJobs++;
|
|
375
|
+
try {
|
|
376
|
+
const t0 = performance.now();
|
|
377
|
+
const result = await this.executor(payload.slot, payload.params);
|
|
378
|
+
const computeMs = Math.round(performance.now() - t0);
|
|
379
|
+
this.totalServed++;
|
|
380
|
+
|
|
381
|
+
this.network.sendTo(peerId, {
|
|
382
|
+
type: SEVA_CONFIG.messageTypes.RESPONSE,
|
|
383
|
+
id: payload.id,
|
|
384
|
+
slot: payload.slot,
|
|
385
|
+
status: 'ok',
|
|
386
|
+
result,
|
|
387
|
+
source: this.hardware.npu ? 'npu' : 'cpu',
|
|
388
|
+
computeMs,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
log.debug({ slot: payload.slot, computeMs, peer: peerId.slice(0, 8) }, 'SEVA work served');
|
|
392
|
+
} catch (err) {
|
|
393
|
+
this.network.sendTo(peerId, {
|
|
394
|
+
type: SEVA_CONFIG.messageTypes.RESPONSE,
|
|
395
|
+
id: payload.id,
|
|
396
|
+
status: 'error',
|
|
397
|
+
error: 'Execution failed',
|
|
398
|
+
});
|
|
399
|
+
} finally {
|
|
400
|
+
this.activeJobs--;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
_handleResponse(payload, peerId) {
|
|
405
|
+
const pending = this.pendingRequests.get(payload.id);
|
|
406
|
+
if (!pending) return;
|
|
407
|
+
|
|
408
|
+
clearTimeout(pending.timer);
|
|
409
|
+
this.pendingRequests.delete(payload.id);
|
|
410
|
+
|
|
411
|
+
if (payload.status === 'ok') {
|
|
412
|
+
pending.resolve(payload);
|
|
413
|
+
} else {
|
|
414
|
+
pending.reject(new Error(payload.error || 'SEVA response error'));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ─── VERIFICATION ─────────────────────────────────────────────────────────
|
|
419
|
+
|
|
420
|
+
async _handleVerify(payload, peerId) {
|
|
421
|
+
if (!this.enabled || !this.executor) return;
|
|
422
|
+
|
|
423
|
+
// Re-execute the computation
|
|
424
|
+
try {
|
|
425
|
+
const result = await this.executor(payload.slot, payload.params);
|
|
426
|
+
|
|
427
|
+
// Compare with claimed result using canonical JSON comparison
|
|
428
|
+
const match = JSON.stringify(result) === JSON.stringify(payload.claimedResult);
|
|
429
|
+
|
|
430
|
+
this.network.sendTo(peerId, {
|
|
431
|
+
type: SEVA_CONFIG.messageTypes.VERIFY_ACK,
|
|
432
|
+
id: payload.id,
|
|
433
|
+
match,
|
|
434
|
+
slot: payload.slot,
|
|
435
|
+
});
|
|
436
|
+
} catch {
|
|
437
|
+
// Can't verify — skip
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
_handleVerifyAck(payload, peerId) {
|
|
442
|
+
this.emit('verifyResult', payload.id, payload.match, peerId);
|
|
443
|
+
|
|
444
|
+
if (!payload.match) {
|
|
445
|
+
log.warn({ id: payload.id, peer: peerId.slice(0, 8) }, 'SEVA verification MISMATCH');
|
|
446
|
+
this.emit('verifyMismatch', payload.id, peerId);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ─── RATE LIMITING ────────────────────────────────────────────────────────
|
|
451
|
+
|
|
452
|
+
_checkPeerRateLimit(peerId) {
|
|
453
|
+
const now = Date.now();
|
|
454
|
+
let state = this.peerRateLimits.get(peerId);
|
|
455
|
+
if (!state) {
|
|
456
|
+
state = { timestamps: [] };
|
|
457
|
+
this.peerRateLimits.set(peerId, state);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Remove timestamps older than 60s
|
|
461
|
+
state.timestamps = state.timestamps.filter(t => now - t < 60000);
|
|
462
|
+
state.timestamps.push(now);
|
|
463
|
+
|
|
464
|
+
return state.timestamps.length <= SEVA_CONFIG.maxRequestsPerPeerPerMinute;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ─── UTILITIES ────────────────────────────────────────────────────────────
|
|
468
|
+
|
|
469
|
+
_validateNumericOnly(obj, depth = 0) {
|
|
470
|
+
if (depth > 3) return false;
|
|
471
|
+
if (typeof obj === 'number' && isFinite(obj)) return true;
|
|
472
|
+
if (Array.isArray(obj)) {
|
|
473
|
+
if (obj.length > 100000) return false;
|
|
474
|
+
return obj.every(v => this._validateNumericOnly(v, depth + 1));
|
|
475
|
+
}
|
|
476
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
477
|
+
const keys = Object.keys(obj);
|
|
478
|
+
if (keys.length > 20) return false;
|
|
479
|
+
return keys.every(k => this._validateNumericOnly(obj[k], depth + 1));
|
|
480
|
+
}
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
_generateId() {
|
|
485
|
+
const chars = '0123456789abcdef';
|
|
486
|
+
let id = '';
|
|
487
|
+
for (let i = 0; i < 16; i++) {
|
|
488
|
+
id += chars[Math.floor(Math.random() * 16)];
|
|
489
|
+
}
|
|
490
|
+
return id;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// ─── PUBLIC API ───────────────────────────────────────────────────────────
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Get all known SEVA-capable peers.
|
|
497
|
+
*/
|
|
498
|
+
getCapablePeers(slot = null) {
|
|
499
|
+
const peers = [];
|
|
500
|
+
for (const [peerId, cap] of this.peerCapabilities) {
|
|
501
|
+
if (slot && !cap.slots.has(slot)) continue;
|
|
502
|
+
peers.push({ peerId, ...cap });
|
|
503
|
+
}
|
|
504
|
+
return peers;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Get this node's current SEVA stats.
|
|
509
|
+
*/
|
|
510
|
+
getStats() {
|
|
511
|
+
return {
|
|
512
|
+
enabled: this.enabled,
|
|
513
|
+
activeJobs: this.activeJobs,
|
|
514
|
+
totalServed: this.totalServed,
|
|
515
|
+
maxConcurrent: this.maxConcurrent,
|
|
516
|
+
knownPeers: this.peerCapabilities.size,
|
|
517
|
+
hardware: {
|
|
518
|
+
npu: !!this.hardware.npu,
|
|
519
|
+
npuTops: this.hardware.npuTops || 0,
|
|
520
|
+
gpu: !!this.hardware.gpu,
|
|
521
|
+
},
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export default SevaMeshHandler;
|