@aster-rpc/aster 0.1.2
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/capabilities.d.ts +26 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +29 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/client.d.ts +65 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +108 -0
- package/dist/client.js.map +1 -0
- package/dist/codec.d.ts +156 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +477 -0
- package/dist/codec.js.map +1 -0
- package/dist/config.d.ts +102 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +454 -0
- package/dist/config.js.map +1 -0
- package/dist/contract/identity.d.ts +115 -0
- package/dist/contract/identity.d.ts.map +1 -0
- package/dist/contract/identity.js +188 -0
- package/dist/contract/identity.js.map +1 -0
- package/dist/contract/manifest.d.ts +77 -0
- package/dist/contract/manifest.d.ts.map +1 -0
- package/dist/contract/manifest.js +127 -0
- package/dist/contract/manifest.js.map +1 -0
- package/dist/contract/publication.d.ts +71 -0
- package/dist/contract/publication.d.ts.map +1 -0
- package/dist/contract/publication.js +85 -0
- package/dist/contract/publication.js.map +1 -0
- package/dist/decorators.d.ts +139 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +175 -0
- package/dist/decorators.js.map +1 -0
- package/dist/dynamic.d.ts +61 -0
- package/dist/dynamic.d.ts.map +1 -0
- package/dist/dynamic.js +147 -0
- package/dist/dynamic.js.map +1 -0
- package/dist/framing.d.ts +74 -0
- package/dist/framing.d.ts.map +1 -0
- package/dist/framing.js +162 -0
- package/dist/framing.js.map +1 -0
- package/dist/health.d.ts +127 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +236 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptors/audit.d.ts +25 -0
- package/dist/interceptors/audit.d.ts.map +1 -0
- package/dist/interceptors/audit.js +46 -0
- package/dist/interceptors/audit.js.map +1 -0
- package/dist/interceptors/auth.d.ts +13 -0
- package/dist/interceptors/auth.d.ts.map +1 -0
- package/dist/interceptors/auth.js +34 -0
- package/dist/interceptors/auth.js.map +1 -0
- package/dist/interceptors/base.d.ts +74 -0
- package/dist/interceptors/base.d.ts.map +1 -0
- package/dist/interceptors/base.js +103 -0
- package/dist/interceptors/base.js.map +1 -0
- package/dist/interceptors/capability.d.ts +16 -0
- package/dist/interceptors/capability.d.ts.map +1 -0
- package/dist/interceptors/capability.js +63 -0
- package/dist/interceptors/capability.js.map +1 -0
- package/dist/interceptors/circuit-breaker.d.ts +40 -0
- package/dist/interceptors/circuit-breaker.d.ts.map +1 -0
- package/dist/interceptors/circuit-breaker.js +91 -0
- package/dist/interceptors/circuit-breaker.js.map +1 -0
- package/dist/interceptors/compression.d.ts +11 -0
- package/dist/interceptors/compression.d.ts.map +1 -0
- package/dist/interceptors/compression.js +12 -0
- package/dist/interceptors/compression.js.map +1 -0
- package/dist/interceptors/deadline.d.ts +12 -0
- package/dist/interceptors/deadline.d.ts.map +1 -0
- package/dist/interceptors/deadline.js +28 -0
- package/dist/interceptors/deadline.js.map +1 -0
- package/dist/interceptors/metrics.d.ts +43 -0
- package/dist/interceptors/metrics.d.ts.map +1 -0
- package/dist/interceptors/metrics.js +132 -0
- package/dist/interceptors/metrics.js.map +1 -0
- package/dist/interceptors/rate-limit.d.ts +24 -0
- package/dist/interceptors/rate-limit.d.ts.map +1 -0
- package/dist/interceptors/rate-limit.js +84 -0
- package/dist/interceptors/rate-limit.js.map +1 -0
- package/dist/interceptors/retry.d.ts +25 -0
- package/dist/interceptors/retry.d.ts.map +1 -0
- package/dist/interceptors/retry.js +55 -0
- package/dist/interceptors/retry.js.map +1 -0
- package/dist/limits.d.ts +77 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +137 -0
- package/dist/limits.js.map +1 -0
- package/dist/logging.d.ts +40 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +92 -0
- package/dist/logging.js.map +1 -0
- package/dist/metadata.d.ts +14 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +68 -0
- package/dist/metadata.js.map +1 -0
- package/dist/metrics.d.ts +40 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +92 -0
- package/dist/metrics.js.map +1 -0
- package/dist/peer-store.d.ts +53 -0
- package/dist/peer-store.d.ts.map +1 -0
- package/dist/peer-store.js +105 -0
- package/dist/peer-store.js.map +1 -0
- package/dist/protocol.d.ts +44 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +59 -0
- package/dist/protocol.js.map +1 -0
- package/dist/registration.d.ts +81 -0
- package/dist/registration.d.ts.map +1 -0
- package/dist/registration.js +161 -0
- package/dist/registration.js.map +1 -0
- package/dist/registry/acl.d.ts +57 -0
- package/dist/registry/acl.d.ts.map +1 -0
- package/dist/registry/acl.js +104 -0
- package/dist/registry/acl.js.map +1 -0
- package/dist/registry/client.d.ts +70 -0
- package/dist/registry/client.d.ts.map +1 -0
- package/dist/registry/client.js +115 -0
- package/dist/registry/client.js.map +1 -0
- package/dist/registry/gossip.d.ts +43 -0
- package/dist/registry/gossip.d.ts.map +1 -0
- package/dist/registry/gossip.js +102 -0
- package/dist/registry/gossip.js.map +1 -0
- package/dist/registry/keys.d.ts +25 -0
- package/dist/registry/keys.d.ts.map +1 -0
- package/dist/registry/keys.js +47 -0
- package/dist/registry/keys.js.map +1 -0
- package/dist/registry/models.d.ts +80 -0
- package/dist/registry/models.d.ts.map +1 -0
- package/dist/registry/models.js +35 -0
- package/dist/registry/models.js.map +1 -0
- package/dist/registry/publisher.d.ts +65 -0
- package/dist/registry/publisher.d.ts.map +1 -0
- package/dist/registry/publisher.js +164 -0
- package/dist/registry/publisher.js.map +1 -0
- package/dist/runtime.d.ts +267 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1366 -0
- package/dist/runtime.js.map +1 -0
- package/dist/server.d.ts +100 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +511 -0
- package/dist/server.js.map +1 -0
- package/dist/service.d.ts +72 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +98 -0
- package/dist/service.js.map +1 -0
- package/dist/session.d.ts +64 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +350 -0
- package/dist/session.js.map +1 -0
- package/dist/status.d.ts +113 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +206 -0
- package/dist/status.js.map +1 -0
- package/dist/transport/base.d.ts +46 -0
- package/dist/transport/base.d.ts.map +1 -0
- package/dist/transport/base.js +10 -0
- package/dist/transport/base.js.map +1 -0
- package/dist/transport/iroh.d.ts +45 -0
- package/dist/transport/iroh.d.ts.map +1 -0
- package/dist/transport/iroh.js +225 -0
- package/dist/transport/iroh.js.map +1 -0
- package/dist/transport/local.d.ts +48 -0
- package/dist/transport/local.d.ts.map +1 -0
- package/dist/transport/local.js +139 -0
- package/dist/transport/local.js.map +1 -0
- package/dist/trust/admission.d.ts +60 -0
- package/dist/trust/admission.d.ts.map +1 -0
- package/dist/trust/admission.js +149 -0
- package/dist/trust/admission.js.map +1 -0
- package/dist/trust/bootstrap.d.ts +109 -0
- package/dist/trust/bootstrap.d.ts.map +1 -0
- package/dist/trust/bootstrap.js +311 -0
- package/dist/trust/bootstrap.js.map +1 -0
- package/dist/trust/clock.d.ts +93 -0
- package/dist/trust/clock.d.ts.map +1 -0
- package/dist/trust/clock.js +154 -0
- package/dist/trust/clock.js.map +1 -0
- package/dist/trust/consumer.d.ts +139 -0
- package/dist/trust/consumer.d.ts.map +1 -0
- package/dist/trust/consumer.js +323 -0
- package/dist/trust/consumer.js.map +1 -0
- package/dist/trust/credentials.d.ts +98 -0
- package/dist/trust/credentials.d.ts.map +1 -0
- package/dist/trust/credentials.js +250 -0
- package/dist/trust/credentials.js.map +1 -0
- package/dist/trust/delegated.d.ts +118 -0
- package/dist/trust/delegated.d.ts.map +1 -0
- package/dist/trust/delegated.js +292 -0
- package/dist/trust/delegated.js.map +1 -0
- package/dist/trust/gossip.d.ts +146 -0
- package/dist/trust/gossip.d.ts.map +1 -0
- package/dist/trust/gossip.js +334 -0
- package/dist/trust/gossip.js.map +1 -0
- package/dist/trust/hooks.d.ts +84 -0
- package/dist/trust/hooks.d.ts.map +1 -0
- package/dist/trust/hooks.js +125 -0
- package/dist/trust/hooks.js.map +1 -0
- package/dist/trust/iid.d.ts +65 -0
- package/dist/trust/iid.d.ts.map +1 -0
- package/dist/trust/iid.js +104 -0
- package/dist/trust/iid.js.map +1 -0
- package/dist/trust/mesh.d.ts +43 -0
- package/dist/trust/mesh.d.ts.map +1 -0
- package/dist/trust/mesh.js +105 -0
- package/dist/trust/mesh.js.map +1 -0
- package/dist/trust/nonce.d.ts +39 -0
- package/dist/trust/nonce.d.ts.map +1 -0
- package/dist/trust/nonce.js +46 -0
- package/dist/trust/nonce.js.map +1 -0
- package/dist/trust/producer.d.ts +80 -0
- package/dist/trust/producer.d.ts.map +1 -0
- package/dist/trust/producer.js +151 -0
- package/dist/trust/producer.js.map +1 -0
- package/dist/trust/rcan.d.ts +29 -0
- package/dist/trust/rcan.d.ts.map +1 -0
- package/dist/trust/rcan.js +57 -0
- package/dist/trust/rcan.js.map +1 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +50 -0
- package/dist/types.js.map +1 -0
- package/dist/xlang.d.ts +26 -0
- package/dist/xlang.d.ts.map +1 -0
- package/dist/xlang.js +55 -0
- package/dist/xlang.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Producer mesh bootstrap -- founding node + join.
|
|
3
|
+
*
|
|
4
|
+
* Spec reference: Aster-trust-spec.md S2.1, S2.5. Plan: ASTER_PLAN.md S14.5.
|
|
5
|
+
*
|
|
6
|
+
* Two startup modes:
|
|
7
|
+
*
|
|
8
|
+
* startFoundingNode()
|
|
9
|
+
* The first producer in a new mesh. Generates a random 32-byte salt, derives
|
|
10
|
+
* the gossip topic, initializes MeshState, and returns it for the caller.
|
|
11
|
+
*
|
|
12
|
+
* joinMesh()
|
|
13
|
+
* A subsequent producer. Builds an AdmissionRequest from its credential.
|
|
14
|
+
* The caller dials the bootstrap peer and sends the request, then calls
|
|
15
|
+
* applyAdmissionResponse() with the result.
|
|
16
|
+
*
|
|
17
|
+
* handleAdmissionRpc()
|
|
18
|
+
* Server-side handler: parses a request, runs offline admission checks,
|
|
19
|
+
* and returns an AdmissionResponse.
|
|
20
|
+
*/
|
|
21
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import { homedir } from 'node:os';
|
|
24
|
+
import { randomBytes } from 'node:crypto';
|
|
25
|
+
import { MeshState, saveMeshState } from './mesh.js';
|
|
26
|
+
import { deriveGossipTopic } from './gossip.js';
|
|
27
|
+
import { hexToBytes, bytesToHex } from './credentials.js';
|
|
28
|
+
import { checkOffline } from './admission.js';
|
|
29
|
+
// ── Internal helpers ────────────────────────────────────────────────────────
|
|
30
|
+
const DEFAULT_STATE_DIR = join(homedir(), '.aster');
|
|
31
|
+
function stateDir(config) {
|
|
32
|
+
return config?.stateDir ?? process.env.ASTER_MESH_STATE_DIR ?? DEFAULT_STATE_DIR;
|
|
33
|
+
}
|
|
34
|
+
function statePath(name, config) {
|
|
35
|
+
return join(stateDir(config), name);
|
|
36
|
+
}
|
|
37
|
+
function ensureStateDir(config) {
|
|
38
|
+
const dir = stateDir(config);
|
|
39
|
+
if (!existsSync(dir)) {
|
|
40
|
+
mkdirSync(dir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Load an EnrollmentCredential from a JSON file.
|
|
45
|
+
* Path resolved from argument -> ASTER_ENROLLMENT env var.
|
|
46
|
+
*/
|
|
47
|
+
function loadEnrollmentCredential(path) {
|
|
48
|
+
const envPath = path ?? process.env.ASTER_ENROLLMENT;
|
|
49
|
+
if (!envPath) {
|
|
50
|
+
throw new Error('Set ASTER_ENROLLMENT to the path of your enrollment credential JSON file');
|
|
51
|
+
}
|
|
52
|
+
const raw = readFileSync(envPath, 'utf-8');
|
|
53
|
+
const d = JSON.parse(raw);
|
|
54
|
+
return {
|
|
55
|
+
endpointId: d.endpoint_id,
|
|
56
|
+
rootPubkey: d.root_pubkey, // already hex
|
|
57
|
+
expiresAt: Number(d.expires_at),
|
|
58
|
+
attributes: d.attributes ?? {},
|
|
59
|
+
signature: d.signature ?? '',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Load or generate 32-byte mesh salt.
|
|
64
|
+
* Persists to stateDir/mesh_salt for crash recovery.
|
|
65
|
+
*/
|
|
66
|
+
function loadOrGenerateSalt(config) {
|
|
67
|
+
const saltPath = statePath('mesh_salt', config);
|
|
68
|
+
ensureStateDir(config);
|
|
69
|
+
if (existsSync(saltPath)) {
|
|
70
|
+
const buf = readFileSync(saltPath);
|
|
71
|
+
if (buf.length !== 32) {
|
|
72
|
+
throw new Error(`mesh_salt at ${saltPath} is ${buf.length} bytes; expected 32`);
|
|
73
|
+
}
|
|
74
|
+
return new Uint8Array(buf);
|
|
75
|
+
}
|
|
76
|
+
const salt = randomBytes(32);
|
|
77
|
+
writeFileSync(saltPath, salt);
|
|
78
|
+
return new Uint8Array(salt);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Persist a MeshState to stateDir/mesh_state.json (atomic rename).
|
|
82
|
+
*/
|
|
83
|
+
function persistMeshState(state, config) {
|
|
84
|
+
const path = statePath('mesh_state.json', config);
|
|
85
|
+
saveMeshState(state, path);
|
|
86
|
+
}
|
|
87
|
+
// ── Founding node ───────────────────────────────────────────────────────────
|
|
88
|
+
/**
|
|
89
|
+
* Start the founding node of a new producer mesh.
|
|
90
|
+
*
|
|
91
|
+
* Steps (S2.1):
|
|
92
|
+
* 1. Load credential from JSON file.
|
|
93
|
+
* 2. Verify credential offline.
|
|
94
|
+
* 3. Generate or load 32-byte salt.
|
|
95
|
+
* 4. Derive gossip topic.
|
|
96
|
+
* 5. Create MeshState with self as only accepted producer.
|
|
97
|
+
* 6. Persist state.
|
|
98
|
+
*
|
|
99
|
+
* @returns The initialized MeshState.
|
|
100
|
+
*/
|
|
101
|
+
export async function startFoundingNode(enrollmentPath, config) {
|
|
102
|
+
// 1. Load credential
|
|
103
|
+
const cred = loadEnrollmentCredential(enrollmentPath);
|
|
104
|
+
// 2. Verify offline
|
|
105
|
+
const result = await checkOffline(cred, cred.endpointId);
|
|
106
|
+
if (!result.admitted) {
|
|
107
|
+
throw new Error(`Founding node credential invalid: ${result.reason}`);
|
|
108
|
+
}
|
|
109
|
+
// 3. Salt
|
|
110
|
+
const salt = loadOrGenerateSalt(config);
|
|
111
|
+
// 4. Topic derivation
|
|
112
|
+
await deriveGossipTopic(hexToBytes(cred.rootPubkey), salt); // validates topic derivation
|
|
113
|
+
// 5. MeshState -- self is the only accepted producer
|
|
114
|
+
const state = new MeshState();
|
|
115
|
+
state.addPeer(cred.endpointId);
|
|
116
|
+
// 6. Persist
|
|
117
|
+
persistMeshState(state, config);
|
|
118
|
+
return state;
|
|
119
|
+
}
|
|
120
|
+
// ── Join mesh ───────────────────────────────────────────────────────────────
|
|
121
|
+
/**
|
|
122
|
+
* Build an AdmissionRequest from a credential for joining an existing mesh.
|
|
123
|
+
*
|
|
124
|
+
* The caller should send this request to the bootstrap peer over
|
|
125
|
+
* the aster.producer_admission ALPN, then call applyAdmissionResponse()
|
|
126
|
+
* with the result.
|
|
127
|
+
*/
|
|
128
|
+
export function joinMesh(credential, iidToken) {
|
|
129
|
+
const credJson = JSON.stringify({
|
|
130
|
+
endpoint_id: credential.endpointId,
|
|
131
|
+
root_pubkey: credential.rootPubkey,
|
|
132
|
+
expires_at: credential.expiresAt,
|
|
133
|
+
attributes: credential.attributes,
|
|
134
|
+
signature: credential.signature,
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
credentialJson: credJson,
|
|
138
|
+
iidToken,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// ── Apply admission response ────────────────────────────────────────────────
|
|
142
|
+
/**
|
|
143
|
+
* Finalize MeshState after receiving a successful AdmissionResponse.
|
|
144
|
+
*
|
|
145
|
+
* @param response The AdmissionResponse from the bootstrap peer.
|
|
146
|
+
* @param ownEndpointId This node's endpoint ID.
|
|
147
|
+
* @param rootPubkey The root public key (raw bytes) for topic derivation.
|
|
148
|
+
* @returns Initialized MeshState ready for gossip subscription.
|
|
149
|
+
* @throws If response.accepted is false.
|
|
150
|
+
*/
|
|
151
|
+
export async function applyAdmissionResponse(response, ownEndpointId, rootPubkey) {
|
|
152
|
+
if (!response.accepted) {
|
|
153
|
+
throw new Error(`Admission refused: ${response.reason ?? '(no reason provided)'}`);
|
|
154
|
+
}
|
|
155
|
+
const salt = hexToBytes(response.salt);
|
|
156
|
+
await deriveGossipTopic(rootPubkey, salt); // validates topic derivation
|
|
157
|
+
const state = new MeshState();
|
|
158
|
+
// Add all accepted producers + self
|
|
159
|
+
for (const peerId of response.acceptedProducers) {
|
|
160
|
+
state.addPeer(peerId);
|
|
161
|
+
}
|
|
162
|
+
state.addPeer(ownEndpointId);
|
|
163
|
+
return state;
|
|
164
|
+
}
|
|
165
|
+
// ── Server-side admission RPC ───────────────────────────────────────────────
|
|
166
|
+
/**
|
|
167
|
+
* Server-side handler for aster.producer_admission ALPN.
|
|
168
|
+
*
|
|
169
|
+
* Parses an AdmissionRequest, runs offline admission checks, and returns
|
|
170
|
+
* an AdmissionResponse. On success, the peer is added to ownState.
|
|
171
|
+
*
|
|
172
|
+
* @param requestJson JSON-serialized credential (the credentialJson field
|
|
173
|
+
* from AdmissionRequest, or a raw credential JSON).
|
|
174
|
+
* @param ownState The founding/accepting node's MeshState.
|
|
175
|
+
* @param ownRootPubkey Hex-encoded root public key this mesh trusts.
|
|
176
|
+
* @param config Optional BootstrapConfig.
|
|
177
|
+
* @returns AdmissionResponse (accepted or rejected with reason).
|
|
178
|
+
*/
|
|
179
|
+
export async function handleAdmissionRpc(requestJson, ownState, ownRootPubkey, config) {
|
|
180
|
+
let cred;
|
|
181
|
+
try {
|
|
182
|
+
const d = JSON.parse(requestJson);
|
|
183
|
+
cred = {
|
|
184
|
+
endpointId: d.endpoint_id,
|
|
185
|
+
rootPubkey: d.root_pubkey,
|
|
186
|
+
expiresAt: Number(d.expires_at),
|
|
187
|
+
attributes: d.attributes ?? {},
|
|
188
|
+
signature: d.signature ?? '',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return {
|
|
193
|
+
accepted: false,
|
|
194
|
+
salt: '',
|
|
195
|
+
acceptedProducers: [],
|
|
196
|
+
reason: 'malformed request',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
// Verify the credential's root_pubkey matches the mesh's trusted key
|
|
200
|
+
if (cred.rootPubkey !== ownRootPubkey) {
|
|
201
|
+
return {
|
|
202
|
+
accepted: false,
|
|
203
|
+
salt: '',
|
|
204
|
+
acceptedProducers: [],
|
|
205
|
+
reason: 'untrusted root key',
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
// Run offline admission checks (signature, expiry, endpoint_id match)
|
|
209
|
+
const result = await checkOffline(cred, cred.endpointId);
|
|
210
|
+
if (!result.admitted) {
|
|
211
|
+
return {
|
|
212
|
+
accepted: false,
|
|
213
|
+
salt: '',
|
|
214
|
+
acceptedProducers: [],
|
|
215
|
+
reason: result.reason ?? 'admission check failed',
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
// Accept: add peer to mesh state
|
|
219
|
+
ownState.addPeer(cred.endpointId);
|
|
220
|
+
persistMeshState(ownState, config);
|
|
221
|
+
// Load salt from state dir for response
|
|
222
|
+
let saltHex = '';
|
|
223
|
+
try {
|
|
224
|
+
const saltPath = statePath('mesh_salt', config);
|
|
225
|
+
if (existsSync(saltPath)) {
|
|
226
|
+
saltHex = bytesToHex(new Uint8Array(readFileSync(saltPath)));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// Salt unavailable -- ephemeral mesh
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
accepted: true,
|
|
234
|
+
salt: saltHex,
|
|
235
|
+
acceptedProducers: ownState.allPeers(),
|
|
236
|
+
reason: '', // never leak reason on wire
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// ── Per-connection handler ──────────────────────────────────────────────────
|
|
240
|
+
/**
|
|
241
|
+
* Handle one producer admission connection: read request, write response.
|
|
242
|
+
*
|
|
243
|
+
* @param conn An IrohConnection-like object with acceptBi() and remoteId().
|
|
244
|
+
* @param ownRootPubkey Hex-encoded root public key.
|
|
245
|
+
* @param ownState This node's MeshState; mutated on accept.
|
|
246
|
+
* @param config Optional BootstrapConfig.
|
|
247
|
+
* @returns The AdmissionResponse that was sent.
|
|
248
|
+
*/
|
|
249
|
+
export async function handleProducerAdmissionConnection(conn, ownRootPubkey, ownState, config) {
|
|
250
|
+
const peerId = conn.remoteId();
|
|
251
|
+
try {
|
|
252
|
+
const [send, recv] = await conn.acceptBi();
|
|
253
|
+
const raw = await recv.readToEnd(64 * 1024);
|
|
254
|
+
if (raw.length === 0) {
|
|
255
|
+
const resp = {
|
|
256
|
+
accepted: false,
|
|
257
|
+
salt: '',
|
|
258
|
+
acceptedProducers: [],
|
|
259
|
+
reason: 'empty request',
|
|
260
|
+
};
|
|
261
|
+
return resp;
|
|
262
|
+
}
|
|
263
|
+
// Parse the AdmissionRequest wrapper and extract credential_json
|
|
264
|
+
let credJson;
|
|
265
|
+
try {
|
|
266
|
+
const wrapper = JSON.parse(new TextDecoder().decode(raw));
|
|
267
|
+
credJson = wrapper.credential_json ?? '';
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
// Back-compat: accept raw credential JSON as well
|
|
271
|
+
credJson = new TextDecoder().decode(raw);
|
|
272
|
+
}
|
|
273
|
+
const response = await handleAdmissionRpc(credJson, ownState, ownRootPubkey, config);
|
|
274
|
+
// Write response (strip reason -- oracle protection)
|
|
275
|
+
const wirePayload = {
|
|
276
|
+
accepted: response.accepted,
|
|
277
|
+
salt: response.salt,
|
|
278
|
+
accepted_producers: response.acceptedProducers,
|
|
279
|
+
reason: '', // oracle protection -- never leak on wire
|
|
280
|
+
};
|
|
281
|
+
await send.writeAll(new TextEncoder().encode(JSON.stringify(wirePayload)));
|
|
282
|
+
await send.finish();
|
|
283
|
+
return response;
|
|
284
|
+
}
|
|
285
|
+
catch (exc) {
|
|
286
|
+
return {
|
|
287
|
+
accepted: false,
|
|
288
|
+
salt: '',
|
|
289
|
+
acceptedProducers: [],
|
|
290
|
+
reason: `connection error from ${peerId}: ${exc}`,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// ── Ephemeral state ─────────────────────────────────────────────────────────
|
|
295
|
+
/**
|
|
296
|
+
* Build an in-memory MeshState for a standalone producer.
|
|
297
|
+
*
|
|
298
|
+
* Useful for demos, tests, and single-node setups. Generates a fresh random
|
|
299
|
+
* salt and an empty accepted-producer set with no persistence.
|
|
300
|
+
*/
|
|
301
|
+
export async function makeEphemeralMeshState(rootPubkey) {
|
|
302
|
+
const salt = randomBytes(32);
|
|
303
|
+
const state = new MeshState();
|
|
304
|
+
// Derive topic if rootPubkey provided (for gossip subscription)
|
|
305
|
+
if (rootPubkey != null) {
|
|
306
|
+
const topicId = await deriveGossipTopic(rootPubkey, new Uint8Array(salt));
|
|
307
|
+
state.topicId = bytesToHex(topicId);
|
|
308
|
+
}
|
|
309
|
+
return state;
|
|
310
|
+
}
|
|
311
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../../src/trust/bootstrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clock drift detection for mesh peers.
|
|
3
|
+
*
|
|
4
|
+
* Spec reference: Aster-trust-spec.md
|
|
5
|
+
*
|
|
6
|
+
* Detects excessive clock skew between mesh peers by comparing
|
|
7
|
+
* timestamps in gossip heartbeats. Peers with drift beyond
|
|
8
|
+
* the configured tolerance are isolated.
|
|
9
|
+
*/
|
|
10
|
+
/** Clock drift configuration. */
|
|
11
|
+
export interface ClockDriftConfig {
|
|
12
|
+
/** Maximum allowed drift in milliseconds (default: 30 seconds). */
|
|
13
|
+
toleranceMs: number;
|
|
14
|
+
/** Grace period after mesh join before drift checks kick in (default: 60 seconds). */
|
|
15
|
+
gracePeriodMs: number;
|
|
16
|
+
/** Minimum number of peers required for mesh median computation (default: 3). */
|
|
17
|
+
minPeers: number;
|
|
18
|
+
}
|
|
19
|
+
/** Default clock drift config. */
|
|
20
|
+
export declare const DEFAULT_CLOCK_DRIFT_CONFIG: ClockDriftConfig;
|
|
21
|
+
/**
|
|
22
|
+
* Check if a peer's timestamp indicates excessive clock drift.
|
|
23
|
+
*
|
|
24
|
+
* @param peerTimestampMs - Epoch ms from the peer's heartbeat
|
|
25
|
+
* @param localTimestampMs - Local epoch ms (default: Date.now())
|
|
26
|
+
* @returns The drift in milliseconds (positive = peer is ahead, negative = behind)
|
|
27
|
+
*/
|
|
28
|
+
export declare function computeDrift(peerTimestampMs: number, localTimestampMs?: number): number;
|
|
29
|
+
/**
|
|
30
|
+
* Check whether a peer should be isolated due to clock drift.
|
|
31
|
+
*
|
|
32
|
+
* @param driftMs - The computed drift (from computeDrift)
|
|
33
|
+
* @param meshJoinedAtMs - When we joined the mesh
|
|
34
|
+
* @param config - Drift configuration
|
|
35
|
+
* @returns true if the peer should be isolated
|
|
36
|
+
*/
|
|
37
|
+
export declare function shouldIsolate(driftMs: number, meshJoinedAtMs: number, config?: ClockDriftConfig): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Clock drift tracker for multiple peers.
|
|
40
|
+
*
|
|
41
|
+
* Records per-peer clock offsets and tracks which peers have been
|
|
42
|
+
* isolated due to excessive drift.
|
|
43
|
+
*/
|
|
44
|
+
export declare class ClockDriftTracker {
|
|
45
|
+
private offsets;
|
|
46
|
+
private isolated;
|
|
47
|
+
private meshJoinedAtMs;
|
|
48
|
+
private config;
|
|
49
|
+
constructor(meshJoinedAtMs?: number, config?: Partial<ClockDriftConfig>);
|
|
50
|
+
/**
|
|
51
|
+
* Update a peer's clock offset from a heartbeat.
|
|
52
|
+
*
|
|
53
|
+
* @returns true if the peer was newly isolated
|
|
54
|
+
*/
|
|
55
|
+
update(peerEndpointId: string, peerTimestampMs: number): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Track a peer's clock offset from a received timestamp.
|
|
58
|
+
* Alias for update() — tracks the offset and returns true if peer was newly isolated.
|
|
59
|
+
*/
|
|
60
|
+
trackOffset(peerEndpointId: string, peerTimestampMs: number): boolean;
|
|
61
|
+
/** Check if a peer is currently isolated. */
|
|
62
|
+
isIsolated(peerEndpointId: string): boolean;
|
|
63
|
+
/** Get drift for a peer in ms. */
|
|
64
|
+
getDrift(peerEndpointId: string): number | undefined;
|
|
65
|
+
/** All isolated peers. */
|
|
66
|
+
isolatedPeers(): string[];
|
|
67
|
+
/** Return a copy of the current peer offset map. */
|
|
68
|
+
peerOffsets(): Map<string, number>;
|
|
69
|
+
/**
|
|
70
|
+
* Compute the mesh median offset from all tracked peers.
|
|
71
|
+
*
|
|
72
|
+
* Returns undefined if fewer than minPeers peers are tracked
|
|
73
|
+
* (not enough data for meaningful drift detection).
|
|
74
|
+
*
|
|
75
|
+
* Uses median_high (higher-middle for even counts) for determinism.
|
|
76
|
+
*/
|
|
77
|
+
meshMedianOffset(): number | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Check if this node's own clock appears to be the outlier.
|
|
80
|
+
*
|
|
81
|
+
* @param selfOffsetEstimate - now_ms - msg.epoch_ms computed when this node
|
|
82
|
+
* sends a message (i.e. ~0 if the clock is correct).
|
|
83
|
+
* @returns true if self is the outlier. Returns false during grace period or
|
|
84
|
+
* when there are too few peers.
|
|
85
|
+
*/
|
|
86
|
+
selfInDrift(selfOffsetEstimate: number): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Remove a peer from the offset tracking table and isolation set.
|
|
89
|
+
* Called on Depart or lease expiry.
|
|
90
|
+
*/
|
|
91
|
+
removePeer(endpointId: string): void;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=clock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../../src/trust/clock.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,iCAAiC;AACjC,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,WAAW,EAAE,MAAM,CAAC;IACpB,sFAAsF;IACtF,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,kCAAkC;AAClC,eAAO,MAAM,0BAA0B,EAAE,gBAIxC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,eAAe,EAAE,MAAM,EACvB,gBAAgB,SAAa,GAC5B,MAAM,CAER;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,EACtB,MAAM,GAAE,gBAA6C,GACpD,OAAO,CAMT;AAiBD;;;;;GAKG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,MAAM,CAAmB;gBAErB,cAAc,SAAa,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC;IAK3E;;;;OAIG;IACH,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO;IAiBhE;;;OAGG;IACH,WAAW,CAAC,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO;IAIrE,6CAA6C;IAC7C,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO;IAI3C,kCAAkC;IAClC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIpD,0BAA0B;IAC1B,aAAa,IAAI,MAAM,EAAE;IAIzB,oDAAoD;IACpD,WAAW,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAIlC;;;;;;;OAOG;IACH,gBAAgB,IAAI,MAAM,GAAG,SAAS;IAQtC;;;;;;;OAOG;IACH,WAAW,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO;IAUhD;;;OAGG;IACH,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;CAIrC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clock drift detection for mesh peers.
|
|
3
|
+
*
|
|
4
|
+
* Spec reference: Aster-trust-spec.md
|
|
5
|
+
*
|
|
6
|
+
* Detects excessive clock skew between mesh peers by comparing
|
|
7
|
+
* timestamps in gossip heartbeats. Peers with drift beyond
|
|
8
|
+
* the configured tolerance are isolated.
|
|
9
|
+
*/
|
|
10
|
+
/** Default clock drift config. */
|
|
11
|
+
export const DEFAULT_CLOCK_DRIFT_CONFIG = {
|
|
12
|
+
toleranceMs: 30_000,
|
|
13
|
+
gracePeriodMs: 60_000,
|
|
14
|
+
minPeers: 3,
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Check if a peer's timestamp indicates excessive clock drift.
|
|
18
|
+
*
|
|
19
|
+
* @param peerTimestampMs - Epoch ms from the peer's heartbeat
|
|
20
|
+
* @param localTimestampMs - Local epoch ms (default: Date.now())
|
|
21
|
+
* @returns The drift in milliseconds (positive = peer is ahead, negative = behind)
|
|
22
|
+
*/
|
|
23
|
+
export function computeDrift(peerTimestampMs, localTimestampMs = Date.now()) {
|
|
24
|
+
return peerTimestampMs - localTimestampMs;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check whether a peer should be isolated due to clock drift.
|
|
28
|
+
*
|
|
29
|
+
* @param driftMs - The computed drift (from computeDrift)
|
|
30
|
+
* @param meshJoinedAtMs - When we joined the mesh
|
|
31
|
+
* @param config - Drift configuration
|
|
32
|
+
* @returns true if the peer should be isolated
|
|
33
|
+
*/
|
|
34
|
+
export function shouldIsolate(driftMs, meshJoinedAtMs, config = DEFAULT_CLOCK_DRIFT_CONFIG) {
|
|
35
|
+
// Don't isolate during grace period
|
|
36
|
+
const elapsed = Date.now() - meshJoinedAtMs;
|
|
37
|
+
if (elapsed < config.gracePeriodMs)
|
|
38
|
+
return false;
|
|
39
|
+
return Math.abs(driftMs) > config.toleranceMs;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Compute the high median of an array of numbers.
|
|
43
|
+
*
|
|
44
|
+
* For odd-length arrays, returns the middle element.
|
|
45
|
+
* For even-length arrays, returns the higher of the two middle elements
|
|
46
|
+
* (matches Python's statistics.median_high for determinism).
|
|
47
|
+
*
|
|
48
|
+
* @param values - Array of numbers (must not be empty).
|
|
49
|
+
*/
|
|
50
|
+
function medianHigh(values) {
|
|
51
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
52
|
+
const mid = Math.floor(sorted.length / 2);
|
|
53
|
+
return sorted[mid];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Clock drift tracker for multiple peers.
|
|
57
|
+
*
|
|
58
|
+
* Records per-peer clock offsets and tracks which peers have been
|
|
59
|
+
* isolated due to excessive drift.
|
|
60
|
+
*/
|
|
61
|
+
export class ClockDriftTracker {
|
|
62
|
+
offsets = new Map(); // peer -> latest drift ms
|
|
63
|
+
isolated = new Set();
|
|
64
|
+
meshJoinedAtMs;
|
|
65
|
+
config;
|
|
66
|
+
constructor(meshJoinedAtMs = Date.now(), config) {
|
|
67
|
+
this.meshJoinedAtMs = meshJoinedAtMs;
|
|
68
|
+
this.config = { ...DEFAULT_CLOCK_DRIFT_CONFIG, ...config };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Update a peer's clock offset from a heartbeat.
|
|
72
|
+
*
|
|
73
|
+
* @returns true if the peer was newly isolated
|
|
74
|
+
*/
|
|
75
|
+
update(peerEndpointId, peerTimestampMs) {
|
|
76
|
+
const drift = computeDrift(peerTimestampMs);
|
|
77
|
+
this.offsets.set(peerEndpointId, drift);
|
|
78
|
+
if (shouldIsolate(drift, this.meshJoinedAtMs, this.config)) {
|
|
79
|
+
if (!this.isolated.has(peerEndpointId)) {
|
|
80
|
+
this.isolated.add(peerEndpointId);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Peer recovered — remove from isolation
|
|
86
|
+
this.isolated.delete(peerEndpointId);
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Track a peer's clock offset from a received timestamp.
|
|
92
|
+
* Alias for update() — tracks the offset and returns true if peer was newly isolated.
|
|
93
|
+
*/
|
|
94
|
+
trackOffset(peerEndpointId, peerTimestampMs) {
|
|
95
|
+
return this.update(peerEndpointId, peerTimestampMs);
|
|
96
|
+
}
|
|
97
|
+
/** Check if a peer is currently isolated. */
|
|
98
|
+
isIsolated(peerEndpointId) {
|
|
99
|
+
return this.isolated.has(peerEndpointId);
|
|
100
|
+
}
|
|
101
|
+
/** Get drift for a peer in ms. */
|
|
102
|
+
getDrift(peerEndpointId) {
|
|
103
|
+
return this.offsets.get(peerEndpointId);
|
|
104
|
+
}
|
|
105
|
+
/** All isolated peers. */
|
|
106
|
+
isolatedPeers() {
|
|
107
|
+
return [...this.isolated];
|
|
108
|
+
}
|
|
109
|
+
/** Return a copy of the current peer offset map. */
|
|
110
|
+
peerOffsets() {
|
|
111
|
+
return new Map(this.offsets);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Compute the mesh median offset from all tracked peers.
|
|
115
|
+
*
|
|
116
|
+
* Returns undefined if fewer than minPeers peers are tracked
|
|
117
|
+
* (not enough data for meaningful drift detection).
|
|
118
|
+
*
|
|
119
|
+
* Uses median_high (higher-middle for even counts) for determinism.
|
|
120
|
+
*/
|
|
121
|
+
meshMedianOffset() {
|
|
122
|
+
const values = [...this.offsets.values()];
|
|
123
|
+
if (values.length < this.config.minPeers) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
return medianHigh(values);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if this node's own clock appears to be the outlier.
|
|
130
|
+
*
|
|
131
|
+
* @param selfOffsetEstimate - now_ms - msg.epoch_ms computed when this node
|
|
132
|
+
* sends a message (i.e. ~0 if the clock is correct).
|
|
133
|
+
* @returns true if self is the outlier. Returns false during grace period or
|
|
134
|
+
* when there are too few peers.
|
|
135
|
+
*/
|
|
136
|
+
selfInDrift(selfOffsetEstimate) {
|
|
137
|
+
const elapsed = Date.now() - this.meshJoinedAtMs;
|
|
138
|
+
if (elapsed < this.config.gracePeriodMs)
|
|
139
|
+
return false;
|
|
140
|
+
const median = this.meshMedianOffset();
|
|
141
|
+
if (median === undefined)
|
|
142
|
+
return false;
|
|
143
|
+
return Math.abs(selfOffsetEstimate - median) > this.config.toleranceMs;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Remove a peer from the offset tracking table and isolation set.
|
|
147
|
+
* Called on Depart or lease expiry.
|
|
148
|
+
*/
|
|
149
|
+
removePeer(endpointId) {
|
|
150
|
+
this.offsets.delete(endpointId);
|
|
151
|
+
this.isolated.delete(endpointId);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=clock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clock.js","sourceRoot":"","sources":["../../src/trust/clock.ts"],"names":[],"mappings":"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"}
|