@dcasegr/skein 0.0.0-stage → 0.1.1
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/README.md +522 -2
- package/bin/skein-host.js +22 -0
- package/bin/skein.js +8 -0
- package/dist/client/admin-cli.d.ts +10 -0
- package/dist/client/admin-cli.js +370 -0
- package/dist/client/admin.d.ts +139 -0
- package/dist/client/admin.js +256 -0
- package/dist/client/bundle.d.ts +9 -0
- package/dist/client/bundle.js +34 -0
- package/dist/client/cli.d.ts +44 -0
- package/dist/client/cli.js +306 -0
- package/dist/client/client.d.ts +161 -0
- package/dist/client/client.js +237 -0
- package/dist/client/config.d.ts +18 -0
- package/dist/client/config.js +31 -0
- package/dist/client/conversation.d.ts +62 -0
- package/dist/client/conversation.js +113 -0
- package/dist/client/raw.d.ts +94 -0
- package/dist/client/raw.js +154 -0
- package/dist/client/target.d.ts +34 -0
- package/dist/client/target.js +158 -0
- package/dist/dev/cli.d.ts +8 -0
- package/dist/dev/cli.js +248 -0
- package/dist/dev/scan.d.ts +15 -0
- package/dist/dev/scan.js +173 -0
- package/dist/envelope-cbor.d.ts +110 -0
- package/dist/envelope-cbor.js +160 -0
- package/dist/envelope.d.ts +38 -0
- package/dist/envelope.js +101 -0
- package/dist/host/arc.d.ts +131 -0
- package/dist/host/arc.js +425 -0
- package/dist/host/billing.d.ts +102 -0
- package/dist/host/billing.js +167 -0
- package/dist/host/boot.d.ts +246 -0
- package/dist/host/boot.js +643 -0
- package/dist/host/cli.d.ts +57 -0
- package/dist/host/cli.js +1253 -0
- package/dist/host/clock.d.ts +3 -0
- package/dist/host/clock.js +8 -0
- package/dist/host/control.d.ts +45 -0
- package/dist/host/control.js +170 -0
- package/dist/host/cron.d.ts +99 -0
- package/dist/host/cron.js +196 -0
- package/dist/host/deliver.d.ts +53 -0
- package/dist/host/deliver.js +251 -0
- package/dist/host/deploy.d.ts +29 -0
- package/dist/host/deploy.js +55 -0
- package/dist/host/door-answer.d.ts +24 -0
- package/dist/host/door-answer.js +21 -0
- package/dist/host/events.d.ts +89 -0
- package/dist/host/events.js +222 -0
- package/dist/host/fake-arcade.d.ts +40 -0
- package/dist/host/fake-arcade.js +149 -0
- package/dist/host/fake-discovery.d.ts +4 -0
- package/dist/host/fake-discovery.js +58 -0
- package/dist/host/feeds.d.ts +127 -0
- package/dist/host/feeds.js +422 -0
- package/dist/host/frontdoor.d.ts +92 -0
- package/dist/host/frontdoor.js +172 -0
- package/dist/host/genesis.d.ts +259 -0
- package/dist/host/genesis.js +353 -0
- package/dist/host/handles.d.ts +40 -0
- package/dist/host/handles.js +88 -0
- package/dist/host/hostenv.d.ts +30 -0
- package/dist/host/hostenv.js +83 -0
- package/dist/host/image-chain.d.ts +127 -0
- package/dist/host/image-chain.js +440 -0
- package/dist/host/install.d.ts +22 -0
- package/dist/host/install.js +139 -0
- package/dist/host/instances.d.ts +227 -0
- package/dist/host/instances.js +430 -0
- package/dist/host/kernel.d.ts +246 -0
- package/dist/host/kernel.js +603 -0
- package/dist/host/liveness.d.ts +65 -0
- package/dist/host/liveness.js +155 -0
- package/dist/host/manifest.d.ts +160 -0
- package/dist/host/manifest.js +597 -0
- package/dist/host/p2p.d.ts +337 -0
- package/dist/host/p2p.js +1002 -0
- package/dist/host/packet.d.ts +121 -0
- package/dist/host/packet.js +562 -0
- package/dist/host/plan.d.ts +154 -0
- package/dist/host/plan.js +418 -0
- package/dist/host/providers.d.ts +238 -0
- package/dist/host/providers.js +565 -0
- package/dist/host/roster.d.ts +72 -0
- package/dist/host/roster.js +172 -0
- package/dist/host/router.d.ts +773 -0
- package/dist/host/router.js +2389 -0
- package/dist/host/signer.d.ts +49 -0
- package/dist/host/signer.js +167 -0
- package/dist/host/store-sqlite.d.ts +39 -0
- package/dist/host/store-sqlite.js +170 -0
- package/dist/host/stores.d.ts +43 -0
- package/dist/host/stores.js +342 -0
- package/dist/host/sync.d.ts +121 -0
- package/dist/host/sync.js +467 -0
- package/dist/host/testbeef.d.ts +21 -0
- package/dist/host/testbeef.js +37 -0
- package/dist/host/testhost.d.ts +149 -0
- package/dist/host/testhost.js +211 -0
- package/dist/host/vcdiff.d.ts +9 -0
- package/dist/host/vcdiff.js +210 -0
- package/dist/host/wasm-kernel.d.ts +87 -0
- package/dist/host/wasm-kernel.js +296 -0
- package/dist/host/wasm-worker.d.ts +1 -0
- package/dist/host/wasm-worker.js +186 -0
- package/dist/peers/cron.d.ts +33 -0
- package/dist/peers/cron.js +75 -0
- package/dist/peers/infer-main.d.ts +2 -0
- package/dist/peers/infer-main.js +74 -0
- package/dist/peers/infer.d.ts +163 -0
- package/dist/peers/infer.js +367 -0
- package/dist/runtime/beef.d.ts +28 -0
- package/dist/runtime/beef.js +107 -0
- package/dist/runtime/bitcoin.d.ts +36 -0
- package/dist/runtime/bitcoin.js +141 -0
- package/dist/runtime/cid.d.ts +14 -0
- package/dist/runtime/cid.js +51 -0
- package/dist/runtime/dispatch.d.ts +92 -0
- package/dist/runtime/dispatch.js +142 -0
- package/dist/runtime/envelope.d.ts +60 -0
- package/dist/runtime/envelope.js +101 -0
- package/dist/runtime/heads.d.ts +25 -0
- package/dist/runtime/heads.js +37 -0
- package/dist/runtime/identity.d.ts +28 -0
- package/dist/runtime/identity.js +67 -0
- package/dist/runtime/index-store.d.ts +152 -0
- package/dist/runtime/index-store.js +715 -0
- package/dist/runtime/log.d.ts +62 -0
- package/dist/runtime/log.js +104 -0
- package/dist/runtime/programs.d.ts +30 -0
- package/dist/runtime/programs.js +61 -0
- package/dist/runtime/records.d.ts +135 -0
- package/dist/runtime/records.js +149 -0
- package/dist/runtime/sqlite.d.ts +33 -0
- package/dist/runtime/sqlite.js +604 -0
- package/dist/runtime/store.d.ts +141 -0
- package/dist/runtime/store.js +24 -0
- package/dist/runtime/syscalls.d.ts +9 -0
- package/dist/runtime/syscalls.js +41 -0
- package/dist/runtime/tree.d.ts +60 -0
- package/dist/runtime/tree.js +196 -0
- package/dist/runtime/types.d.ts +100 -0
- package/dist/runtime/types.js +7 -0
- package/dist/testapps.d.ts +101 -0
- package/dist/testapps.js +275 -0
- package/dist/testkit.d.ts +25 -0
- package/dist/testkit.js +48 -0
- package/dist/wallet.d.ts +35 -0
- package/dist/wallet.js +87 -0
- package/images/README.md +68 -0
- package/images/default/README.md +38 -0
- package/images/default/bin/frontdoor.cid +1 -0
- package/images/default/bin/frontdoor.json +5 -0
- package/images/default/bin/messagebox.cid +1 -0
- package/images/default/bin/messagebox.json +8 -0
- package/images/default/bin/wallet.cid +1 -0
- package/images/default/bin/wallet.json +14 -0
- package/images/default/etc/config.json +1 -0
- package/images/default/etc/dispatch.json +82 -0
- package/images/host/etc/apps.json +8 -0
- package/kernel-zig/README.md +1247 -0
- package/kernel-zig/web/client.js +65 -0
- package/kernel-zig/web/kernel.d.ts +59 -0
- package/kernel-zig/web/kernel.js +329 -0
- package/kernel-zig/web/store.js +67 -0
- package/kernel-zig/web/worker.js +79 -0
- package/kernel-zig/zig-out/web/skein-kernel.wasm +0 -0
- package/package.json +77 -4
- package/wasm/README.md +35 -0
- package/wasm/frontdoor.wasm +0 -0
- package/wasm/messagebox.wasm +0 -0
- package/wasm/resolve.wasm +0 -0
- package/wasm/wallet.wasm +0 -0
- package/wasm/wire-probe.wasm +0 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// The host loads the machine (the settled split, 2026-10-31): the kernel as ONE
|
|
2
|
+
// wasm module (kernel-zig/zig-out/web/skein-kernel.wasm — the same zig source
|
|
3
|
+
// as the native binary), loaded in the host's own process: one machine per
|
|
4
|
+
// ACTIVE instance on a node worker thread (wasm-worker.ts), one step at a time
|
|
5
|
+
// on it, instances in parallel; an idle instance has no machine (the router's
|
|
6
|
+
// idle stop ends its worker: it is its set on disk). Storage is the host's:
|
|
7
|
+
// every instance a set of the host's one SQLite database (store-sqlite.ts);
|
|
8
|
+
// `instances.store` = `sqlite:<db path>` (the set: the handle).
|
|
9
|
+
//
|
|
10
|
+
// `WasmHost` is one per database per node process (acquired and released as
|
|
11
|
+
// `KernelHost` is): the compiled module, shared by every worker, and the host
|
|
12
|
+
// chain's holder — one more worker over the `host/chain` set, with no
|
|
13
|
+
// instance (#156: the header chain kept once; every instance reads it through
|
|
14
|
+
// the chain exception). `WasmKernel` is an instance's machine with the
|
|
15
|
+
// router's `KernelSurface`. The kernel's one blocking request, the signer
|
|
16
|
+
// (`wallet`), is answered here on the main thread through the instance's
|
|
17
|
+
// WalletWireProcessor (the wallet is asynchronous; the worker waits on a
|
|
18
|
+
// SharedArrayBuffer). There is no `http`: #157.
|
|
19
|
+
import { readFileSync } from "node:fs";
|
|
20
|
+
import { dirname, extname, join, resolve as resolvePath } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { Worker } from "node:worker_threads";
|
|
23
|
+
import { WalletWireProcessor } from "@bsv/sdk";
|
|
24
|
+
import { decode, encode } from "../runtime/cid.js";
|
|
25
|
+
import { CHAIN_SET, parseSqliteSpec } from "../runtime/index-store.js";
|
|
26
|
+
import { Rejected } from "../runtime/store.js";
|
|
27
|
+
import { KernelSurface } from "./kernel.js";
|
|
28
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const ROOT = resolvePath(HERE, "../..");
|
|
30
|
+
/** The wasm kernel: $SKEIN_KERNEL_WASM, else kernel-zig's `zig build web` output (the package ships it there). */
|
|
31
|
+
export const KERNEL_WASM = process.env.SKEIN_KERNEL_WASM || join(ROOT, "kernel-zig/zig-out/web/skein-kernel.wasm");
|
|
32
|
+
/** The pinned modules (the boundary programs): $SKEIN_WASM_DIR, else the repo's (the package's) wasm/. */
|
|
33
|
+
export const WASM_DIR = process.env.SKEIN_WASM_DIR || join(ROOT, "wasm");
|
|
34
|
+
const WORKER = new URL(`./wasm-worker${extname(fileURLToPath(import.meta.url))}`, import.meta.url);
|
|
35
|
+
/** One worker holding one machine: frames in, replies and the kernel's notices and requests out. */
|
|
36
|
+
class Machine {
|
|
37
|
+
worker;
|
|
38
|
+
nextId = 1;
|
|
39
|
+
waiting = new Map();
|
|
40
|
+
ready;
|
|
41
|
+
done;
|
|
42
|
+
gone = false;
|
|
43
|
+
control = new SharedArrayBuffer(12);
|
|
44
|
+
ctrl = new Int32Array(this.control);
|
|
45
|
+
data = new SharedArrayBuffer(1 << 16, { maxByteLength: 1 << 30 });
|
|
46
|
+
constructor(module, w, o) {
|
|
47
|
+
this.worker = new Worker(WORKER, { workerData: { module, ...w, wasmDir: WASM_DIR, control: this.control, data: this.data } });
|
|
48
|
+
let ready, fail;
|
|
49
|
+
this.ready = new Promise((res, rej) => { ready = res; fail = rej; });
|
|
50
|
+
this.ready.catch(() => { });
|
|
51
|
+
this.worker.on("message", (m) => {
|
|
52
|
+
if (m.ready)
|
|
53
|
+
return ready();
|
|
54
|
+
if (m.re !== undefined) {
|
|
55
|
+
const p = this.waiting.get(m.re);
|
|
56
|
+
if (!p)
|
|
57
|
+
return;
|
|
58
|
+
this.waiting.delete(m.re);
|
|
59
|
+
settle(p, decode(m.frame));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (m.notify !== undefined) {
|
|
63
|
+
try {
|
|
64
|
+
o.onNotify(m.notify, m.v);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
o.log?.(`router: ${m.notify}: ${e.message}`);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (m.request !== undefined) {
|
|
72
|
+
void (async () => {
|
|
73
|
+
let status = 0, bytes;
|
|
74
|
+
try {
|
|
75
|
+
bytes = await o.onRequest(m.request, m.v);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
status = 1;
|
|
79
|
+
bytes = new TextEncoder().encode(e?.message ?? String(e));
|
|
80
|
+
}
|
|
81
|
+
if (bytes.length > this.data.byteLength)
|
|
82
|
+
this.data.grow(bytes.length);
|
|
83
|
+
new Uint8Array(this.data, 0, bytes.length).set(bytes);
|
|
84
|
+
this.ctrl[1] = status;
|
|
85
|
+
this.ctrl[2] = bytes.length;
|
|
86
|
+
Atomics.store(this.ctrl, 0, 1);
|
|
87
|
+
Atomics.notify(this.ctrl, 0);
|
|
88
|
+
})();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
this.worker.on("error", (e) => { o.log?.(`kernel worker: ${e.message}`); fail(e); });
|
|
92
|
+
this.done = new Promise((res) => this.worker.once("exit", (code) => {
|
|
93
|
+
this.gone = true;
|
|
94
|
+
for (const p of this.waiting.values())
|
|
95
|
+
p.reject(new Error(`the kernel's worker exited (${code})`));
|
|
96
|
+
this.waiting.clear();
|
|
97
|
+
fail(new Error(`the kernel's worker exited (${code}) before it was ready`));
|
|
98
|
+
res(code);
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
call(op, v) {
|
|
102
|
+
if (this.gone)
|
|
103
|
+
return Promise.reject(new Error("the kernel's worker is gone"));
|
|
104
|
+
const id = this.nextId++;
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
this.waiting.set(id, { resolve, reject });
|
|
107
|
+
this.worker.postMessage({ id, frame: encode({ op, v: v ?? null }).bytes });
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function settle(p, f) {
|
|
112
|
+
if (typeof f.rejected === "string")
|
|
113
|
+
p.reject(new Rejected(f.rejected, String(f.error)));
|
|
114
|
+
else if (typeof f.error === "string")
|
|
115
|
+
p.reject(new Error(f.error));
|
|
116
|
+
else
|
|
117
|
+
p.resolve(f.ok);
|
|
118
|
+
}
|
|
119
|
+
/** The host's machines over one SQLite database (one per database per node process: acquire/release). */
|
|
120
|
+
export class WasmHost {
|
|
121
|
+
static all = new Map();
|
|
122
|
+
static compiled;
|
|
123
|
+
/** The wasm kernel, compiled once per node process. */
|
|
124
|
+
static module() {
|
|
125
|
+
WasmHost.compiled ??= WebAssembly.compile(readFileSync(KERNEL_WASM));
|
|
126
|
+
return WasmHost.compiled;
|
|
127
|
+
}
|
|
128
|
+
static acquire(db, o = {}) {
|
|
129
|
+
const key = resolvePath(db);
|
|
130
|
+
let h = WasmHost.all.get(key);
|
|
131
|
+
if (!h) {
|
|
132
|
+
h = new WasmHost(key, o);
|
|
133
|
+
WasmHost.all.set(key, h);
|
|
134
|
+
}
|
|
135
|
+
h.holders++;
|
|
136
|
+
return h;
|
|
137
|
+
}
|
|
138
|
+
db;
|
|
139
|
+
o;
|
|
140
|
+
holders = 0;
|
|
141
|
+
views = new Map();
|
|
142
|
+
chain;
|
|
143
|
+
recent = new Map();
|
|
144
|
+
constructor(db, o) { this.db = db; this.o = o; }
|
|
145
|
+
/** Hydrate an instance: its machine on a worker of its own (the store's pinned modules installed). */
|
|
146
|
+
async open(o) {
|
|
147
|
+
const p = parseSqliteSpec(o.db);
|
|
148
|
+
if (!p)
|
|
149
|
+
throw new Error(`kernel ${o.handle}: ${o.db} is not a SQLite set`);
|
|
150
|
+
if (p.set !== o.handle)
|
|
151
|
+
throw new Error(`kernel ${o.handle}: its SQLite set is its handle, not ${p.set}`);
|
|
152
|
+
if (resolvePath(p.db) !== this.db)
|
|
153
|
+
throw new Error(`kernel ${o.handle}: its database is ${p.db}, this host's ${this.db}`);
|
|
154
|
+
// A machine being stopped (an idle stop, a restart): its next open waits for its worker to end (KernelHost's `closing`).
|
|
155
|
+
const had = this.views.get(o.handle);
|
|
156
|
+
if (had && !had.gone) {
|
|
157
|
+
if (!had.stopping)
|
|
158
|
+
throw new Error(`kernel ${o.handle}: open already`);
|
|
159
|
+
await had.machine.done;
|
|
160
|
+
}
|
|
161
|
+
const k = new WasmKernel(this, o, await WasmHost.module());
|
|
162
|
+
this.views.set(o.handle, k);
|
|
163
|
+
try {
|
|
164
|
+
await k.machine.ready;
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
this.views.delete(o.handle);
|
|
168
|
+
throw e;
|
|
169
|
+
}
|
|
170
|
+
void k.machine.done.then(() => { if (this.views.get(o.handle) === k)
|
|
171
|
+
this.views.delete(o.handle); });
|
|
172
|
+
return k;
|
|
173
|
+
}
|
|
174
|
+
/** The host chain's holder (#156): a machine over the `host/chain` set, no instance. */
|
|
175
|
+
holder() {
|
|
176
|
+
this.chain ??= (async () => {
|
|
177
|
+
const m = new Machine(await WasmHost.module(), { db: this.db, set: CHAIN_SET, mode: "chain", network: this.o.network ?? "main" }, {
|
|
178
|
+
onNotify: (op, v) => { if (op === "say" || op === "panic")
|
|
179
|
+
this.o.log?.(`host chain: ${String(v)}`); },
|
|
180
|
+
onRequest: async (op) => { throw new Error(`the host chain asks no ${op}`); },
|
|
181
|
+
log: this.o.log,
|
|
182
|
+
});
|
|
183
|
+
await m.ready;
|
|
184
|
+
void m.done.then(() => { this.chain = undefined; });
|
|
185
|
+
return m;
|
|
186
|
+
})();
|
|
187
|
+
return this.chain;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* #156: a run of headers into the host chain. The same run asked for again while it is in flight or
|
|
191
|
+
* just answered (every instance on one feed asks for the same event) is one call: its answer shared.
|
|
192
|
+
*/
|
|
193
|
+
chainHeaders(raws) {
|
|
194
|
+
const key = raws.map((r) => Buffer.from(r).toString("hex")).join("");
|
|
195
|
+
const had = this.recent.get(key);
|
|
196
|
+
if (had)
|
|
197
|
+
return had;
|
|
198
|
+
const p = this.holder().then((m) => m.call("headers", { raws })).then(tipOf);
|
|
199
|
+
this.recent.set(key, p);
|
|
200
|
+
p.catch(() => this.recent.delete(key));
|
|
201
|
+
while (this.recent.size > 64)
|
|
202
|
+
this.recent.delete(this.recent.keys().next().value);
|
|
203
|
+
return p;
|
|
204
|
+
}
|
|
205
|
+
/** #156: an image's chain/headers (the tree `root`, whose blocks the set `set` holds) into the empty host chain. */
|
|
206
|
+
async chainImport(set, root) { return tipOf(await (await this.holder()).call("chain-import", { set, root })); }
|
|
207
|
+
/** #156: the host chain as it stands, or undefined (empty). */
|
|
208
|
+
async chainTip() { const v = await (await this.holder()).call("chain"); return v == null ? undefined : tipOf(v); }
|
|
209
|
+
/** One holder less; the last stops every machine. */
|
|
210
|
+
async release() {
|
|
211
|
+
if (--this.holders > 0)
|
|
212
|
+
return;
|
|
213
|
+
WasmHost.all.delete(this.db);
|
|
214
|
+
for (const k of [...this.views.values()])
|
|
215
|
+
await k.stop();
|
|
216
|
+
const c = await this.chain?.catch(() => undefined);
|
|
217
|
+
if (c && !c.gone) {
|
|
218
|
+
await c.call("stop").catch(() => { });
|
|
219
|
+
await c.done;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function tipOf(v) {
|
|
224
|
+
const o = v;
|
|
225
|
+
return { root: o.root, height: Number(o.height), hash: o.hash, ...(o.added !== undefined ? { added: Number(o.added) } : {}), ...(o.replaced !== undefined ? { replaced: Number(o.replaced) } : {}) };
|
|
226
|
+
}
|
|
227
|
+
/** One instance's machine, as the router holds it. */
|
|
228
|
+
export class WasmKernel extends KernelSurface {
|
|
229
|
+
o;
|
|
230
|
+
host;
|
|
231
|
+
machine;
|
|
232
|
+
wire;
|
|
233
|
+
/** Its stop has begun (the next open of the handle waits for the worker to end). */
|
|
234
|
+
stopping = false;
|
|
235
|
+
constructor(host, o, module) {
|
|
236
|
+
super();
|
|
237
|
+
this.host = host;
|
|
238
|
+
this.o = o;
|
|
239
|
+
if (o.wallet)
|
|
240
|
+
this.wire = new WalletWireProcessor(o.wallet);
|
|
241
|
+
const set = parseSqliteSpec(o.db);
|
|
242
|
+
this.machine = new Machine(module, { db: set.db, set: set.set, mode: "instance" }, {
|
|
243
|
+
onNotify: (op, v) => this.onNotify(op, v),
|
|
244
|
+
onRequest: async (op, v) => {
|
|
245
|
+
if (op !== "wallet")
|
|
246
|
+
throw new Error(`this host answers no ${op}`);
|
|
247
|
+
if (!this.wire)
|
|
248
|
+
throw new Error("no wallet");
|
|
249
|
+
this.busy++;
|
|
250
|
+
// The request and its answer cross as dag-cbor (web.zig `request`: a byte string each way).
|
|
251
|
+
try {
|
|
252
|
+
return encode(Uint8Array.from(await this.wire.transmitToWallet([...decode(v)]))).bytes;
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
this.busy--;
|
|
256
|
+
this.last = Date.now();
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
log: o.log,
|
|
260
|
+
});
|
|
261
|
+
void this.machine.done.then((code) => { this.gone = true; o.exited?.(code, null); });
|
|
262
|
+
}
|
|
263
|
+
onNotify(op, v) {
|
|
264
|
+
if (op === "emit") {
|
|
265
|
+
this.o.emit?.(decode(v));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (op === "say" || op === "panic")
|
|
269
|
+
this.o.log?.(String(v));
|
|
270
|
+
}
|
|
271
|
+
get pid() { return process.pid; }
|
|
272
|
+
call(op, v) {
|
|
273
|
+
this.busy++;
|
|
274
|
+
this.last = Date.now();
|
|
275
|
+
return this.machine.call(op, v).finally(() => { this.busy--; this.last = Date.now(); });
|
|
276
|
+
}
|
|
277
|
+
async running(identity) {
|
|
278
|
+
this.o.log?.(`skein runtime ${identity} (${this.o.handle}@${this.o.domain}) · wasm kernel, worker ${this.machine.worker.threadId} · db ${this.o.db}`);
|
|
279
|
+
}
|
|
280
|
+
/** #156: the host chain is the host's: its holder's. */
|
|
281
|
+
chainHeaders(raws) { return this.host.chainHeaders(raws); }
|
|
282
|
+
chainImport(root) { return this.host.chainImport(this.o.handle, root); }
|
|
283
|
+
chainTip() { return this.host.chainTip(); }
|
|
284
|
+
/** Stop the machine: what its worker runs finishes, its store is closed, the worker ends. */
|
|
285
|
+
async stop() {
|
|
286
|
+
if (this.gone || this.machine.gone)
|
|
287
|
+
return;
|
|
288
|
+
this.stopping = true;
|
|
289
|
+
try {
|
|
290
|
+
await this.machine.call("stop");
|
|
291
|
+
}
|
|
292
|
+
catch { /* it exited */ }
|
|
293
|
+
await this.machine.done;
|
|
294
|
+
}
|
|
295
|
+
exited() { return this.machine.done.then(() => { }); }
|
|
296
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// One machine (the settled split, 2026-10-31): a node worker thread holding
|
|
2
|
+
// one wasm kernel (kernel-zig/zig-out/web/skein-kernel.wasm through the
|
|
3
|
+
// dependency-free shim kernel-zig/web/kernel.js) for ONE active instance — or
|
|
4
|
+
// the host's chain holder (#156: the header chain, kept once for the host). The
|
|
5
|
+
// host's main thread never runs a program; one step at a time here; instances
|
|
6
|
+
// in parallel on their own workers (wasm-kernel.ts).
|
|
7
|
+
//
|
|
8
|
+
// The kernel's store is a set of the host's one SQLite database (store-sqlite.ts,
|
|
9
|
+
// this worker's own connection); the apps' wasm runs in the same V8 through the
|
|
10
|
+
// shim's engine (the kernel names a module, the shim compiles and instantiates
|
|
11
|
+
// it with imports that call back into the kernel). The kernel's blocking
|
|
12
|
+
// request — the signer, `wallet` — goes to the main thread and this thread
|
|
13
|
+
// waits for the answer (Atomics.wait on a SharedArrayBuffer, the browser
|
|
14
|
+
// worker's way: kernel-zig/web/worker.js): the wallet the host holds the key
|
|
15
|
+
// in is asynchronous. Its notices (emit, say, panic) are posted to the main
|
|
16
|
+
// thread as they come.
|
|
17
|
+
//
|
|
18
|
+
// Messages in: {id, frame} (dag-cbor {op, v}) → {re: id, frame} (the reply:
|
|
19
|
+
// dag-cbor {ok} | {error, rejected?}). After an admit or a start the worker
|
|
20
|
+
// drains (the step loop) before it takes the next message, so the main
|
|
21
|
+
// thread's next call sees what that ran. `answer` {entry, wait} answers once
|
|
22
|
+
// the request entry's thread is at rest, or at its bound with the state as it
|
|
23
|
+
// stands (serve's waiters, the same rule). `stop` closes the store and ends.
|
|
24
|
+
import { readFileSync } from "node:fs";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
27
|
+
import * as dagCbor from "@ipld/dag-cbor";
|
|
28
|
+
import { Kernel } from "../../kernel-zig/web/kernel.js";
|
|
29
|
+
import { openHostDb, SqliteSetStore } from "./store-sqlite.js";
|
|
30
|
+
const d = workerData;
|
|
31
|
+
const port = parentPort;
|
|
32
|
+
const enc = new TextEncoder();
|
|
33
|
+
const dec = new TextDecoder();
|
|
34
|
+
const db = openHostDb(d.db);
|
|
35
|
+
const stores = { 1: new SqliteSetStore(db, d.set) };
|
|
36
|
+
const ctrl = new Int32Array(d.control); // [0] answered, [1] status (0 ok, 1 error), [2] length
|
|
37
|
+
function request(op, v) {
|
|
38
|
+
Atomics.store(ctrl, 0, 0);
|
|
39
|
+
port.postMessage({ request: op, v });
|
|
40
|
+
Atomics.wait(ctrl, 0, 0);
|
|
41
|
+
const bytes = new Uint8Array(d.data, 0, ctrl[2]).slice();
|
|
42
|
+
return ctrl[1] === 0 ? { ok: bytes } : { error: dec.decode(bytes) };
|
|
43
|
+
}
|
|
44
|
+
let fatal = false;
|
|
45
|
+
const kernel = new Kernel(d.module, {
|
|
46
|
+
stores,
|
|
47
|
+
peers: {
|
|
48
|
+
request,
|
|
49
|
+
notify: (op, v) => {
|
|
50
|
+
if (op === "panic")
|
|
51
|
+
fatal = true;
|
|
52
|
+
port.postMessage({ notify: op, v });
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
const reply = (v) => dagCbor.encode(v);
|
|
57
|
+
if (d.mode === "chain") {
|
|
58
|
+
const net = kernel.push(d.network ?? "main");
|
|
59
|
+
try {
|
|
60
|
+
kernel.check(kernel.x.skein_open_chain(1, net[0], net[1]));
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
kernel.release(net);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
kernel.open(1);
|
|
68
|
+
// The pinned modules (the boundary programs), checked against their CIDs by the kernel (serve's install).
|
|
69
|
+
const mods = dagCbor.decode(kernel.modulesList());
|
|
70
|
+
for (const m of mods) {
|
|
71
|
+
if (stores[1].hasBlock(m.cid.bytes))
|
|
72
|
+
continue;
|
|
73
|
+
kernel.putBlock(m.cid.bytes, readFileSync(join(d.wasmDir, m.file)));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
let waiters = [];
|
|
77
|
+
let timer;
|
|
78
|
+
function answerOf(entry) {
|
|
79
|
+
return kernel.call(dagCbor.encode({ op: "answer", v: entry }));
|
|
80
|
+
}
|
|
81
|
+
function atRest(r) {
|
|
82
|
+
const s = dagCbor.decode(r).ok?.state;
|
|
83
|
+
return s === "finished" || s === "errored";
|
|
84
|
+
}
|
|
85
|
+
/** Answer the waiters whose thread came to rest, or whose bound has passed. */
|
|
86
|
+
function checkWaiters() {
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
const keep = [];
|
|
89
|
+
for (const w of waiters) {
|
|
90
|
+
const r = answerOf(w.entry);
|
|
91
|
+
if (atRest(r) || w.until <= now)
|
|
92
|
+
port.postMessage({ re: w.id, frame: r });
|
|
93
|
+
else
|
|
94
|
+
keep.push(w);
|
|
95
|
+
}
|
|
96
|
+
waiters = keep;
|
|
97
|
+
if (timer) {
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
timer = undefined;
|
|
100
|
+
}
|
|
101
|
+
if (waiters.length)
|
|
102
|
+
timer = setTimeout(checkWaiters, Math.max(0, Math.min(...waiters.map((w) => w.until)) - Date.now()));
|
|
103
|
+
}
|
|
104
|
+
// ------------------------------------------------------------ the main thread's calls
|
|
105
|
+
function handle(id, f, raw) {
|
|
106
|
+
switch (f.op) {
|
|
107
|
+
case "admit": {
|
|
108
|
+
const r = kernel.admit(dagCbor.encode(f.v));
|
|
109
|
+
port.postMessage({ re: id, frame: r });
|
|
110
|
+
const ok = dagCbor.decode(r).ok;
|
|
111
|
+
if (ok && !(typeof ok === "object" && "answered" in ok)) {
|
|
112
|
+
kernel.drain();
|
|
113
|
+
checkWaiters();
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
case "start": {
|
|
118
|
+
const r = kernel.start();
|
|
119
|
+
port.postMessage({ re: id, frame: r });
|
|
120
|
+
kernel.drain();
|
|
121
|
+
checkWaiters();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
case "drain": {
|
|
125
|
+
port.postMessage({ re: id, frame: kernel.drain() });
|
|
126
|
+
checkWaiters();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
case "answer": {
|
|
130
|
+
const v = f.v;
|
|
131
|
+
const r = answerOf(v.entry);
|
|
132
|
+
if (atRest(r) || Number(v.wait ?? 0) <= 0) {
|
|
133
|
+
port.postMessage({ re: id, frame: r });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
waiters.push({ id, entry: v.entry, until: Date.now() + Number(v.wait) });
|
|
137
|
+
checkWaiters();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
case "idle":
|
|
141
|
+
port.postMessage({ re: id, frame: reply({ ok: null }) });
|
|
142
|
+
return; // nothing runs between calls
|
|
143
|
+
case "chain-import": {
|
|
144
|
+
// #156: the image's chain/headers read from the instance's set (read only), as store 2.
|
|
145
|
+
const v = f.v;
|
|
146
|
+
stores[2] = new SqliteSetStore(db, v.set, { readOnly: true });
|
|
147
|
+
try {
|
|
148
|
+
port.postMessage({ re: id, frame: kernel.call(dagCbor.encode({ op: "chain-import", v: { root: v.root, store: 2 } })) });
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
delete stores[2];
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
case "stop": {
|
|
156
|
+
if (timer)
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
for (const w of waiters)
|
|
159
|
+
port.postMessage({ re: w.id, frame: answerOf(w.entry) });
|
|
160
|
+
waiters = [];
|
|
161
|
+
db.close();
|
|
162
|
+
port.postMessage({ re: id, frame: reply({ ok: null }) });
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|
|
165
|
+
default:
|
|
166
|
+
port.postMessage({ re: id, frame: kernel.call(raw) });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
port.on("message", (m) => {
|
|
170
|
+
const f = dagCbor.decode(m.frame);
|
|
171
|
+
try {
|
|
172
|
+
handle(m.id, f, m.frame);
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
port.postMessage({ re: m.id, frame: reply({ error: `${f.op}: ${e?.message ?? String(e)}` }) });
|
|
176
|
+
}
|
|
177
|
+
if (fatal) {
|
|
178
|
+
try {
|
|
179
|
+
db.close();
|
|
180
|
+
}
|
|
181
|
+
catch { /* closing anyway */ }
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
port.postMessage({ ready: true });
|
|
186
|
+
void enc;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Listed } from "../client/raw.ts";
|
|
2
|
+
import { Cron, type ScheduleStore } from "../host/cron.ts";
|
|
3
|
+
export interface CronPeerOptions {
|
|
4
|
+
/** Its mailbox instance (a RawBox as the service's key): requests arrive in box `cron`. */
|
|
5
|
+
inbox: {
|
|
6
|
+
list(box: string): Promise<Listed[]>;
|
|
7
|
+
ack(ids: string[]): Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
/** A session with the messagebox at `url` (a RawBox as the service's key). */
|
|
10
|
+
outbox(url: string): {
|
|
11
|
+
send(recipient: string, box: string, body: unknown): Promise<unknown>;
|
|
12
|
+
};
|
|
13
|
+
/** Its address book: the messagebox URL of the sender `key`; undefined: no route. */
|
|
14
|
+
addressOf(key: string): string | undefined;
|
|
15
|
+
/** The clock (ms); default Date.now. */
|
|
16
|
+
now?(): number;
|
|
17
|
+
/** Where the schedules are kept (default memory). */
|
|
18
|
+
store?: ScheduleStore;
|
|
19
|
+
log?(line: string): void;
|
|
20
|
+
}
|
|
21
|
+
export declare class CronPeer {
|
|
22
|
+
readonly o: CronPeerOptions;
|
|
23
|
+
readonly cron: Cron;
|
|
24
|
+
private timer?;
|
|
25
|
+
private polling?;
|
|
26
|
+
constructor(o: CronPeerOptions);
|
|
27
|
+
private say;
|
|
28
|
+
/** Take up the kept schedules, and collect the `cron` box every `ms`. */
|
|
29
|
+
start(ms?: number): void;
|
|
30
|
+
stop(): Promise<void>;
|
|
31
|
+
/** Collect the `cron` box once: each request answered at its sender's messagebox, then acknowledged. How many were answered. */
|
|
32
|
+
poll(): Promise<number>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// A remote cron service (#69): the cron provider as a peer of its own,
|
|
2
|
+
// reached by mailbox. An instance whose address book names the cron
|
|
3
|
+
// provider's key with transport `mailbox` (its messagebox URL) emits its tick
|
|
4
|
+
// requests exactly as it would to the host's own (`local`) cron provider:
|
|
5
|
+
// the instance's delivery thread carries each over BRC-103/104 into the
|
|
6
|
+
// service's mailbox instance; the program does not know which it talks to.
|
|
7
|
+
//
|
|
8
|
+
// The service collects its `cron` box there (raw BRC-33 on a BRC-104
|
|
9
|
+
// session as its own key, like the inference peer: src/peers/infer.ts),
|
|
10
|
+
// keeps the schedules (src/host/cron.ts: the same `Cron` the host's provider
|
|
11
|
+
// runs; the instance is the sender's key) and answers each request — and
|
|
12
|
+
// sends each tick — at the sender's messagebox, on a session of its own: a
|
|
13
|
+
// message from the service's key, `replyTo` the request's id for an answer,
|
|
14
|
+
// none for a tick. Where a sender's messagebox is comes from the service's
|
|
15
|
+
// own address book (its admin's: `addressOf`); a request from a key it does
|
|
16
|
+
// not know has nowhere to go and is dropped with one line.
|
|
17
|
+
import { CID } from "multiformats/cid";
|
|
18
|
+
import { Cron } from "../host/cron.js";
|
|
19
|
+
const short = (s) => s.slice(-8);
|
|
20
|
+
export class CronPeer {
|
|
21
|
+
o;
|
|
22
|
+
cron;
|
|
23
|
+
timer;
|
|
24
|
+
polling;
|
|
25
|
+
constructor(o) {
|
|
26
|
+
this.o = o;
|
|
27
|
+
this.cron = new Cron({
|
|
28
|
+
now: () => (o.now ?? Date.now)(), store: o.store, log: (_s, l) => this.say(l),
|
|
29
|
+
tick: async (s, body) => {
|
|
30
|
+
const url = this.o.addressOf(s.recipient);
|
|
31
|
+
if (!url)
|
|
32
|
+
throw new Error(`no route to ${short(s.recipient)}`);
|
|
33
|
+
await this.o.outbox(url).send(s.recipient, s.box, body);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
say(line) { this.o.log?.(line); }
|
|
38
|
+
/** Take up the kept schedules, and collect the `cron` box every `ms`. */
|
|
39
|
+
start(ms = 1000) {
|
|
40
|
+
this.cron.start();
|
|
41
|
+
const tick = () => { if (!this.polling)
|
|
42
|
+
this.polling = this.poll().catch((e) => this.say(`cron: ${e.message}`)).finally(() => { this.polling = undefined; }); };
|
|
43
|
+
tick();
|
|
44
|
+
this.timer = setInterval(tick, ms);
|
|
45
|
+
}
|
|
46
|
+
async stop() {
|
|
47
|
+
if (this.timer)
|
|
48
|
+
clearInterval(this.timer);
|
|
49
|
+
await this.polling;
|
|
50
|
+
await this.cron.stop();
|
|
51
|
+
}
|
|
52
|
+
/** Collect the `cron` box once: each request answered at its sender's messagebox, then acknowledged. How many were answered. */
|
|
53
|
+
async poll() {
|
|
54
|
+
let n = 0;
|
|
55
|
+
for (const m of await this.o.inbox.list("cron")) {
|
|
56
|
+
const url = this.o.addressOf(m.sender);
|
|
57
|
+
if (!url) {
|
|
58
|
+
this.say(`cron ${short(m.messageId)} from ${short(m.sender)}: no route: ${m.sender} is not in the address book; dropped`);
|
|
59
|
+
await this.o.inbox.ack([m.messageId]);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const answer = this.cron.request(m.sender, m.sender, m.messageId, m.value);
|
|
63
|
+
try {
|
|
64
|
+
await this.o.outbox(url).send(m.sender, "cron", { replyTo: CID.parse(m.messageId), ...answer });
|
|
65
|
+
n++;
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
this.say(`cron ${short(m.messageId)}: answer to ${url}: ${e.message} (left unacknowledged)`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
await this.o.inbox.ack([m.messageId]);
|
|
72
|
+
}
|
|
73
|
+
return n;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --experimental-strip-types --no-warnings
|
|
2
|
+
// `skein-infer`: the inference peer process (infer.ts). Its environment
|
|
3
|
+
// (bin/skein-infer fills it from ~/.skein):
|
|
4
|
+
// SKEIN_INFER_WALLET_URL its BRC-100 wallet, default http://127.0.0.1:3323 (`1sat serve wallet-api`), origin "skein-infer"
|
|
5
|
+
// SKEIN_MAILBOX_URL its mailbox instance (#40), e.g. http://infer.localhost:8100 or http://127.0.0.1:8100/@infer
|
|
6
|
+
// SKEIN_INFER_PEERS its address book (#40): {"<identity key hex>": "<messagebox URL>", …}, default
|
|
7
|
+
// $SKEIN_HOME/infer-peers.json (scripts/host/up.sh writes every agent's); read again
|
|
8
|
+
// when it changes. A request from a key not in it has nowhere to go: dropped, one line.
|
|
9
|
+
// SKEIN_INFER_PROVIDERS the provider map file, default $SKEIN_HOME/infer.json
|
|
10
|
+
// SKEIN_INFER_HANDLE its handle, default infer@localhost
|
|
11
|
+
// SKEIN_INFER_CACHE its conversation graph on disk (issue #12), default $SKEIN_HOME/infer-cache;
|
|
12
|
+
// "" keeps it in memory only
|
|
13
|
+
// SKEIN_INFER_NODES how many nodes it holds in memory, default 50000
|
|
14
|
+
// SKEIN_POLL_MS default 1000
|
|
15
|
+
import { readFileSync, statSync } from "node:fs";
|
|
16
|
+
import { RawBox } from "../client/raw.js";
|
|
17
|
+
import { connectWallet } from "../wallet.js";
|
|
18
|
+
import { InferPeer, NodeGraph } from "./infer.js";
|
|
19
|
+
const env = process.env;
|
|
20
|
+
const home = env.SKEIN_HOME || `${env.HOME}/.skein`;
|
|
21
|
+
const say = (line) => process.stdout.write(`${line}\n`);
|
|
22
|
+
const die = (msg) => { process.stderr.write(`skein-infer: ${msg}\n`); process.exit(1); };
|
|
23
|
+
const providersFile = env.SKEIN_INFER_PROVIDERS || `${home}/infer.json`;
|
|
24
|
+
let providers;
|
|
25
|
+
try {
|
|
26
|
+
providers = JSON.parse(readFileSync(providersFile, "utf8"));
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
die(`${providersFile}: ${e.message}`);
|
|
30
|
+
}
|
|
31
|
+
if (!env.SKEIN_MAILBOX_URL)
|
|
32
|
+
die("SKEIN_MAILBOX_URL is not set (its mailbox instance: skein-host add infer --mailbox --owner <its key>)");
|
|
33
|
+
const wallet = await connectWallet({ kind: "remote", url: env.SKEIN_INFER_WALLET_URL || "http://127.0.0.1:3323", originator: "skein-infer" });
|
|
34
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
35
|
+
const cache = env.SKEIN_INFER_CACHE ?? `${home}/infer-cache`;
|
|
36
|
+
const graph = new NodeGraph({ dir: cache || undefined, max: env.SKEIN_INFER_NODES ? Number(env.SKEIN_INFER_NODES) : undefined });
|
|
37
|
+
const peersFile = env.SKEIN_INFER_PEERS || `${home}/infer-peers.json`;
|
|
38
|
+
let book = { mtime: -1, peers: {} };
|
|
39
|
+
/** The address book, read again when the file changed (the admin edits it; up.sh rewrites it). */
|
|
40
|
+
const addressOf = (key) => {
|
|
41
|
+
let mtime = 0;
|
|
42
|
+
try {
|
|
43
|
+
mtime = statSync(peersFile).mtimeMs;
|
|
44
|
+
}
|
|
45
|
+
catch { /* none: an empty book */ }
|
|
46
|
+
if (mtime !== book.mtime) {
|
|
47
|
+
try {
|
|
48
|
+
book = { mtime, peers: mtime ? JSON.parse(readFileSync(peersFile, "utf8")) : {} };
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
say(`${peersFile}: ${e.message}`);
|
|
52
|
+
book = { mtime, peers: {} };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const url = book.peers[key];
|
|
56
|
+
return typeof url === "string" && url ? url : undefined;
|
|
57
|
+
};
|
|
58
|
+
const outboxes = new Map();
|
|
59
|
+
const peer = new InferPeer({
|
|
60
|
+
graph, wallet, providers: providers, handle: env.SKEIN_INFER_HANDLE, log: say,
|
|
61
|
+
raw: {
|
|
62
|
+
inbox: new RawBox(wallet, env.SKEIN_MAILBOX_URL, { originator: "skein-infer" }),
|
|
63
|
+
outbox: (url) => { let b = outboxes.get(url); if (!b) {
|
|
64
|
+
b = new RawBox(wallet, url, { originator: "skein-infer" });
|
|
65
|
+
outboxes.set(url, b);
|
|
66
|
+
} return b; },
|
|
67
|
+
addressOf,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
peer.start(Number(env.SKEIN_POLL_MS || 1000));
|
|
71
|
+
say(`skein-infer ${publicKey} · providers ${Object.keys(providers).join(",")} · mailbox ${env.SKEIN_MAILBOX_URL} · graph ${cache || "in memory"} · address book ${peersFile}`);
|
|
72
|
+
const stop = async (sig) => { say(`${sig}: stopping`); await peer.stop(); process.exit(0); };
|
|
73
|
+
process.once("SIGINT", () => void stop("SIGINT"));
|
|
74
|
+
process.once("SIGTERM", () => void stop("SIGTERM"));
|