@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,342 @@
|
|
|
1
|
+
// The host's opener of instance stores (#96, #133): a SQLite file through
|
|
2
|
+
// src/runtime/index-store.ts, or a set of the host's LMDB environment,
|
|
3
|
+
// `lmdb:<env dir>#<set>` (index-store.ts `storeSpec`), read here directly,
|
|
4
|
+
// read only, through lmdb-js — with the kernel's member gate
|
|
5
|
+
// (kernel-zig/src/lmdb_store.zig): a block is there if the set holds it, or
|
|
6
|
+
// the chain's set does. The disk is the host's (src/runtime reads none).
|
|
7
|
+
//
|
|
8
|
+
// A set of the one-process kernel's RocksDB database, `rocksdb:<db dir>#<set>`
|
|
9
|
+
// (#158), is read through that process — RocksDB lets no second process see
|
|
10
|
+
// the live database — over its host-read socket (`<db dir>/kernel.sock`,
|
|
11
|
+
// kernel-zig/src/host.zig): `get`/`has` by CID through the set's member gate
|
|
12
|
+
// (the frame names the `set`: a block only another instance holds is absent,
|
|
13
|
+
// as for an LMDB set), the set's `state` pointer, and `members` (the CIDs it
|
|
14
|
+
// holds: a checkpoint's extras), the frames its stdin takes. The index reader here is
|
|
15
|
+
// synchronous, so the socket is spoken from a worker thread the caller waits
|
|
16
|
+
// on (Atomics.wait, the answer taken with receiveMessageOnPort). With no
|
|
17
|
+
// kernel process running there, a read is refused: the host must run.
|
|
18
|
+
import { existsSync } from "node:fs";
|
|
19
|
+
import { createRequire } from "node:module";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { DatabaseSync } from "node:sqlite";
|
|
22
|
+
import { MessageChannel, receiveMessageOnPort, Worker } from "node:worker_threads";
|
|
23
|
+
import { decode, encode, fromBytes } from "../runtime/cid.js";
|
|
24
|
+
import { CHAIN_SET, indexStoreOver, openStoreFile as openFile, parseLmdbSpec, parseRocksSpec, parseSqliteSpec } from "../runtime/index-store.js";
|
|
25
|
+
import { openHostDb, setHasState } from "./store-sqlite.js";
|
|
26
|
+
import { KernelHost } from "./kernel.js";
|
|
27
|
+
/** A store, whichever backend: a file (index-store.ts openStoreFile), or an LMDB set, read only. */
|
|
28
|
+
export function openStoreFile(spec, o = {}) {
|
|
29
|
+
const q = parseSqliteSpec(spec);
|
|
30
|
+
if (q) {
|
|
31
|
+
if (o.readOnly === false)
|
|
32
|
+
throw new Error(`${spec}: a SQLite set is read only here (the kernel writes it)`);
|
|
33
|
+
return openSqliteSetStore(q.db, q.set);
|
|
34
|
+
}
|
|
35
|
+
const r = parseRocksSpec(spec);
|
|
36
|
+
if (r) {
|
|
37
|
+
if (o.readOnly === false)
|
|
38
|
+
throw new Error(`${spec}: a RocksDB set is read only here (the kernel writes it; the host never does)`);
|
|
39
|
+
return openRocksStore(r.dir, r.set);
|
|
40
|
+
}
|
|
41
|
+
const l = parseLmdbSpec(spec);
|
|
42
|
+
if (!l)
|
|
43
|
+
return openFile(spec, o);
|
|
44
|
+
if (o.readOnly === false)
|
|
45
|
+
throw new Error(`${spec}: an LMDB set is read only here (the kernel writes it; the host never does)`);
|
|
46
|
+
return openLmdbStore(l.dir, l.set);
|
|
47
|
+
}
|
|
48
|
+
/** Whether a store is there to read: the file; for an LMDB set, its state pointer (the kernel has committed). */
|
|
49
|
+
export function storeExists(spec) {
|
|
50
|
+
const q = parseSqliteSpec(spec);
|
|
51
|
+
if (q) {
|
|
52
|
+
if (!existsSync(q.db))
|
|
53
|
+
return false;
|
|
54
|
+
const db = new DatabaseSync(q.db, { readOnly: true });
|
|
55
|
+
try {
|
|
56
|
+
return setHasState(db, q.set);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
db.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const r = parseRocksSpec(spec);
|
|
63
|
+
if (r)
|
|
64
|
+
return rocksReader(r.dir).request({ op: "state", h: r.set }) != null;
|
|
65
|
+
const l = parseLmdbSpec(spec);
|
|
66
|
+
if (!l)
|
|
67
|
+
return existsSync(spec);
|
|
68
|
+
if (!existsSync(join(l.dir, "data.mdb")))
|
|
69
|
+
return false;
|
|
70
|
+
const e = lmdbEnv(l.dir);
|
|
71
|
+
e.pointers.resetReadTxn();
|
|
72
|
+
return e.pointers.doesExist(Buffer.concat([setPrefix(l.set), Buffer.from("state")]));
|
|
73
|
+
}
|
|
74
|
+
const envs = new Map();
|
|
75
|
+
/**
|
|
76
|
+
* The environment at `dir`, opened read only once per process (LMDB: one open per process) and
|
|
77
|
+
* kept. lmdb-js must link LMDB 0.9.x (its data-v1 build: `LMDB_DATA_V1=true npm rebuild lmdb
|
|
78
|
+
* --build-from-source`, kernel-zig/README.md): its prebuilt binaries link LMDB 0.9.90 (data format
|
|
79
|
+
* 2, LMDB's 1.0 line), which cannot open the kernel's 0.9.36 environment.
|
|
80
|
+
*/
|
|
81
|
+
function lmdbEnv(dir) {
|
|
82
|
+
const had = envs.get(dir);
|
|
83
|
+
if (had)
|
|
84
|
+
return had;
|
|
85
|
+
const lmdb = createRequire(import.meta.url)("lmdb");
|
|
86
|
+
const v = lmdb.version;
|
|
87
|
+
if (v.major !== 0 || v.minor !== 9 || v.patch >= 90)
|
|
88
|
+
throw new Error(`lmdb-js links LMDB ${v.major}.${v.minor}.${v.patch}, not 0.9.x: it cannot read the kernel's environment (rebuild it: LMDB_DATA_V1=true npm rebuild lmdb --build-from-source)`);
|
|
89
|
+
const env = lmdb.open({ path: dir, readOnly: true, noSubdir: false, maxDbs: 4, maxReaders: 4096, mapSize: 2 ** 40 });
|
|
90
|
+
const db = (name) => env.openDB({ name, encoding: "binary", keyEncoding: "binary" });
|
|
91
|
+
const e = { blocks: db("blocks"), members: db("members"), pointers: db("pointers") };
|
|
92
|
+
envs.set(dir, e);
|
|
93
|
+
return e;
|
|
94
|
+
}
|
|
95
|
+
const setPrefix = (set) => Buffer.concat([Buffer.from(set), Buffer.from([0])]);
|
|
96
|
+
const prefixEnd = (p) => { const b = Buffer.from(p); b[b.length - 1]++; return b; };
|
|
97
|
+
/**
|
|
98
|
+
* A set of an LMDB environment through the index (#96), read only: the same reader as a SQLite
|
|
99
|
+
* file's, with the kernel's gate — a block is there if the set holds it, or the chain's set does
|
|
100
|
+
* (#133); one only another set holds is absent. Each state read takes a fresh snapshot.
|
|
101
|
+
*/
|
|
102
|
+
export function openLmdbStore(dir, set) {
|
|
103
|
+
const e = lmdbEnv(dir);
|
|
104
|
+
const own = setPrefix(set), chain = setPrefix(CHAIN_SET);
|
|
105
|
+
const member = (c) => e.members.doesExist(Buffer.concat([own, c])) || e.members.doesExist(Buffer.concat([chain, c]));
|
|
106
|
+
return indexStoreOver({
|
|
107
|
+
get: (c) => (member(c) ? e.blocks.get(c) : undefined),
|
|
108
|
+
has: (c) => member(c) && e.blocks.doesExist(c),
|
|
109
|
+
pointer: () => { e.pointers.resetReadTxn(); return e.pointers.get(Buffer.concat([own, Buffer.from("state")])); },
|
|
110
|
+
close: () => { },
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* A set of the host's SQLite database (the settled split), read only, through the index: the same
|
|
115
|
+
* reader as a file's, with the gate the wasm host's store keeps (store-sqlite.ts) — a block is there
|
|
116
|
+
* if the set holds it, or the `host/chain` set does. Its own read-only connection, closed with it.
|
|
117
|
+
*/
|
|
118
|
+
export function openSqliteSetStore(path, set) {
|
|
119
|
+
const db = openHostDb(path, { readOnly: true });
|
|
120
|
+
const get = db.prepare(`SELECT b.bytes FROM members m JOIN blocks b ON b.cid = m.cid WHERE m.cid = ?3 AND (m."set" = ?1 OR m."set" = ?2) LIMIT 1`);
|
|
121
|
+
const has = db.prepare(`SELECT 1 FROM members m WHERE m.cid = ?3 AND (m."set" = ?1 OR m."set" = ?2) LIMIT 1`);
|
|
122
|
+
const ptr = db.prepare(`SELECT cid FROM pointers WHERE "set" = ? AND name = 'state'`);
|
|
123
|
+
return indexStoreOver({
|
|
124
|
+
get: (c) => { const r = get.get(set, CHAIN_SET, c); return r ? new Uint8Array(r.bytes) : undefined; },
|
|
125
|
+
has: (c) => has.get(set, CHAIN_SET, c) !== undefined,
|
|
126
|
+
pointer: () => { const r = ptr.get(set); return r ? new Uint8Array(r.cid) : undefined; },
|
|
127
|
+
close: () => db.close(),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The host chain a store sees (#156): a RocksDB database's (the kernel process's `chain` frame over
|
|
132
|
+
* its host-read socket), or a SQLite file's or an LMDB set's own (a serve instance's: the pointer
|
|
133
|
+
* `host/chain` and the record it names, read directly).
|
|
134
|
+
*/
|
|
135
|
+
export function readHostChain(spec) {
|
|
136
|
+
const q = parseSqliteSpec(spec);
|
|
137
|
+
if (q) {
|
|
138
|
+
// The host's database: the `host/chain` set's pointer `host/chain` (host_chain.zig), and the record it names.
|
|
139
|
+
if (!existsSync(q.db))
|
|
140
|
+
return undefined;
|
|
141
|
+
const db = new DatabaseSync(q.db, { readOnly: true });
|
|
142
|
+
try {
|
|
143
|
+
const p = db.prepare(`SELECT cid FROM pointers WHERE "set" = ? AND name = 'host/chain'`).get(CHAIN_SET);
|
|
144
|
+
if (!p)
|
|
145
|
+
return undefined;
|
|
146
|
+
const b = db.prepare("SELECT bytes FROM blocks WHERE cid = ?").get(p.cid);
|
|
147
|
+
if (!b)
|
|
148
|
+
return undefined;
|
|
149
|
+
const v = decode(new Uint8Array(b.bytes));
|
|
150
|
+
return v.tip ? { root: fromBytes(new Uint8Array(p.cid)), height: Number(v.tip.height), hash: v.tip.hash } : undefined;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
db.close();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const r = parseRocksSpec(spec);
|
|
160
|
+
if (r) {
|
|
161
|
+
const v = rocksReader(r.dir).request({ op: "chain" });
|
|
162
|
+
return v ? { root: v.root, height: Number(v.height), hash: v.hash } : undefined;
|
|
163
|
+
}
|
|
164
|
+
let ptr;
|
|
165
|
+
let get;
|
|
166
|
+
const l = parseLmdbSpec(spec);
|
|
167
|
+
if (l) {
|
|
168
|
+
if (!existsSync(join(l.dir, "data.mdb")))
|
|
169
|
+
return undefined;
|
|
170
|
+
const e = lmdbEnv(l.dir);
|
|
171
|
+
e.pointers.resetReadTxn();
|
|
172
|
+
ptr = e.pointers.get(Buffer.concat([setPrefix(l.set), Buffer.from("host/chain")]));
|
|
173
|
+
get = (c) => e.blocks.get(c);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
if (!existsSync(spec))
|
|
177
|
+
return undefined;
|
|
178
|
+
const db = new DatabaseSync(spec, { readOnly: true });
|
|
179
|
+
try {
|
|
180
|
+
const t = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'pointers'").get();
|
|
181
|
+
ptr = t ? db.prepare("SELECT cid FROM pointers WHERE name = 'host/chain'").get()?.cid : undefined;
|
|
182
|
+
if (!ptr)
|
|
183
|
+
return undefined;
|
|
184
|
+
const b = db.prepare("SELECT bytes FROM blocks WHERE cid = ?").get(ptr)?.bytes;
|
|
185
|
+
get = () => b;
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
db.close();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (!ptr)
|
|
192
|
+
return undefined;
|
|
193
|
+
const bytes = get(ptr);
|
|
194
|
+
if (!bytes)
|
|
195
|
+
return undefined;
|
|
196
|
+
const v = decode(bytes);
|
|
197
|
+
if (!v.tip)
|
|
198
|
+
return undefined;
|
|
199
|
+
return { root: fromBytes(ptr), height: Number(v.tip.height), hash: v.tip.hash };
|
|
200
|
+
}
|
|
201
|
+
/** Every block a store holds (a SQLite file's `blocks`; an LMDB set's members), for a checkpoint's extras. */
|
|
202
|
+
export function* storeBlocks(spec) {
|
|
203
|
+
const q = parseSqliteSpec(spec);
|
|
204
|
+
if (q) {
|
|
205
|
+
const db = new DatabaseSync(q.db, { readOnly: true });
|
|
206
|
+
try {
|
|
207
|
+
for (const r of db.prepare(`SELECT m.cid, b.bytes FROM members m JOIN blocks b ON b.cid = m.cid WHERE m."set" = ?`).all(q.set))
|
|
208
|
+
yield { cid: fromBytes(new Uint8Array(r.cid)), bytes: new Uint8Array(r.bytes) };
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
db.close();
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const r = parseRocksSpec(spec);
|
|
216
|
+
if (r) {
|
|
217
|
+
// #158: the set's members, listed by the kernel; each block read through it.
|
|
218
|
+
const rd = rocksReader(r.dir);
|
|
219
|
+
for (const c of rd.request({ op: "members", set: r.set })) {
|
|
220
|
+
const bytes = rd.request({ op: "get", set: r.set, v: c });
|
|
221
|
+
if (bytes instanceof Uint8Array)
|
|
222
|
+
yield { cid: c, bytes };
|
|
223
|
+
}
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const l = parseLmdbSpec(spec);
|
|
227
|
+
if (l) {
|
|
228
|
+
const e = lmdbEnv(l.dir);
|
|
229
|
+
e.members.resetReadTxn();
|
|
230
|
+
const own = setPrefix(l.set);
|
|
231
|
+
for (const k of e.members.getKeys({ start: own, end: prefixEnd(own) })) {
|
|
232
|
+
const c = new Uint8Array(k.subarray(own.length));
|
|
233
|
+
const bytes = e.blocks.get(c);
|
|
234
|
+
if (bytes)
|
|
235
|
+
yield { cid: fromBytes(c), bytes: new Uint8Array(bytes) };
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const db = new DatabaseSync(spec, { readOnly: true });
|
|
240
|
+
try {
|
|
241
|
+
for (const r of db.prepare("SELECT cid, bytes FROM blocks").all())
|
|
242
|
+
yield { cid: fromBytes(new Uint8Array(r.cid)), bytes: new Uint8Array(r.bytes) };
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
db.close();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// ---------------------------------------------------------------- RocksDB, through the kernel (#158)
|
|
249
|
+
/** The worker thread: speaks the host-read socket for a waiting caller (one request at a time). */
|
|
250
|
+
const READER_WORKER = `
|
|
251
|
+
const { workerData } = require("node:worker_threads");
|
|
252
|
+
const net = require("node:net");
|
|
253
|
+
const { sock, flag, port } = workerData;
|
|
254
|
+
const i32 = new Int32Array(flag);
|
|
255
|
+
let conn, buf = Buffer.alloc(0), pending = false;
|
|
256
|
+
const done = (m) => { port.postMessage(m); Atomics.store(i32, 0, 1); Atomics.notify(i32, 0); };
|
|
257
|
+
function connect(until) {
|
|
258
|
+
return new Promise((resolve, reject) => {
|
|
259
|
+
const c = net.createConnection(sock);
|
|
260
|
+
c.once("connect", () => {
|
|
261
|
+
c.removeAllListeners("error");
|
|
262
|
+
c.on("error", () => {});
|
|
263
|
+
c.on("close", () => { if (conn === c) conn = undefined; if (pending) { pending = false; done({ error: "the kernel's socket closed" }); } });
|
|
264
|
+
c.on("data", onData);
|
|
265
|
+
resolve(c);
|
|
266
|
+
});
|
|
267
|
+
c.once("error", (e) => { if (Date.now() < until) setTimeout(() => connect(until).then(resolve, reject), 50); else reject(e); });
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
function onData(d) {
|
|
271
|
+
buf = Buffer.concat([buf, d]);
|
|
272
|
+
while (buf.length >= 4) {
|
|
273
|
+
const n = buf.readUInt32BE(0);
|
|
274
|
+
if (buf.length < 4 + n) return;
|
|
275
|
+
const f = new Uint8Array(buf.subarray(4, 4 + n));
|
|
276
|
+
buf = buf.subarray(4 + n);
|
|
277
|
+
if (pending) { pending = false; done({ frame: f }); }
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
port.on("message", async ({ bytes, waitMs }) => {
|
|
281
|
+
try {
|
|
282
|
+
if (!conn) { buf = Buffer.alloc(0); conn = await connect(Date.now() + waitMs); }
|
|
283
|
+
pending = true;
|
|
284
|
+
const h = Buffer.alloc(4); h.writeUInt32BE(bytes.length);
|
|
285
|
+
conn.write(Buffer.concat([h, Buffer.from(bytes)]));
|
|
286
|
+
} catch (e) { done({ error: (e && e.code) || String(e) }); }
|
|
287
|
+
});
|
|
288
|
+
`;
|
|
289
|
+
class RocksReader {
|
|
290
|
+
flag = new Int32Array(new SharedArrayBuffer(4));
|
|
291
|
+
port;
|
|
292
|
+
worker;
|
|
293
|
+
nextId = 1;
|
|
294
|
+
dir;
|
|
295
|
+
constructor(dir) {
|
|
296
|
+
this.dir = dir;
|
|
297
|
+
const { port1, port2 } = new MessageChannel();
|
|
298
|
+
this.port = port1;
|
|
299
|
+
this.worker = new Worker(READER_WORKER, { eval: true, workerData: { sock: join(dir, "kernel.sock"), flag: this.flag.buffer, port: port2 }, transferList: [port2] });
|
|
300
|
+
this.worker.unref();
|
|
301
|
+
this.port.unref();
|
|
302
|
+
}
|
|
303
|
+
/** One frame to the kernel and its `ok`, synchronously. */
|
|
304
|
+
request(f) {
|
|
305
|
+
// The process this node process runs may still be starting: wait for its socket a while; else not at all.
|
|
306
|
+
const waitMs = KernelHost.running(this.dir) ? 15_000 : 0;
|
|
307
|
+
Atomics.store(this.flag, 0, 0);
|
|
308
|
+
this.port.postMessage({ bytes: encode({ id: this.nextId++, ...f }).bytes, waitMs });
|
|
309
|
+
if (Atomics.wait(this.flag, 0, 0, waitMs + 30_000) === "timed-out")
|
|
310
|
+
throw new Error(`${this.dir}: the kernel's host-read socket did not answer`);
|
|
311
|
+
const m = receiveMessageOnPort(this.port)?.message;
|
|
312
|
+
if (!m || m.error !== undefined)
|
|
313
|
+
throw new Error(`${this.dir}: a RocksDB set is read through the host's kernel process (skein-kernel host), and none answers at ${join(this.dir, "kernel.sock")} (${m?.error ?? "no answer"}): start the host (skein-host run)`);
|
|
314
|
+
const a = decode(m.frame);
|
|
315
|
+
if (typeof a.error === "string")
|
|
316
|
+
throw new Error(`${this.dir}: ${a.error}`);
|
|
317
|
+
return a.ok;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const readers = new Map();
|
|
321
|
+
function rocksReader(dir) {
|
|
322
|
+
let r = readers.get(dir);
|
|
323
|
+
if (!r) {
|
|
324
|
+
r = new RocksReader(dir);
|
|
325
|
+
readers.set(dir, r);
|
|
326
|
+
}
|
|
327
|
+
return r;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* A set of the one-process kernel's RocksDB database (#158), read only, through the kernel: blocks by
|
|
331
|
+
* CID through the set's gate (what the instance holds), the state pointer read on every state read.
|
|
332
|
+
*/
|
|
333
|
+
export function openRocksStore(dir, set) {
|
|
334
|
+
const r = rocksReader(dir);
|
|
335
|
+
const asBytes = (v) => (v instanceof Uint8Array ? v : undefined);
|
|
336
|
+
return indexStoreOver({
|
|
337
|
+
get: (c) => asBytes(r.request({ op: "get", set, v: fromBytes(c) })),
|
|
338
|
+
has: (c) => r.request({ op: "has", set, v: fromBytes(c) }) === true,
|
|
339
|
+
pointer: () => { const p = r.request({ op: "state", h: set }); return p ? p.bytes : undefined; },
|
|
340
|
+
close: () => { },
|
|
341
|
+
});
|
|
342
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { type InboundAnswer } from "./p2p.ts";
|
|
2
|
+
import type { LiveBeat } from "./liveness.ts";
|
|
3
|
+
export declare const SYNC_PROTOCOL = "/skein/overlay/sync/1.0.0";
|
|
4
|
+
/** The overlay's receive row (skein-overlay etc/app.json): the host's own entry, one Atomic BEEF per frame; never served to peers. */
|
|
5
|
+
export declare const BEEF_PROTOCOL = "/skein/overlay/beef/1.0.0";
|
|
6
|
+
export interface SyncOptions {
|
|
7
|
+
/** The app that beats on `<topic>-live` for `handle` (its overlay), if any. */
|
|
8
|
+
overlayOf(handle: string, liveTopic: string): string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Our own overlay's read (shruggr/skein#154): the engine's function `fn` (`anchors`, `admitted`,
|
|
11
|
+
* `holds`) over `handle`'s current state, with its read route's query; its JSON answer. Throws on
|
|
12
|
+
* a status other than 200.
|
|
13
|
+
*/
|
|
14
|
+
local(handle: string, app: string, fn: "anchors" | "admitted" | "holds", query: Record<string, string>): Promise<unknown>;
|
|
15
|
+
/** One BEEF into the overlay's receive row, as from `peer` (its peer ID). */
|
|
16
|
+
deliver(handle: string, peer: string, beef: Uint8Array): Promise<InboundAnswer>;
|
|
17
|
+
/** The chain state's header at `height`: its hash and merkle root (display hex). */
|
|
18
|
+
header(handle: string, height: number): Promise<{
|
|
19
|
+
hash: string;
|
|
20
|
+
root: string;
|
|
21
|
+
} | undefined>;
|
|
22
|
+
/** One request frame to `peer` on the sync protocol: its answer frame. */
|
|
23
|
+
request(handle: string, peer: string, body: Uint8Array): Promise<Uint8Array>;
|
|
24
|
+
/** The live beats on a liveness topic at `handle` (the liveness tool's: the latest per sender). */
|
|
25
|
+
beats(handle: string, liveTopic: string): LiveBeat[];
|
|
26
|
+
/** Our own beacon's body on a liveness topic (what our overlay declared last), if any. */
|
|
27
|
+
ownBeat(handle: string, liveTopic: string): Uint8Array | undefined;
|
|
28
|
+
/** `handle`'s own peer ID. */
|
|
29
|
+
self(handle: string): string;
|
|
30
|
+
log?(source: string, line: string): void;
|
|
31
|
+
/** The heights per ranged pull; default 1 000. */
|
|
32
|
+
rangeHeights?: number;
|
|
33
|
+
}
|
|
34
|
+
/** A peer ID's string from its multihash bytes. */
|
|
35
|
+
export declare const peerOf: (mh: Uint8Array) => string;
|
|
36
|
+
/**
|
|
37
|
+
* An `admit` announcement on `<topic>` (skein-overlay gossip.zig: dag-cbor `{kind: "admit", txid,
|
|
38
|
+
* topics: {<topic>: {outputsToAdmit, coinsToRetain}}}`) → its txid, or undefined when the body is
|
|
39
|
+
* not one naming `topic`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function admitOf(topic: string, body: Uint8Array): string | undefined;
|
|
42
|
+
export declare class OverlaySync {
|
|
43
|
+
private readonly o;
|
|
44
|
+
/** `${handle} ${txid}` → the announced transaction, while it is not taken. */
|
|
45
|
+
private announced;
|
|
46
|
+
/** Reconciles running: `${handle} ${topic} ${peer}`. */
|
|
47
|
+
private running;
|
|
48
|
+
/** The beat body (hex) that started, or ended, the last reconcile per `${handle} ${topic} ${peer}`. */
|
|
49
|
+
private seen;
|
|
50
|
+
/** Peers caught serving a list that does not check: per handle. */
|
|
51
|
+
private dropped;
|
|
52
|
+
private inflight;
|
|
53
|
+
private stopped;
|
|
54
|
+
constructor(o: SyncOptions);
|
|
55
|
+
private say;
|
|
56
|
+
private track;
|
|
57
|
+
/** Whether anything is under way (a reconcile, a fetch). */
|
|
58
|
+
busy(): boolean;
|
|
59
|
+
idle(): Promise<void>;
|
|
60
|
+
stop(): void;
|
|
61
|
+
/** The announced transactions not taken yet, per handle (tests). */
|
|
62
|
+
pending(handle: string): Array<{
|
|
63
|
+
txid: string;
|
|
64
|
+
peers: string[];
|
|
65
|
+
fetching: boolean;
|
|
66
|
+
}>;
|
|
67
|
+
private isDropped;
|
|
68
|
+
/**
|
|
69
|
+
* A peer's `admit` for `txid` on `topic` (its announcement), at `handle` whose overlay `app` runs
|
|
70
|
+
* the topic: fetched from that peer unless the instance holds it or a fetch of it is under way;
|
|
71
|
+
* the peer joins the transaction's set either way.
|
|
72
|
+
*/
|
|
73
|
+
admit(handle: string, app: string, topic: string, txid: string, peer: string): void;
|
|
74
|
+
/** `admit`'s work: the fetch it started, if any (the tail awaits it). */
|
|
75
|
+
private announce;
|
|
76
|
+
/** Whether `handle`'s overlay state reaches `txid` (its engine's `holds`). */
|
|
77
|
+
private holds;
|
|
78
|
+
/** Fetch an announced transaction from its peers, one after another, until one delivery is taken or none is left. */
|
|
79
|
+
private pursue;
|
|
80
|
+
/** A beat newly kept on `liveTopic` from another peer: a reconcile from it when its body changed since the last. */
|
|
81
|
+
beat(handle: string, liveTopic: string, b: LiveBeat): void;
|
|
82
|
+
/**
|
|
83
|
+
* A start (router.ts, the persisted peers): each peer that beat on the topic before asked at once,
|
|
84
|
+
* the locator and the tail, without a beat. One reconcile per peer, as for a beat.
|
|
85
|
+
*/
|
|
86
|
+
reconnect(handle: string, app: string, topic: string, peer: string): void;
|
|
87
|
+
private start;
|
|
88
|
+
private anchors;
|
|
89
|
+
private ask;
|
|
90
|
+
/** The beat body's sync fields, or undefined when it does not decode. */
|
|
91
|
+
private static fields;
|
|
92
|
+
/**
|
|
93
|
+
* One reconcile with `peer` on `topic`: its mined history (the locator and the pulls) when its
|
|
94
|
+
* tip anchor is not ours, then its unmined tail when the mined side is equal and its tail digest
|
|
95
|
+
* is not ours. No beat (a start): both asked.
|
|
96
|
+
*/
|
|
97
|
+
private reconcile;
|
|
98
|
+
/** The peer's unmined tail: each txid we do not hold fetched as an announced one from that peer. */
|
|
99
|
+
private tail;
|
|
100
|
+
/**
|
|
101
|
+
* Catch `topic` up from `peer`: the locator, then the pulls. `tip`: the tip its beat named (the
|
|
102
|
+
* ranges are spread across the peers whose last beat names the same); none (a start): that peer
|
|
103
|
+
* alone. → whether every range was taken (a height we hold no header for, or a list that fails,
|
|
104
|
+
* leaves it unfinished).
|
|
105
|
+
*/
|
|
106
|
+
private catchUp;
|
|
107
|
+
/** The peer's admitted lists for `from..to`, every page. */
|
|
108
|
+
private pullRange;
|
|
109
|
+
/**
|
|
110
|
+
* Each block against our headers before any transaction is fetched: its hash ours at its height,
|
|
111
|
+
* the BUMP at that height and every listed txid in it with our merkle root. A failure drops the
|
|
112
|
+
* peer; a height we hold no header for yet stops the range (the next changed beat resumes it).
|
|
113
|
+
* → whether all checked.
|
|
114
|
+
*/
|
|
115
|
+
private checked;
|
|
116
|
+
private problem;
|
|
117
|
+
/** One transaction asked of `peer` and handed to the receive row as from it. → whether it was accepted. */
|
|
118
|
+
private fetchInto;
|
|
119
|
+
/** Until the topic lists `txids` at `height` (admitted: each delivery is judged on its own thread), or 10 s. */
|
|
120
|
+
private landed;
|
|
121
|
+
}
|