soulprint-network 0.4.3 → 0.4.4
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/dist/code-hash.json +5 -4
- package/dist/server.js +54 -1
- package/dist/state/StateStore.d.ts +22 -0
- package/dist/state/StateStore.js +61 -0
- package/dist/validator.d.ts +23 -0
- package/dist/validator.js +138 -0
- package/package.json +1 -1
package/dist/code-hash.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"codeHash": "
|
|
3
|
-
"codeHashHex": "
|
|
4
|
-
"computedAt": "2026-03-
|
|
5
|
-
"fileCount":
|
|
2
|
+
"codeHash": "39a29e119e7cb485d5b7dc5bdbe1533318617ec05a4fc4da321c94253da9d7f9",
|
|
3
|
+
"codeHashHex": "0x39a29e119e7cb485d5b7dc5bdbe1533318617ec05a4fc4da321c94253da9d7f9",
|
|
4
|
+
"computedAt": "2026-03-01T02:07:26.276Z",
|
|
5
|
+
"fileCount": 22,
|
|
6
6
|
"files": [
|
|
7
7
|
"blockchain/PeerRegistryClient.ts",
|
|
8
8
|
"blockchain/blockchain-anchor.ts",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"p2p.ts",
|
|
25
25
|
"peer-challenge.ts",
|
|
26
26
|
"server.ts",
|
|
27
|
+
"state/StateStore.ts",
|
|
27
28
|
"validator.ts"
|
|
28
29
|
]
|
|
29
30
|
}
|
package/dist/server.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* 1. HTTP server (port 4888) — clientes y legado
|
|
7
7
|
* 2. libp2p P2P node (port 6888) — Kademlia DHT + GossipSub + mDNS
|
|
8
8
|
*/
|
|
9
|
-
import { startValidatorNode, setP2PNode, setPeerRegistryClient } from "./validator.js";
|
|
9
|
+
import { startValidatorNode, setP2PNode, setPeerRegistryClient, getNodeState, setLastSyncTs } from "./validator.js";
|
|
10
|
+
import { computeHash, saveState } from "./state/StateStore.js";
|
|
10
11
|
import { createSoulprintP2PNode, MAINNET_BOOTSTRAP, stopP2PNode } from "./p2p.js";
|
|
11
12
|
import { PeerRegistryClient } from "./blockchain/PeerRegistryClient.js";
|
|
12
13
|
// ─── Config ──────────────────────────────────────────────────────────────────
|
|
@@ -148,6 +149,58 @@ if (httpBootstraps.length > 0) {
|
|
|
148
149
|
}
|
|
149
150
|
}, 2_000);
|
|
150
151
|
}
|
|
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.
|
|
155
|
+
setInterval(async () => {
|
|
156
|
+
const { nullifiers, repStore, peers: knownPeers } = getNodeState();
|
|
157
|
+
if (knownPeers.length === 0)
|
|
158
|
+
return;
|
|
159
|
+
const localHash = computeHash(Object.keys(nullifiers));
|
|
160
|
+
for (const peerUrl of knownPeers) {
|
|
161
|
+
try {
|
|
162
|
+
const hashRes = await fetch(`${peerUrl}/state/hash`, { signal: AbortSignal.timeout(5_000) });
|
|
163
|
+
if (!hashRes.ok)
|
|
164
|
+
continue;
|
|
165
|
+
const hashData = await hashRes.json();
|
|
166
|
+
const peerHash = hashData.hash;
|
|
167
|
+
if (peerHash === localHash) {
|
|
168
|
+
console.log(`[sync] peer ${peerUrl}: hash match ✅`);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
// Hashes differ — fetch full state and merge
|
|
172
|
+
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})`);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const peerState = await exportRes.json();
|
|
178
|
+
const mergeRes = await fetch(`http://localhost:${HTTP_PORT}/state/merge`, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: { "Content-Type": "application/json" },
|
|
181
|
+
body: JSON.stringify(peerState),
|
|
182
|
+
signal: AbortSignal.timeout(5_000),
|
|
183
|
+
});
|
|
184
|
+
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
|
|
187
|
+
const { nullifiers: n2, repStore: r2, peers: p2 } = getNodeState();
|
|
188
|
+
const ts = Date.now();
|
|
189
|
+
saveState({
|
|
190
|
+
nullifiers: Object.keys(n2),
|
|
191
|
+
reputation: Object.fromEntries(Object.entries(r2).map(([d, e]) => [d, e.score])),
|
|
192
|
+
attestations: Object.values(r2).flatMap((e) => e.attestations ?? []),
|
|
193
|
+
peers: p2,
|
|
194
|
+
lastSync: ts,
|
|
195
|
+
stateHash: computeHash(Object.keys(n2)),
|
|
196
|
+
}, 0);
|
|
197
|
+
setLastSyncTs(ts);
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
console.warn(`[sync] peer ${peerUrl}: error — ${e.message}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}, 60_000);
|
|
151
204
|
// ─── Graceful shutdown ────────────────────────────────────────────────────────
|
|
152
205
|
async function shutdown(signal) {
|
|
153
206
|
console.log(`\n${signal} recibido — cerrando...`);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface StoredAttestation {
|
|
2
|
+
issuer_did: string;
|
|
3
|
+
target_did: string;
|
|
4
|
+
value: number;
|
|
5
|
+
context: string;
|
|
6
|
+
timestamp: number;
|
|
7
|
+
sig: string;
|
|
8
|
+
}
|
|
9
|
+
export interface NodeState {
|
|
10
|
+
nullifiers: string[];
|
|
11
|
+
reputation: Record<string, number>;
|
|
12
|
+
attestations: StoredAttestation[];
|
|
13
|
+
peers: string[];
|
|
14
|
+
lastSync: number;
|
|
15
|
+
stateHash: string;
|
|
16
|
+
}
|
|
17
|
+
/** sha256(JSON.stringify(nullifiers.sort())) */
|
|
18
|
+
export declare function computeHash(nullifiers: string[]): string;
|
|
19
|
+
/** Load state from disk. Returns default if file missing or corrupt. */
|
|
20
|
+
export declare function loadState(): NodeState;
|
|
21
|
+
/** Write state to disk — debounced 2 s by default. */
|
|
22
|
+
export declare function saveState(state: NodeState, debounceMs?: number): void;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StateStore — persistent state for P2P sync
|
|
3
|
+
* Saves/loads full node state to disk with debounced writes.
|
|
4
|
+
*/
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
const STATE_PATH = process.env.SOULPRINT_STATE_PATH
|
|
10
|
+
?? join(homedir(), ".soulprint", "node", "state.json");
|
|
11
|
+
const DEFAULT_STATE = {
|
|
12
|
+
nullifiers: [],
|
|
13
|
+
reputation: {},
|
|
14
|
+
attestations: [],
|
|
15
|
+
peers: [],
|
|
16
|
+
lastSync: 0,
|
|
17
|
+
stateHash: "",
|
|
18
|
+
};
|
|
19
|
+
let saveTimer = null;
|
|
20
|
+
/** sha256(JSON.stringify(nullifiers.sort())) */
|
|
21
|
+
export function computeHash(nullifiers) {
|
|
22
|
+
const sorted = [...nullifiers].sort();
|
|
23
|
+
return createHash("sha256").update(JSON.stringify(sorted)).digest("hex");
|
|
24
|
+
}
|
|
25
|
+
/** Load state from disk. Returns default if file missing or corrupt. */
|
|
26
|
+
export function loadState() {
|
|
27
|
+
if (!existsSync(STATE_PATH)) {
|
|
28
|
+
return { ...DEFAULT_STATE, stateHash: computeHash([]) };
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const raw = JSON.parse(readFileSync(STATE_PATH, "utf8"));
|
|
32
|
+
const nullifiers = raw.nullifiers ?? [];
|
|
33
|
+
return {
|
|
34
|
+
nullifiers,
|
|
35
|
+
reputation: raw.reputation ?? {},
|
|
36
|
+
attestations: raw.attestations ?? [],
|
|
37
|
+
peers: raw.peers ?? [],
|
|
38
|
+
lastSync: raw.lastSync ?? 0,
|
|
39
|
+
stateHash: raw.stateHash ?? computeHash(nullifiers),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return { ...DEFAULT_STATE, stateHash: computeHash([]) };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Write state to disk — debounced 2 s by default. */
|
|
47
|
+
export function saveState(state, debounceMs = 2000) {
|
|
48
|
+
if (saveTimer)
|
|
49
|
+
clearTimeout(saveTimer);
|
|
50
|
+
saveTimer = setTimeout(() => {
|
|
51
|
+
try {
|
|
52
|
+
const dir = dirname(STATE_PATH);
|
|
53
|
+
if (!existsSync(dir))
|
|
54
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
55
|
+
writeFileSync(STATE_PATH, JSON.stringify(state, null, 2));
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
console.error("[state] Failed to save state:", e);
|
|
59
|
+
}
|
|
60
|
+
}, debounceMs);
|
|
61
|
+
}
|
package/dist/validator.d.ts
CHANGED
|
@@ -21,6 +21,18 @@ export declare function setPeerRegistryClient(client: PeerRegistryClient): void;
|
|
|
21
21
|
* 2. Desde ese momento, gossipAttestation() también publica por P2P
|
|
22
22
|
*/
|
|
23
23
|
export declare function setP2PNode(node: SoulprintP2PNode): void;
|
|
24
|
+
/**
|
|
25
|
+
* Per-DID reputation: score (0-20) + attestation history.
|
|
26
|
+
* Persisted to disk - survives node restarts.
|
|
27
|
+
*/
|
|
28
|
+
interface ReputeEntry {
|
|
29
|
+
score: number;
|
|
30
|
+
base: number;
|
|
31
|
+
attestations: BotAttestation[];
|
|
32
|
+
last_updated: number;
|
|
33
|
+
identityScore: number;
|
|
34
|
+
hasDocumentVerified: boolean;
|
|
35
|
+
}
|
|
24
36
|
/**
|
|
25
37
|
* Aplica una nueva attestation al DID objetivo y persiste.
|
|
26
38
|
*
|
|
@@ -50,3 +62,14 @@ export declare function getBotReputation(nodeUrl: string, did: string): Promise<
|
|
|
50
62
|
export declare function getNodeInfo(nodeUrl: string): Promise<any>;
|
|
51
63
|
export declare const BOOTSTRAP_NODES: string[];
|
|
52
64
|
export { applyAttestation };
|
|
65
|
+
/** Used by anti-entropy loop in server.ts */
|
|
66
|
+
export declare function getNodeState(): {
|
|
67
|
+
nullifiers: Record<string, {
|
|
68
|
+
did: string;
|
|
69
|
+
verified_at: number;
|
|
70
|
+
}>;
|
|
71
|
+
repStore: Record<string, ReputeEntry>;
|
|
72
|
+
peers: string[];
|
|
73
|
+
lastSyncTs: number;
|
|
74
|
+
};
|
|
75
|
+
export declare function setLastSyncTs(ts: number): void;
|
package/dist/validator.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createServer } from "node:http";
|
|
|
2
2
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
+
import { computeHash, loadState, saveState } from "./state/StateStore.js";
|
|
5
6
|
import { generateKeypair, keypairFromPrivateKey, decodeToken, sign, createToken, TOKEN_LIFETIME_SECONDS, TOKEN_RENEW_PREEMPTIVE_SECS, TOKEN_RENEW_GRACE_SECS, TOKEN_RENEW_COOLDOWN_SECS, NonceStore, verifyAttestation, computeReputation, defaultReputation, PROTOCOL, PROTOCOL_HASH, isProtocolHashCompatible, computeTotalScoreWithFloor, checkFarming, recordApprovedGain, recordFarmingStrike, loadAuditStore, exportAuditStore, } from "soulprint-core";
|
|
6
7
|
import { verifyProof, deserializeProof } from "soulprint-zkp";
|
|
7
8
|
import { buildChallengeResponse, verifyPeerBehavior, } from "./peer-challenge.js";
|
|
@@ -206,6 +207,8 @@ function applyAttestation(att) {
|
|
|
206
207
|
saveReputation();
|
|
207
208
|
return { score: finalRepScore, attestations: allAtts.length, last_updated: rep.last_updated };
|
|
208
209
|
}
|
|
210
|
+
// ── P2P state sync metadata ───────────────────────────────────────────────────
|
|
211
|
+
let lastSyncTs = 0; // timestamp (ms) of last successful anti-entropy sync
|
|
209
212
|
// ── Peers registry (P2P gossip) ───────────────────────────────────────────────
|
|
210
213
|
let peers = []; // URLs de otros nodos (ej: "http://node2.example.com:4888")
|
|
211
214
|
function loadPeers() {
|
|
@@ -242,6 +245,9 @@ async function gossipAttestation(att, excludeUrl) {
|
|
|
242
245
|
if (targets.length < peers.length - (excludeUrl ? 1 : 0)) {
|
|
243
246
|
console.log(routingStats(peers.length, targets.length, att.target_did));
|
|
244
247
|
}
|
|
248
|
+
if (targets.length > 0) {
|
|
249
|
+
console.log(`[gossip] broadcasted to ${targets.length} peers`);
|
|
250
|
+
}
|
|
245
251
|
// Cifrar el payload con AES-256-GCM antes de enviar
|
|
246
252
|
// Solo nodos con PROTOCOL_HASH correcto pueden descifrar
|
|
247
253
|
const encrypted = encryptGossip({ attestation: att, from_peer: true });
|
|
@@ -831,6 +837,30 @@ function handleNullifierCheck(res, nullifier) {
|
|
|
831
837
|
}
|
|
832
838
|
// ── Server ────────────────────────────────────────────────────────────────────
|
|
833
839
|
export function startValidatorNode(port = PORT) {
|
|
840
|
+
// ── Load persistent state from disk (v0.4.4) ───────────────────────────────
|
|
841
|
+
const persisted = loadState();
|
|
842
|
+
if (persisted.nullifiers.length > 0 || Object.keys(persisted.reputation).length > 0) {
|
|
843
|
+
console.log(`[state] Loaded ${persisted.nullifiers.length} nullifiers, ${Object.keys(persisted.reputation).length} reputation entries from disk`);
|
|
844
|
+
// Merge persisted nullifiers into in-memory store
|
|
845
|
+
for (const n of persisted.nullifiers) {
|
|
846
|
+
if (!nullifiers[n]) {
|
|
847
|
+
nullifiers[n] = { did: `did:soulprint:recovered:${n.slice(0, 8)}`, verified_at: persisted.lastSync || Date.now() };
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
// Merge persisted reputation
|
|
851
|
+
for (const [did, score] of Object.entries(persisted.reputation)) {
|
|
852
|
+
if (!repStore[did]) {
|
|
853
|
+
repStore[did] = {
|
|
854
|
+
score,
|
|
855
|
+
base: 10,
|
|
856
|
+
attestations: [],
|
|
857
|
+
last_updated: persisted.lastSync || Date.now(),
|
|
858
|
+
identityScore: 0,
|
|
859
|
+
hasDocumentVerified: false,
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
834
864
|
loadNullifiers();
|
|
835
865
|
loadReputation();
|
|
836
866
|
loadPeers();
|
|
@@ -1044,6 +1074,9 @@ export function startValidatorNode(port = PORT) {
|
|
|
1044
1074
|
total_peers: Math.max(httpPeers, libp2pPeers),
|
|
1045
1075
|
// on-chain registered peers (PeerRegistry)
|
|
1046
1076
|
registered_peers: registeredPeers,
|
|
1077
|
+
// state sync (v0.4.4)
|
|
1078
|
+
state_hash: computeHash(Object.keys(nullifiers)).slice(0, 16) + "...",
|
|
1079
|
+
last_sync: lastSyncTs,
|
|
1047
1080
|
// estado general
|
|
1048
1081
|
uptime_ms: Date.now() - (globalThis._startTime ?? Date.now()),
|
|
1049
1082
|
timestamp: Date.now(),
|
|
@@ -1052,6 +1085,106 @@ export function startValidatorNode(port = PORT) {
|
|
|
1052
1085
|
}
|
|
1053
1086
|
if (cleanUrl === "/verify" && req.method === "POST")
|
|
1054
1087
|
return handleVerify(req, res, nodeKeypair, ip);
|
|
1088
|
+
// ── State sync endpoints (v0.4.4) ─────────────────────────────────────────
|
|
1089
|
+
// GET /state/hash — quick hash comparison for anti-entropy
|
|
1090
|
+
if (cleanUrl === "/state/hash" && req.method === "GET") {
|
|
1091
|
+
const currentNullifiers = Object.keys(nullifiers);
|
|
1092
|
+
const hash = computeHash(currentNullifiers);
|
|
1093
|
+
return json(res, 200, {
|
|
1094
|
+
hash,
|
|
1095
|
+
nullifier_count: currentNullifiers.length,
|
|
1096
|
+
reputation_count: Object.keys(repStore).length,
|
|
1097
|
+
attestation_count: Object.values(repStore).reduce((n, e) => n + (e.attestations?.length ?? 0), 0),
|
|
1098
|
+
timestamp: Date.now(),
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
// GET /state/export — full state export for sync
|
|
1102
|
+
if (cleanUrl === "/state/export" && req.method === "GET") {
|
|
1103
|
+
const allAttestations = Object.values(repStore).flatMap(e => e.attestations ?? []);
|
|
1104
|
+
return json(res, 200, {
|
|
1105
|
+
nullifiers: Object.keys(nullifiers),
|
|
1106
|
+
reputation: Object.fromEntries(Object.entries(repStore).map(([did, e]) => [did, e.score])),
|
|
1107
|
+
attestations: allAttestations,
|
|
1108
|
+
peers,
|
|
1109
|
+
lastSync: lastSyncTs,
|
|
1110
|
+
stateHash: computeHash(Object.keys(nullifiers)),
|
|
1111
|
+
timestamp: Date.now(),
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
// POST /state/merge — merge partial state from a peer
|
|
1115
|
+
if (cleanUrl === "/state/merge" && req.method === "POST") {
|
|
1116
|
+
let body;
|
|
1117
|
+
try {
|
|
1118
|
+
body = await readBody(req);
|
|
1119
|
+
}
|
|
1120
|
+
catch (e) {
|
|
1121
|
+
return json(res, 400, { error: e.message });
|
|
1122
|
+
}
|
|
1123
|
+
const incoming = body ?? {};
|
|
1124
|
+
let newNullifiers = 0;
|
|
1125
|
+
let newAttestations = 0;
|
|
1126
|
+
// Merge nullifiers (union)
|
|
1127
|
+
if (Array.isArray(incoming.nullifiers)) {
|
|
1128
|
+
for (const n of incoming.nullifiers) {
|
|
1129
|
+
if (typeof n === "string" && !nullifiers[n]) {
|
|
1130
|
+
nullifiers[n] = { did: `did:soulprint:synced:${n.slice(0, 8)}`, verified_at: Date.now() };
|
|
1131
|
+
newNullifiers++;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
if (newNullifiers > 0)
|
|
1135
|
+
saveNullifiers();
|
|
1136
|
+
}
|
|
1137
|
+
// Merge reputation (take max score)
|
|
1138
|
+
if (incoming.reputation && typeof incoming.reputation === "object") {
|
|
1139
|
+
for (const [did, score] of Object.entries(incoming.reputation)) {
|
|
1140
|
+
if (typeof score !== "number")
|
|
1141
|
+
continue;
|
|
1142
|
+
if (!repStore[did]) {
|
|
1143
|
+
repStore[did] = { score, base: 10, attestations: [], last_updated: Date.now(), identityScore: 0, hasDocumentVerified: false };
|
|
1144
|
+
}
|
|
1145
|
+
else if (score > repStore[did].score) {
|
|
1146
|
+
repStore[did].score = score;
|
|
1147
|
+
repStore[did].last_updated = Date.now();
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
if (Object.keys(incoming.reputation).length > 0)
|
|
1151
|
+
saveReputation();
|
|
1152
|
+
}
|
|
1153
|
+
// Merge attestations (dedup by issuer+timestamp+context)
|
|
1154
|
+
if (Array.isArray(incoming.attestations)) {
|
|
1155
|
+
for (const att of incoming.attestations) {
|
|
1156
|
+
if (!att?.issuer_did || !att?.target_did)
|
|
1157
|
+
continue;
|
|
1158
|
+
const existing = repStore[att.target_did];
|
|
1159
|
+
const prevAtts = existing?.attestations ?? [];
|
|
1160
|
+
const isDup = prevAtts.some(a => a.issuer_did === att.issuer_did &&
|
|
1161
|
+
a.timestamp === att.timestamp &&
|
|
1162
|
+
a.context === att.context);
|
|
1163
|
+
if (!isDup) {
|
|
1164
|
+
applyAttestation(att);
|
|
1165
|
+
newAttestations++;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
// Persist new unified state
|
|
1170
|
+
if (newNullifiers > 0 || newAttestations > 0) {
|
|
1171
|
+
const snapshot = {
|
|
1172
|
+
nullifiers: Object.keys(nullifiers),
|
|
1173
|
+
reputation: Object.fromEntries(Object.entries(repStore).map(([d, e]) => [d, e.score])),
|
|
1174
|
+
attestations: Object.values(repStore).flatMap(e => e.attestations ?? []),
|
|
1175
|
+
peers,
|
|
1176
|
+
lastSync: Date.now(),
|
|
1177
|
+
stateHash: computeHash(Object.keys(nullifiers)),
|
|
1178
|
+
};
|
|
1179
|
+
saveState(snapshot);
|
|
1180
|
+
lastSyncTs = Date.now();
|
|
1181
|
+
}
|
|
1182
|
+
return json(res, 200, {
|
|
1183
|
+
ok: true,
|
|
1184
|
+
new_nullifiers: newNullifiers,
|
|
1185
|
+
new_attestations: newAttestations,
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1055
1188
|
if (cleanUrl === "/token/renew" && req.method === "POST")
|
|
1056
1189
|
return handleTokenRenew(req, res, nodeKeypair);
|
|
1057
1190
|
if (cleanUrl === "/challenge" && req.method === "POST")
|
|
@@ -1433,3 +1566,8 @@ export async function getNodeInfo(nodeUrl) {
|
|
|
1433
1566
|
}
|
|
1434
1567
|
export const BOOTSTRAP_NODES = [];
|
|
1435
1568
|
export { applyAttestation };
|
|
1569
|
+
/** Used by anti-entropy loop in server.ts */
|
|
1570
|
+
export function getNodeState() {
|
|
1571
|
+
return { nullifiers, repStore, peers, lastSyncTs };
|
|
1572
|
+
}
|
|
1573
|
+
export function setLastSyncTs(ts) { lastSyncTs = ts; }
|
package/package.json
CHANGED