| … | … | @@ -0,0 +1,3 @@ |
| 1 | node_modules | |
| 2 | work | |
| 3 | *.log |
| … | … | @@ -0,0 +1,105 @@ |
| 1 | # hyper-vdrive | |
| 2 | ||
| 3 | A **real** Hyperdrive that stores **no blob bytes**. | |
| 4 | ||
| 5 | The drive is genuine all the way up — real Hyperbee metadata, real Hyperblobs core, | |
| 6 | real merkle tree, real signatures, stock `hyperdrive` / `hyperblobs` / `hypercore` | |
| 7 | with no patches. Only the storage layer underneath is swapped: block bytes are never | |
| 8 | written to RocksDB. Instead each block record holds a 32-byte content hash, and the | |
| 9 | bytes are read on demand from a content-addressed store already on disk. | |
| 10 | ||
| 11 | Peers can't tell. `pear install pear://<key>` works against it. | |
| 12 | ||
| 13 | ## Why | |
| 14 | ||
| 15 | We already store every file once, chunked and content-addressed, in `hyper-overlay`. | |
| 16 | Publishing the same content as a Hyperdrive meant a second full copy in RocksDB. This | |
| 17 | removes the second copy while keeping `pear://` compatibility. | |
| 18 | ||
| 19 | Measured on ten consecutive real 367 MB game-pack releases: | |
| 20 | ||
| 21 | | | stock drive | virtual drive | | |
| 22 | |---|---|---| | |
| 23 | | store after 10 releases | 580.8 MB | **0.95 MB** | | |
| 24 | | publish time per release | 3.6 s | **2.4 s** | | |
| 25 | | drive key | — | **identical** | | |
| 26 | ||
| 27 | See [RESULTS.md](RESULTS.md) for the full measurements and method. | |
| 28 | ||
| 29 | ## How | |
| 30 | ||
| 31 | `Core` takes its per-core storage from exactly two calls | |
| 32 | (`hypercore/lib/core.js`): `db.resumeCore(discoveryKey)` and `db.createCore({...})`. | |
| 33 | Subclassing `CorestoreStorage` and wrapping those two return values puts us underneath | |
| 34 | every block read and write, with nothing above aware. | |
| 35 | ||
| 36 | Stored block records carry a one-byte tag: | |
| 37 | ||
| 38 | ``` | |
| 39 | 0x00 | <original bytes> literal — bee blocks, and anything we don't hold | |
| 40 | 0x01 | <32-byte hash> pointer — bytes live in the content store | |
| 41 | ``` | |
| 42 | ||
| 43 | Because the pointer *is* the block value, it commits in the same `CoreTX` as the tree | |
| 44 | node — tree and pointer land atomically, and `deleteBlock` / truncate free it with no | |
| 45 | extra bookkeeping. Cost: 1 byte per literal block, 33 bytes per virtualised block. | |
| 46 | ||
| 47 | The chunk hash is `hypercore-crypto`'s `data()` — the same construction hypercore uses | |
| 48 | for a merkle **leaf** (`merkle-tree.js`). So our existing FastCDC chunk hashes are | |
| 49 | already the leaf hashes, and a chunk maps directly onto a block. | |
| 50 | ||
| 51 | ## Usage | |
| 52 | ||
| 53 | ```js | |
| 54 | const { ContentStore, build } = require('hyper-vdrive') | |
| 55 | ||
| 56 | const content = new ContentStore() | |
| 57 | // tell it where bytes live: contentHash -> (file, offset, length) | |
| 58 | for (const c of chunk(buf)) content.add(c.hash, file, c.offset, c.length) | |
| 59 | ||
| 60 | const Storage = build(content) | |
| 61 | const store = new Corestore(new Storage(dir)) | |
| 62 | const drive = new Hyperdrive(store) | |
| 63 | ``` | |
| 64 | ||
| 65 | Write files chunk-by-chunk so block boundaries are your chunk boundaries: | |
| 66 | ||
| 67 | ```js | |
| 68 | const ws = drive.createWriteStream('/app.bin', { dedup: true }) | |
| 69 | for (const c of chunks) ws.write(c.data) // honour backpressure | |
| 70 | ws.end() | |
| 71 | ``` | |
| 72 | ||
| 73 | ## Tests | |
| 74 | ||
| 75 | Tests import the FastCDC chunker from a sibling `hyper-overlay` checkout | |
| 76 | (`../../hyper-overlay/lib/chunker.js`) rather than vendoring a copy that could drift. | |
| 77 | ||
| 78 | ``` | |
| 79 | npm install | |
| 80 | node test/pass-through.mjs ./work/passthrough # seam holds, identical drive key | |
| 81 | node test/redirect.mjs ./work/redirect <f1> <f2> | |
| 82 | node test/releases.mjs ./work/releases <f...> # N-release size curve | |
| 83 | node test/gc-bitfield.mjs ./work/gc # GC / bitfield pairing | |
| 84 | node example/publish.mjs ./work/app # pear-install-shaped drive | |
| 85 | node example/seed.mjs ./work/app # serve it on the DHT | |
| 86 | ``` | |
| 87 | ||
| 88 | ## Status — spike | |
| 89 | ||
| 90 | Proven: identical drive key/blobs key/root hash, reopen from disk, replication to a | |
| 91 | stock peer, and a real `pear install` from a drive holding zero blobs. | |
| 92 | ||
| 93 | Known gaps, in the order they'd bite: | |
| 94 | ||
| 95 | 1. **GC must be paired.** Dropping bytes does *not* update the bitfield — the core goes | |
| 96 | on advertising blocks it can't produce. GC has to clear the block range too. And with | |
| 97 | `dedup: true`, old releases share blocks with new ones, so ranges must be computed | |
| 98 | from the content store's refcounts, never from a version's block range. | |
| 99 | 2. **Block streams** (`createBlockStream`, used by audit/copy-prologue) yield raw tagged | |
| 100 | records; they need decoding like `getBlock` does. | |
| 101 | 3. **Encryption** sits above storage and prepends 8 bytes of padding, so block bytes stop | |
| 102 | equalling chunk bytes. Run unencrypted, or teach the adapter to re-derive. | |
| 103 | 4. **Unsupported seam.** `hypercore-storage` has no public pluggable-storage contract | |
| 104 | since `random-access-storage` was dropped. Pin versions and run the tests as a canary. | |
| 105 | 5. Untested: many small files, truncate/clear, crash during append. |
| … | … | @@ -0,0 +1,125 @@ |
| 1 | # Measurements | |
| 2 | ||
| 3 | All runs on tabertron, 2026-08-24. Source data: ten consecutive real `hypertps.pck` | |
| 4 | release artifacts (~367 MB each) from `~/.local/share/store/dependencies`, and | |
| 5 | synthetic files where noted. Chunker: `hyper-overlay/lib/chunker.js` (FastCDC, | |
| 6 | tier 2 → ~320 KB average chunk). | |
| 7 | ||
| 8 | ## T0 — does stock Pear read chunk-sized blocks? | |
| 9 | ||
| 10 | The installed `pear` 3.0.1 bundle (`~/.config/pear/current/boot.bundle`) contains | |
| 11 | `node_modules/hyperblobs/lib/block-map.js` and the marker strings `Block map is too | |
| 12 | large`, `Unsupported block map version`, `_dedup`, `getBlockMap`. So the shipped | |
| 13 | reader supports block maps and variable-size blocks. **Pass.** | |
| 14 | ||
| 15 | ## T1 — storage cost, one release pair | |
| 16 | ||
| 17 | Two consecutive releases, 367.06 MB each, into one drive at the same path. | |
| 18 | ||
| 19 | | Arm | after v1 | after v2 | v2 delta | | |
| 20 | |---|---|---|---| | |
| 21 | | overlay (control) | 360.1 MB | 378.3 MB | 18.2 MB | | |
| 22 | | hyperdrive, no dedup | 368.3 MB | 736.4 MB | 368.1 MB | | |
| 23 | | hyperdrive, `dedup:true`, fixed 64 KB | 367.6 MB | 488.7 MB | 121.2 MB | | |
| 24 | | hyperdrive, `dedup:true`, FastCDC | 360.5 MB | 378.7 MB | 18.2 MB | | |
| 25 | ||
| 26 | Two corrections to earlier assumptions: | |
| 27 | ||
| 28 | - RocksDB per-byte overhead is **0.3%**, not a multiplier (368.3 MB stored for | |
| 29 | 367.06 MB in). The corestore column family enables blob files with GC. | |
| 30 | - Hyperblobs **does** dedup, at block level, via `{dedup:true}` + block maps | |
| 31 | (`hyperblobs/lib/streams.js:86-101`, `hyperdrive/index.js:591-596`). A stock drive | |
| 32 | fed FastCDC chunks matched the overlay to within 0.1%. | |
| 33 | ||
| 34 | ## T1c — ten releases, stock drive vs overlay | |
| 35 | ||
| 36 | | Release | drive | overlay | gap | | |
| 37 | |---|---|---|---| | |
| 38 | | 1 | 360.5 MB | 360.1 MB | +0.4 | | |
| 39 | | 5 | 456.1 MB | 453.2 MB | +2.9 | | |
| 40 | | 6 | 484.8 MB | 471.4 MB | +13.4 | | |
| 41 | | 10 | 580.8 MB | 563.3 MB | +17.5 (3.1%) | | |
| 42 | ||
| 43 | The gap appears at release 6 — a chunk set that vanished and returned. `dedup:true` | |
| 44 | seeds its hash table from **only the previous entry at that path** | |
| 45 | (`hyperdrive/index.js:592`), so resurrected chunks are re-appended. Publish time was | |
| 46 | flat at ~3.6 s: the seeding re-read is O(previous release), not O(history). | |
| 47 | ||
| 48 | ## T2 — does the storage seam hold? | |
| 49 | ||
| 50 | Pass-through wrapper (delegates everything, redirects nothing): | |
| 51 | ||
| 52 | ``` | |
| 53 | identical drive key / blobs key / root hash / digests true | |
| 54 | reopen from disk true | |
| 55 | replication to a stock peer true | |
| 56 | intercepted 111 getBlock, 111 putBlock | |
| 57 | ``` | |
| 58 | ||
| 59 | Nothing escaped the wrapper. **Pass.** | |
| 60 | ||
| 61 | ## T3 — redirect, two releases | |
| 62 | ||
| 63 | ``` | |
| 64 | sameDriveKey / sameBlobsKey / sameRootHash true | |
| 65 | blobsLength stock 2244 virtual 2244 | |
| 66 | input 734.0 MB | |
| 67 | stock store 720.8 MB | |
| 68 | virtual store 1.03 MB (tagged-record scheme, no side file) | |
| 69 | peer store 720.7 MB (stock peer replicated from the virtual drive) | |
| 70 | blocks 2242 redirected, 5 literal (the bee's own) | |
| 71 | hash failures 0 | |
| 72 | ``` | |
| 73 | ||
| 74 | ## T3n — ten releases, virtual | |
| 75 | ||
| 76 | | Release | blocks | store | stock equivalent | | |
| 77 | |---|---|---|---| | |
| 78 | | 1 | 1122 | 0.50 MB | 360.5 MB | | |
| 79 | | 5 | 1368 | 0.50 MB | 456.1 MB | | |
| 80 | | 10 | 1682 | **0.82 MB** | **580.8 MB** | | |
| 81 | ||
| 82 | 3.67 GB of releases → 0.95 MB (**610×**). Publish ~2.4 s/release, *faster* than stock | |
| 83 | because the RocksDB block writes disappear. Pointer overhead is 36 bytes per block — | |
| 84 | ≈0.011% of content at tier-2 chunk sizes. | |
| 85 | ||
| 86 | ## T5 — real `pear install` | |
| 87 | ||
| 88 | Built a `/by-arch/linux-x64/app/` drive on virtual storage (6.29 MB payload, 120 KB | |
| 89 | store), seeded it on the public DHT, and ran the stock `pear` 3.0.1 CLI: | |
| 90 | ||
| 91 | ``` | |
| 92 | $ pear install pear://uimennwjwyot5cinekhb348kawufit1eq4ctyf5irurn1nfmo3so --to … | |
| 93 | [ Peers: 1 ] [ ⬇ 2.8MB - 2.8MB/s ] | |
| 94 | App: t5app Version: 1.0.0 | |
| 95 | Target: …/t5/installed/t5app | |
| 96 | Installed | |
| 97 | ``` | |
| 98 | ||
| 99 | Installed file: 6,291,480 bytes, mode 755, hash identical to source, runs. Seeder | |
| 100 | store unchanged at 139 KB; it served 6,291,560 bytes as 89 redirected block reads | |
| 101 | with 0 hash failures. **Pass.** | |
| 102 | ||
| 103 | ## GC / bitfield | |
| 104 | ||
| 105 | ``` | |
| 106 | host still advertises after bytes deleted TRUE ← the lie | |
| 107 | peer requesting it REQUEST_TIMEOUT after 5.0 s | |
| 108 | after core.clear(range): host advertises FALSE | |
| 109 | unrelated file still fetchable TRUE | |
| 110 | ``` | |
| 111 | ||
| 112 | `core.clear()` on the affected range is the correct pairing. Not demonstrated: that | |
| 113 | clearing turns a stall into "ask another peer" — with one seeder both look like a | |
| 114 | timeout, so proving it needs a two-seeder test. One run of the probe died with an | |
| 115 | *uncaught* `REQUEST_TIMEOUT` thrown from `hypercore/lib/replicator.js:3371` rather than | |
| 116 | rejecting `get()`; the rerun did not reproduce it. Racy, but a long-lived seeder in that | |
| 117 | state could take an uncaught exception. | |
| 118 | ||
| 119 | ## Upstream bug found | |
| 120 | ||
| 121 | A 0-byte file written with `dedup: true` cannot be read back: hyperblobs writes an | |
| 122 | empty block map, then `seekBlockMap` dereferences `map.blocks === null` | |
| 123 | (`hyperblobs/lib/streams.js:327`). Reproduced on a plain store, so it is upstream. | |
| 124 | Matters because app trees are full of empty files. Workaround: write 0-byte files | |
| 125 | without dedup. |
| … | … | @@ -0,0 +1,85 @@ |
| 1 | // T5 build — publish a pear-install-shaped app into a VIRTUAL drive (no blob bytes). | |
| 2 | // Layout required by pear-install/index.js: | |
| 3 | // /package.json (name, version, bin) | |
| 4 | // /by-arch/<platform>-<arch>/app/<bin> | |
| 5 | import { createRequire } from 'module' | |
| 6 | const require = createRequire(import.meta.url) | |
| 7 | ||
| 8 | const fs = require('fs') | |
| 9 | const path = require('path') | |
| 10 | const process = require('process') | |
| 11 | const crypto = require('hypercore-crypto') | |
| 12 | const idEnc = require('hypercore-id-encoding') | |
| 13 | const Corestore = require('corestore') | |
| 14 | const Hyperdrive = require('hyperdrive') | |
| 15 | const { ContentStore, build } = require('../lib/storage.cjs') | |
| 16 | const { chunk } = await import('../../hyper-overlay/lib/chunker.js') | |
| 17 | ||
| 18 | const ROOT = process.argv[2] | |
| 19 | const APP = 't5app' | |
| 20 | const HOST = process.platform + '-' + process.arch | |
| 21 | const srcDir = ROOT + '/src' | |
| 22 | const dir = ROOT + '/virtual' | |
| 23 | ||
| 24 | // The "overlay": real files on disk that stay canonical. | |
| 25 | fs.mkdirSync(srcDir, { recursive: true }) | |
| 26 | const binBytes = Buffer.concat([ | |
| 27 | Buffer.from('#!/bin/sh\necho t5app-ok\n'), | |
| 28 | crypto.randomBytes(6 * 1024 * 1024) // payload so replication is real work | |
| 29 | ]) | |
| 30 | const pkgBytes = Buffer.from( | |
| 31 | JSON.stringify({ name: APP, version: '1.0.0', bin: { [APP]: APP } }, null, 2) | |
| 32 | ) | |
| 33 | fs.writeFileSync(path.join(srcDir, APP), binBytes) | |
| 34 | fs.writeFileSync(path.join(srcDir, 'package.json'), pkgBytes) | |
| 35 | ||
| 36 | const files = [ | |
| 37 | { name: '/package.json', file: path.join(srcDir, 'package.json'), buf: pkgBytes }, | |
| 38 | { name: '/by-arch/' + HOST + '/app/' + APP, file: path.join(srcDir, APP), buf: binBytes } | |
| 39 | ] | |
| 40 | ||
| 41 | const content = new ContentStore() | |
| 42 | for (const f of files) { | |
| 43 | f.chunks = chunk(f.buf) | |
| 44 | for (const c of f.chunks) content.add(c.hash, f.file, c.offset, c.length) | |
| 45 | } | |
| 46 | ||
| 47 | const Storage = build(content) | |
| 48 | const store = new Corestore(new Storage(dir), { primaryKey: Buffer.alloc(32, 7), unsafe: true }) | |
| 49 | const drive = new Hyperdrive(store) | |
| 50 | await drive.ready() | |
| 51 | ||
| 52 | function write (ws, buf) { | |
| 53 | return new Promise((resolve) => { | |
| 54 | if (ws.write(buf)) return resolve() | |
| 55 | ws.once('drain', resolve) | |
| 56 | }) | |
| 57 | } | |
| 58 | ||
| 59 | for (const f of files) { | |
| 60 | const ws = drive.createWriteStream(f.name, { dedup: true }) | |
| 61 | for (const c of f.chunks) await write(ws, c.data) | |
| 62 | await new Promise((resolve, reject) => { | |
| 63 | ws.once('error', reject) | |
| 64 | ws.once('close', resolve) | |
| 65 | ws.end() | |
| 66 | }) | |
| 67 | } | |
| 68 | ||
| 69 | await drive.getBlobs() | |
| 70 | const key = drive.key | |
| 71 | const info = { | |
| 72 | key: key.toString('hex'), | |
| 73 | link: 'pear://' + idEnc.normalize(key), | |
| 74 | blobsLength: drive.blobs.core.length, | |
| 75 | binSha: crypto.hash(binBytes).toString('hex'), | |
| 76 | binBytes: binBytes.length, | |
| 77 | host: HOST | |
| 78 | } | |
| 79 | ||
| 80 | await drive.close() | |
| 81 | await store.close() | |
| 82 | content.close() | |
| 83 | ||
| 84 | fs.writeFileSync(ROOT + '/drive.json', JSON.stringify(info, null, 2)) | |
| 85 | console.log(JSON.stringify(info, null, 2)) |
| … | … | @@ -0,0 +1,65 @@ |
| 1 | // T5 seed — serve the VIRTUAL drive on the DHT. Every byte a peer receives is | |
| 2 | // materialised on demand from the source files; the store holds no blob bytes. | |
| 3 | import { createRequire } from 'module' | |
| 4 | const require = createRequire(import.meta.url) | |
| 5 | ||
| 6 | const fs = require('fs') | |
| 7 | const path = require('path') | |
| 8 | const process = require('process') | |
| 9 | const Corestore = require('corestore') | |
| 10 | const Hyperdrive = require('hyperdrive') | |
| 11 | const Hyperswarm = require('hyperswarm') | |
| 12 | const { ContentStore, build, stats } = require('../lib/storage.cjs') | |
| 13 | const { chunk } = await import('../../hyper-overlay/lib/chunker.js') | |
| 14 | ||
| 15 | const ROOT = process.argv[2] | |
| 16 | const dir = ROOT + '/virtual' | |
| 17 | const srcDir = ROOT + '/src' | |
| 18 | ||
| 19 | // Rebuild the content index from the source files, exactly as the overlay would. | |
| 20 | const content = new ContentStore() | |
| 21 | for (const name of fs.readdirSync(srcDir)) { | |
| 22 | const file = path.join(srcDir, name) | |
| 23 | const buf = fs.readFileSync(file) | |
| 24 | for (const c of chunk(buf)) content.add(c.hash, file, c.offset, c.length) | |
| 25 | } | |
| 26 | ||
| 27 | const Storage = build(content) | |
| 28 | const store = new Corestore(new Storage(dir), { primaryKey: Buffer.alloc(32, 7), unsafe: true }) | |
| 29 | const drive = new Hyperdrive(store) | |
| 30 | await drive.ready() | |
| 31 | await drive.getBlobs() | |
| 32 | ||
| 33 | const swarm = new Hyperswarm() | |
| 34 | swarm.on('connection', (conn) => store.replicate(conn)) | |
| 35 | const discovery = swarm.join(drive.discoveryKey, { server: true, client: false }) | |
| 36 | await discovery.flushed() | |
| 37 | ||
| 38 | console.log( | |
| 39 | JSON.stringify({ seeding: drive.key.toString('hex'), storeBytes: dirSize(dir) }) | |
| 40 | ) | |
| 41 | ||
| 42 | function dirSize (p) { | |
| 43 | let total = 0 | |
| 44 | for (const e of fs.readdirSync(p, { withFileTypes: true })) { | |
| 45 | const full = path.join(p, e.name) | |
| 46 | total += e.isDirectory() ? dirSize(full) : fs.statSync(full).size | |
| 47 | } | |
| 48 | return total | |
| 49 | } | |
| 50 | ||
| 51 | process.on('SIGTERM', shutdown) | |
| 52 | process.on('SIGINT', shutdown) | |
| 53 | ||
| 54 | async function shutdown () { | |
| 55 | console.log(JSON.stringify({ served: stats })) | |
| 56 | await swarm.destroy() | |
| 57 | await drive.close() | |
| 58 | await store.close() | |
| 59 | content.close() | |
| 60 | process.exit(0) | |
| 61 | } | |
| 62 | ||
| 63 | setInterval(() => { | |
| 64 | console.log(JSON.stringify({ peers: swarm.connections.size, bytesServed: stats.bytesServed })) | |
| 65 | }, 10000) |
| … | … | @@ -0,0 +1,75 @@ |
| 1 | // T2 — pass-through wrapper around hypercore-storage. | |
| 2 | // | |
| 3 | // Core takes its per-core storage from exactly two calls (hypercore/lib/core.js): | |
| 4 | // :162 await this.db.resumeCore(discoveryKey) | |
| 5 | // :232 await this.db.createCore({...}) | |
| 6 | // so subclassing CorestoreStorage and wrapping those two return values puts us | |
| 7 | // under every block read/write without touching anything above. | |
| 8 | // | |
| 9 | // Objects that construct more of themselves (snapshot/atomize) must be re-wrapped, | |
| 10 | // and read()/write() must return wrapped RX/TX — that is where T3 will redirect | |
| 11 | // getBlock/putBlock. Here everything is pure delegation. | |
| 12 | ||
| 13 | const CorestoreStorage = require('hypercore-storage') | |
| 14 | ||
| 15 | const stats = { getBlock: 0, putBlock: 0, read: 0, write: 0, wrapped: 0 } | |
| 16 | ||
| 17 | function delegate (target, overrides) { | |
| 18 | return new Proxy(target, { | |
| 19 | get (t, prop) { | |
| 20 | if (prop === '__unwrapped') return t | |
| 21 | if (overrides && prop in overrides) return overrides[prop] | |
| 22 | // NB: no receiver — getters must run with `this` bound to the real object | |
| 23 | const v = Reflect.get(t, prop) | |
| 24 | return typeof v === 'function' ? v.bind(t) : v | |
| 25 | }, | |
| 26 | set (t, prop, value) { | |
| 27 | t[prop] = value | |
| 28 | return true | |
| 29 | } | |
| 30 | }) | |
| 31 | } | |
| 32 | ||
| 33 | function wrapRX (rx) { | |
| 34 | stats.read++ | |
| 35 | return delegate(rx, { | |
| 36 | getBlock (index) { | |
| 37 | stats.getBlock++ | |
| 38 | return rx.getBlock(index) | |
| 39 | } | |
| 40 | }) | |
| 41 | } | |
| 42 | ||
| 43 | function wrapTX (tx) { | |
| 44 | stats.write++ | |
| 45 | return delegate(tx, { | |
| 46 | putBlock (index, data) { | |
| 47 | stats.putBlock++ | |
| 48 | return tx.putBlock(index, data) | |
| 49 | } | |
| 50 | }) | |
| 51 | } | |
| 52 | ||
| 53 | function wrapCoreStorage (inner) { | |
| 54 | if (!inner) return inner | |
| 55 | stats.wrapped++ | |
| 56 | const overrides = { | |
| 57 | read: (...args) => wrapRX(inner.read(...args)), | |
| 58 | write: (...args) => wrapTX(inner.write(...args)), | |
| 59 | snapshot: (...args) => wrapCoreStorage(inner.snapshot(...args)), | |
| 60 | atomize: (...args) => wrapCoreStorage(inner.atomize(...args)) | |
| 61 | } | |
| 62 | return delegate(inner, overrides) | |
| 63 | } | |
| 64 | ||
| 65 | class WrappedCorestoreStorage extends CorestoreStorage { | |
| 66 | async resumeCore (discoveryKey) { | |
| 67 | return wrapCoreStorage(await super.resumeCore(discoveryKey)) | |
| 68 | } | |
| 69 | ||
| 70 | async createCore (data) { | |
| 71 | return wrapCoreStorage(await super.createCore(data)) | |
| 72 | } | |
| 73 | } | |
| 74 | ||
| 75 | module.exports = { WrappedCorestoreStorage, stats } |
| … | … | @@ -0,0 +1,147 @@ |
| 1 | // Step 1 — atomic pointers. | |
| 2 | // | |
| 3 | // T3 kept index->hash in a JSON side-file, so a crash between the tree write and | |
| 4 | // the flush orphaned the tree. Fix: the pointer IS the block value. Every stored | |
| 5 | // record carries a 1-byte tag: | |
| 6 | // | |
| 7 | // 0x00 | <original bytes> literal (bee blocks, anything we don't hold) | |
| 8 | // 0x01 | <32-byte hash> pointer (bytes live in the content store) | |
| 9 | // | |
| 10 | // Now the pointer lands in the same CoreTX as the tree node — same atom, same | |
| 11 | // flush — and deleteBlock/deleteBlockRange/truncate free it with no extra work. | |
| 12 | // Cost: 1 byte per literal block, 33 bytes per virtualised block. | |
| 13 | ||
| 14 | const fs = require('fs') | |
| 15 | const crypto = require('hypercore-crypto') | |
| 16 | const CorestoreStorage = require('hypercore-storage') | |
| 17 | ||
| 18 | const TAG_LITERAL = 0x00 | |
| 19 | const TAG_POINTER = 0x01 | |
| 20 | ||
| 21 | const stats = { | |
| 22 | putRedirected: 0, | |
| 23 | putStored: 0, | |
| 24 | getRedirected: 0, | |
| 25 | getStored: 0, | |
| 26 | bytesServed: 0, | |
| 27 | hashFailures: 0, | |
| 28 | missingBytes: 0 | |
| 29 | } | |
| 30 | ||
| 31 | class ContentStore { | |
| 32 | constructor () { | |
| 33 | this.locations = new Map() | |
| 34 | this._fds = new Map() | |
| 35 | } | |
| 36 | ||
| 37 | add (hash, file, offset, length) { | |
| 38 | if (!this.locations.has(hash)) this.locations.set(hash, { file, offset, length }) | |
| 39 | } | |
| 40 | ||
| 41 | has (hash) { | |
| 42 | return this.locations.has(hash) | |
| 43 | } | |
| 44 | ||
| 45 | read (hash, { verify = true } = {}) { | |
| 46 | const loc = this.locations.get(hash) | |
| 47 | if (!loc) { | |
| 48 | stats.missingBytes++ | |
| 49 | return null | |
| 50 | } | |
| 51 | let fd = this._fds.get(loc.file) | |
| 52 | if (fd === undefined) { | |
| 53 | fd = fs.openSync(loc.file, 'r') | |
| 54 | this._fds.set(loc.file, fd) | |
| 55 | } | |
| 56 | const buf = Buffer.allocUnsafe(loc.length) | |
| 57 | fs.readSync(fd, buf, 0, loc.length, loc.offset) | |
| 58 | if (verify && crypto.data(buf).toString('hex') !== hash) { | |
| 59 | stats.hashFailures++ | |
| 60 | return null | |
| 61 | } | |
| 62 | stats.bytesServed += buf.length | |
| 63 | return buf | |
| 64 | } | |
| 65 | ||
| 66 | close () { | |
| 67 | for (const fd of this._fds.values()) fs.closeSync(fd) | |
| 68 | this._fds.clear() | |
| 69 | } | |
| 70 | } | |
| 71 | ||
| 72 | function delegate (target, overrides) { | |
| 73 | return new Proxy(target, { | |
| 74 | get (t, prop) { | |
| 75 | if (prop === '__unwrapped') return t | |
| 76 | if (overrides && prop in overrides) return overrides[prop] | |
| 77 | const v = Reflect.get(t, prop) | |
| 78 | return typeof v === 'function' ? v.bind(t) : v | |
| 79 | }, | |
| 80 | set (t, prop, value) { | |
| 81 | t[prop] = value | |
| 82 | return true | |
| 83 | } | |
| 84 | }) | |
| 85 | } | |
| 86 | ||
| 87 | function build (content) { | |
| 88 | function decode (record) { | |
| 89 | if (record === null) return null | |
| 90 | if (record[0] === TAG_POINTER) { | |
| 91 | stats.getRedirected++ | |
| 92 | return content.read(record.subarray(1).toString('hex')) | |
| 93 | } | |
| 94 | stats.getStored++ | |
| 95 | return record.subarray(1) | |
| 96 | } | |
| 97 | ||
| 98 | function wrapRX (rx) { | |
| 99 | return delegate(rx, { | |
| 100 | getBlock (index) { | |
| 101 | return rx.getBlock(index).then(decode) | |
| 102 | } | |
| 103 | }) | |
| 104 | } | |
| 105 | ||
| 106 | function wrapTX (tx) { | |
| 107 | return delegate(tx, { | |
| 108 | putBlock (index, data) { | |
| 109 | const digest = crypto.data(data) | |
| 110 | if (content.has(digest.toString('hex'))) { | |
| 111 | stats.putRedirected++ | |
| 112 | const rec = Buffer.allocUnsafe(33) | |
| 113 | rec[0] = TAG_POINTER | |
| 114 | digest.copy(rec, 1) | |
| 115 | return tx.putBlock(index, rec) | |
| 116 | } | |
| 117 | stats.putStored++ | |
| 118 | const rec = Buffer.allocUnsafe(data.byteLength + 1) | |
| 119 | rec[0] = TAG_LITERAL | |
| 120 | data.copy(rec, 1) | |
| 121 | return tx.putBlock(index, rec) | |
| 122 | } | |
| 123 | }) | |
| 124 | } | |
| 125 | ||
| 126 | function wrapCoreStorage (inner) { | |
| 127 | if (!inner) return inner | |
| 128 | return delegate(inner, { | |
| 129 | read: (...args) => wrapRX(inner.read(...args)), | |
| 130 | write: (...args) => wrapTX(inner.write(...args)), | |
| 131 | snapshot: (...args) => wrapCoreStorage(inner.snapshot(...args)), | |
| 132 | atomize: (...args) => wrapCoreStorage(inner.atomize(...args)) | |
| 133 | }) | |
| 134 | } | |
| 135 | ||
| 136 | return class RedirectingCorestoreStorage extends CorestoreStorage { | |
| 137 | async resumeCore (discoveryKey) { | |
| 138 | return wrapCoreStorage(await super.resumeCore(discoveryKey)) | |
| 139 | } | |
| 140 | ||
| 141 | async createCore (data) { | |
| 142 | return wrapCoreStorage(await super.createCore(data)) | |
| 143 | } | |
| 144 | } | |
| 145 | } | |
| 146 | ||
| 147 | module.exports = { ContentStore, build, stats, TAG_LITERAL, TAG_POINTER } |
| … | … | @@ -0,0 +1,1103 @@ |
| 1 | { | |
| 2 | "name": "hyper-vdrive", | |
| 3 | "version": "0.0.1", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "hyper-vdrive", | |
| 9 | "version": "0.0.1", | |
| 10 | "license": "AGPL-3.0-or-later", | |
| 11 | "dependencies": { | |
| 12 | "corestore": "^7.12.0", | |
| 13 | "hypercore": "^11.35.2", | |
| 14 | "hypercore-crypto": "^3.7.0", | |
| 15 | "hypercore-id-encoding": "^1.3.0", | |
| 16 | "hypercore-storage": "^3.2.1", | |
| 17 | "hyperdrive": "^13.3.3", | |
| 18 | "hyperswarm": "^4.17.0" | |
| 19 | } | |
| 20 | }, | |
| 21 | "node_modules/@hyperswarm/secret-stream": { | |
| 22 | "version": "6.9.1", | |
| 23 | "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz", | |
| 24 | "integrity": "sha512-xb0S5y3YJwBakD77JOGBHlBxdp63mHClZoXBYoLv+9wH8e054ESKlmQptWqjJK5dv5VMUIVYOJB4MaOpB0JdGw==", | |
| 25 | "license": "Apache-2.0", | |
| 26 | "dependencies": { | |
| 27 | "b4a": "^1.1.0", | |
| 28 | "hypercore-crypto": "^3.3.1", | |
| 29 | "noise-curve-ed": "^2.0.1", | |
| 30 | "noise-handshake": "^4.0.0", | |
| 31 | "sodium-secretstream": "^1.1.0", | |
| 32 | "sodium-universal": "^5.0.0", | |
| 33 | "streamx": "^2.14.0", | |
| 34 | "timeout-refresh": "^2.0.0", | |
| 35 | "unslab": "^1.3.0" | |
| 36 | } | |
| 37 | }, | |
| 38 | "node_modules/adaptive-timeout": { | |
| 39 | "version": "1.0.1", | |
| 40 | "resolved": "https://registry.npmjs.org/adaptive-timeout/-/adaptive-timeout-1.0.1.tgz", | |
| 41 | "integrity": "sha512-+seGiBtHqbnyZ0Quq/ZGxxwP66153WBABawa07/QeyuPFzbduPBjiGQAyCiriiLDzt3dkbMBskSlfqtRwblK8Q==", | |
| 42 | "license": "Apache-2.0", | |
| 43 | "dependencies": { | |
| 44 | "xache": "^1.2.1" | |
| 45 | } | |
| 46 | }, | |
| 47 | "node_modules/b4a": { | |
| 48 | "version": "1.8.1", | |
| 49 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", | |
| 50 | "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", | |
| 51 | "license": "Apache-2.0", | |
| 52 | "peerDependencies": { | |
| 53 | "react-native-b4a": "*" | |
| 54 | }, | |
| 55 | "peerDependenciesMeta": { | |
| 56 | "react-native-b4a": { | |
| 57 | "optional": true | |
| 58 | } | |
| 59 | } | |
| 60 | }, | |
| 61 | "node_modules/bare-addon-resolve": { | |
| 62 | "version": "1.10.1", | |
| 63 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.1.tgz", | |
| 64 | "integrity": "sha512-F/SD2du8keuYSb4xipnGz5j2E6yhNdHA8ZVxtHae6h2uOrpBIjjbhXvjzKZbr5XUOzqBzh/i8GVFycj2DlFQIA==", | |
| 65 | "license": "Apache-2.0", | |
| 66 | "dependencies": { | |
| 67 | "bare-module-resolve": "^1.10.0", | |
| 68 | "bare-semver": "^1.0.0" | |
| 69 | }, | |
| 70 | "peerDependencies": { | |
| 71 | "bare-url": "*" | |
| 72 | }, | |
| 73 | "peerDependenciesMeta": { | |
| 74 | "bare-url": { | |
| 75 | "optional": true | |
| 76 | } | |
| 77 | } | |
| 78 | }, | |
| 79 | "node_modules/bare-ansi-escapes": { | |
| 80 | "version": "2.2.3", | |
| 81 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 82 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 83 | "license": "Apache-2.0", | |
| 84 | "dependencies": { | |
| 85 | "bare-stream": "^2.6.5" | |
| 86 | }, | |
| 87 | "peerDependencies": { | |
| 88 | "bare-buffer": "*" | |
| 89 | }, | |
| 90 | "peerDependenciesMeta": { | |
| 91 | "bare-buffer": { | |
| 92 | "optional": true | |
| 93 | } | |
| 94 | } | |
| 95 | }, | |
| 96 | "node_modules/bare-assert": { | |
| 97 | "version": "1.2.0", | |
| 98 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 99 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 100 | "license": "Apache-2.0", | |
| 101 | "dependencies": { | |
| 102 | "bare-inspect": "^3.1.2" | |
| 103 | } | |
| 104 | }, | |
| 105 | "node_modules/bare-events": { | |
| 106 | "version": "2.9.1", | |
| 107 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", | |
| 108 | "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", | |
| 109 | "license": "Apache-2.0", | |
| 110 | "peerDependencies": { | |
| 111 | "bare-abort-controller": "*" | |
| 112 | }, | |
| 113 | "peerDependenciesMeta": { | |
| 114 | "bare-abort-controller": { | |
| 115 | "optional": true | |
| 116 | } | |
| 117 | } | |
| 118 | }, | |
| 119 | "node_modules/bare-fs": { | |
| 120 | "version": "4.8.1", | |
| 121 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.1.tgz", | |
| 122 | "integrity": "sha512-N1nnXdHZAOSstz0XiHikGS4HGMH4CnSwhqWdGQQMqqdvp4Jybm9sE3R1WVnpWVd4SFkc8ryPDBLViNLwiEqECg==", | |
| 123 | "license": "Apache-2.0", | |
| 124 | "dependencies": { | |
| 125 | "bare-events": "^2.5.4", | |
| 126 | "bare-path": "^3.0.0", | |
| 127 | "bare-stream": "^2.6.4", | |
| 128 | "bare-url": "^2.2.2", | |
| 129 | "fast-fifo": "^1.3.2" | |
| 130 | }, | |
| 131 | "engines": { | |
| 132 | "bare": ">=1.28.0" | |
| 133 | }, | |
| 134 | "peerDependencies": { | |
| 135 | "bare-buffer": "*" | |
| 136 | }, | |
| 137 | "peerDependenciesMeta": { | |
| 138 | "bare-buffer": { | |
| 139 | "optional": true | |
| 140 | } | |
| 141 | } | |
| 142 | }, | |
| 143 | "node_modules/bare-inspect": { | |
| 144 | "version": "3.1.5", | |
| 145 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.5.tgz", | |
| 146 | "integrity": "sha512-6xmjxF3vPxNYNb5I6nqPgaRQlQhO1qjkRNe2lKUSTel5IZZQtHJKm59OVYFgCly4urvVqJJvRrJecZGy6rUWdQ==", | |
| 147 | "license": "Apache-2.0", | |
| 148 | "dependencies": { | |
| 149 | "bare-ansi-escapes": "^2.1.0", | |
| 150 | "bare-type": "^1.0.0" | |
| 151 | }, | |
| 152 | "engines": { | |
| 153 | "bare": ">=1.18.0" | |
| 154 | } | |
| 155 | }, | |
| 156 | "node_modules/bare-module-resolve": { | |
| 157 | "version": "1.12.4", | |
| 158 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.4.tgz", | |
| 159 | "integrity": "sha512-xcfgg2u7HqgJiBmah71O9vvdFAgHCvkqC/WSC2O7Bbgosoc1eC/BWe/6IDJ4OsfKlkxuvC/TDWXC+oH5yeW8mA==", | |
| 160 | "license": "Apache-2.0", | |
| 161 | "dependencies": { | |
| 162 | "bare-semver": "^1.0.0" | |
| 163 | }, | |
| 164 | "peerDependencies": { | |
| 165 | "bare-url": "*" | |
| 166 | }, | |
| 167 | "peerDependenciesMeta": { | |
| 168 | "bare-url": { | |
| 169 | "optional": true | |
| 170 | } | |
| 171 | } | |
| 172 | }, | |
| 173 | "node_modules/bare-path": { | |
| 174 | "version": "3.1.1", | |
| 175 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", | |
| 176 | "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", | |
| 177 | "license": "Apache-2.0" | |
| 178 | }, | |
| 179 | "node_modules/bare-semver": { | |
| 180 | "version": "1.1.0", | |
| 181 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.1.0.tgz", | |
| 182 | "integrity": "sha512-1Hw5qJ7hXdVt3uPUqjeFTuxyvBUJauvz5A1I2jk8gzjZMHp04n//6nV9MDbG9CMw78JHY2lGV0w6s//LrASm2w==", | |
| 183 | "license": "Apache-2.0" | |
| 184 | }, | |
| 185 | "node_modules/bare-stream": { | |
| 186 | "version": "2.13.3", | |
| 187 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", | |
| 188 | "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", | |
| 189 | "license": "Apache-2.0", | |
| 190 | "dependencies": { | |
| 191 | "b4a": "^1.8.1", | |
| 192 | "streamx": "^2.25.0", | |
| 193 | "teex": "^1.0.1" | |
| 194 | }, | |
| 195 | "peerDependencies": { | |
| 196 | "bare-abort-controller": "*", | |
| 197 | "bare-buffer": "*", | |
| 198 | "bare-events": "*" | |
| 199 | }, | |
| 200 | "peerDependenciesMeta": { | |
| 201 | "bare-abort-controller": { | |
| 202 | "optional": true | |
| 203 | }, | |
| 204 | "bare-buffer": { | |
| 205 | "optional": true | |
| 206 | }, | |
| 207 | "bare-events": { | |
| 208 | "optional": true | |
| 209 | } | |
| 210 | } | |
| 211 | }, | |
| 212 | "node_modules/bare-type": { | |
| 213 | "version": "1.1.0", | |
| 214 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 215 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 216 | "license": "Apache-2.0", | |
| 217 | "engines": { | |
| 218 | "bare": ">=1.2.0" | |
| 219 | } | |
| 220 | }, | |
| 221 | "node_modules/bare-url": { | |
| 222 | "version": "2.5.2", | |
| 223 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.2.tgz", | |
| 224 | "integrity": "sha512-L13PCJzKG8RGvx8V1/DdMi12ERhC3tprr7/8a94BxpmnRsFqxh5XZNdhtMxu5HPkRshYOOWRGY8lDP7ZhpG9Cg==", | |
| 225 | "license": "Apache-2.0", | |
| 226 | "dependencies": { | |
| 227 | "bare-path": "^3.0.0" | |
| 228 | } | |
| 229 | }, | |
| 230 | "node_modules/big-sparse-array": { | |
| 231 | "version": "1.0.3", | |
| 232 | "resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz", | |
| 233 | "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==", | |
| 234 | "license": "MIT" | |
| 235 | }, | |
| 236 | "node_modules/binary-stream-equals": { | |
| 237 | "version": "1.0.0", | |
| 238 | "resolved": "https://registry.npmjs.org/binary-stream-equals/-/binary-stream-equals-1.0.0.tgz", | |
| 239 | "integrity": "sha512-xiUT5LGfD8JiLhbXiG+ByOnbgb9f2ssRLfZDQMl3nZdf89EotQZGZuMkDN8J3n46emabE7RnJ1q0r7Hv3INExw==", | |
| 240 | "license": "MIT", | |
| 241 | "dependencies": { | |
| 242 | "b4a": "^1.3.1" | |
| 243 | } | |
| 244 | }, | |
| 245 | "node_modules/bits-to-bytes": { | |
| 246 | "version": "1.3.0", | |
| 247 | "resolved": "https://registry.npmjs.org/bits-to-bytes/-/bits-to-bytes-1.3.0.tgz", | |
| 248 | "integrity": "sha512-OJoHTpFXS9bXHBCekGTByf3MqM8CGblBDIduKQeeVVeiU9dDWywSSirXIBYGgg3d1zbVuvnMa1vD4r6PA0kOKg==", | |
| 249 | "license": "ISC", | |
| 250 | "dependencies": { | |
| 251 | "b4a": "^1.5.0" | |
| 252 | } | |
| 253 | }, | |
| 254 | "node_modules/blind-relay": { | |
| 255 | "version": "1.6.1", | |
| 256 | "resolved": "https://registry.npmjs.org/blind-relay/-/blind-relay-1.6.1.tgz", | |
| 257 | "integrity": "sha512-38YmKCl/m9NcTkwueBbcGIt9Zx3IT/Q9Nsluu6C5Gur6PqfE0zWPhPwCzia1hri/g0ICYxrqkgLtEaoWD5WeSQ==", | |
| 258 | "license": "Apache-2.0", | |
| 259 | "dependencies": { | |
| 260 | "b4a": "^1.6.4", | |
| 261 | "bare-events": "^2.2.0", | |
| 262 | "bits-to-bytes": "^1.3.0", | |
| 263 | "compact-encoding": "^3.0.0", | |
| 264 | "compact-encoding-bitfield": "^1.0.0", | |
| 265 | "protomux": "^3.5.1", | |
| 266 | "sodium-universal": "^5.0.0", | |
| 267 | "streamx": "^2.15.1" | |
| 268 | } | |
| 269 | }, | |
| 270 | "node_modules/bogon": { | |
| 271 | "version": "1.3.0", | |
| 272 | "resolved": "https://registry.npmjs.org/bogon/-/bogon-1.3.0.tgz", | |
| 273 | "integrity": "sha512-04tu0G/v0f2HPuQlSfhO635WwHDBCaITJ490rhxH9ttUlgPqOirZVKV02YB6NvPf5VkmbqJCm3bnZwrPk/eDSg==", | |
| 274 | "license": "MIT", | |
| 275 | "dependencies": { | |
| 276 | "compact-encoding": "^3.0.0", | |
| 277 | "compact-encoding-net": "^1.2.0" | |
| 278 | } | |
| 279 | }, | |
| 280 | "node_modules/codecs": { | |
| 281 | "version": "3.1.0", | |
| 282 | "resolved": "https://registry.npmjs.org/codecs/-/codecs-3.1.0.tgz", | |
| 283 | "integrity": "sha512-Dqx8NwvBvnMeuPQdVKy/XEF71igjR5apxBvCGeV0pP1tXadOiaLvDTXt7xh+/5wI1ASB195mXQGJbw3Ml4YDWQ==", | |
| 284 | "license": "MIT", | |
| 285 | "dependencies": { | |
| 286 | "b4a": "^1.6.3" | |
| 287 | } | |
| 288 | }, | |
| 289 | "node_modules/compact-encoding": { | |
| 290 | "version": "3.3.2", | |
| 291 | "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.3.2.tgz", | |
| 292 | "integrity": "sha512-bgZ84oVSzjQmVHXBYWON+wQUkdVflrr8fM5+0X6UwQHZyzolsGIkdsjNK3vLLn22dhdASGFz5mS6Lf7rfrPdlg==", | |
| 293 | "license": "Apache-2.0", | |
| 294 | "dependencies": { | |
| 295 | "b4a": "^1.3.0" | |
| 296 | } | |
| 297 | }, | |
| 298 | "node_modules/compact-encoding-bitfield": { | |
| 299 | "version": "1.1.0", | |
| 300 | "resolved": "https://registry.npmjs.org/compact-encoding-bitfield/-/compact-encoding-bitfield-1.1.0.tgz", | |
| 301 | "integrity": "sha512-F6wliSKHi50BsBStmnsRJAzJgYSIYzdfSe3M0oHj4uQ1RcctJK/NQVD7wF51bj/DBMne2i5gUxrGUFkNZQns5w==", | |
| 302 | "license": "Apache-2.0", | |
| 303 | "dependencies": { | |
| 304 | "compact-encoding": "^3.0.0" | |
| 305 | } | |
| 306 | }, | |
| 307 | "node_modules/compact-encoding-net": { | |
| 308 | "version": "1.3.0", | |
| 309 | "resolved": "https://registry.npmjs.org/compact-encoding-net/-/compact-encoding-net-1.3.0.tgz", | |
| 310 | "integrity": "sha512-HIYjL3aMiSENApR691aMLngXt6rkTHb9HUkEukcx3YwIZpoMUVXHXyjBzoo9kVwC7KakooBA1RRWb46Cu6qtAQ==", | |
| 311 | "license": "Apache-2.0", | |
| 312 | "dependencies": { | |
| 313 | "compact-encoding": "^3.0.0" | |
| 314 | } | |
| 315 | }, | |
| 316 | "node_modules/corestore": { | |
| 317 | "version": "7.12.0", | |
| 318 | "resolved": "https://registry.npmjs.org/corestore/-/corestore-7.12.0.tgz", | |
| 319 | "integrity": "sha512-yRWQ8VkjlAl7ITRo/xT8EK9NdiJ7xrsCCkRShD2TlSaoGa3B7bRVNPr1nWGE5NjiIi4HiLBCxtiI4gzmjAjJ0Q==", | |
| 320 | "license": "MIT", | |
| 321 | "dependencies": { | |
| 322 | "b4a": "^1.6.7", | |
| 323 | "bare-events": "^2.8.3", | |
| 324 | "hypercore": "^11.32.0", | |
| 325 | "hypercore-crypto": "^3.4.2", | |
| 326 | "hypercore-errors": "^1.4.0", | |
| 327 | "hypercore-id-encoding": "^1.3.0", | |
| 328 | "ready-resource": "^1.1.1", | |
| 329 | "sodium-universal": "^5.0.1", | |
| 330 | "streamx": "^2.26.0", | |
| 331 | "which-runtime": "^1.2.1" | |
| 332 | } | |
| 333 | }, | |
| 334 | "node_modules/debounceify": { | |
| 335 | "version": "1.1.0", | |
| 336 | "resolved": "https://registry.npmjs.org/debounceify/-/debounceify-1.1.0.tgz", | |
| 337 | "integrity": "sha512-eKuHDVfJVg+u/0nPy8P+fhnLgbyuTgVxuCRrS/R7EpDSMMkBDgSes41MJtSAY1F1hcqfHz3Zy/qpqHHIp/EhdA==", | |
| 338 | "license": "MIT" | |
| 339 | }, | |
| 340 | "node_modules/device-file": { | |
| 341 | "version": "2.3.1", | |
| 342 | "resolved": "https://registry.npmjs.org/device-file/-/device-file-2.3.1.tgz", | |
| 343 | "integrity": "sha512-bmON44lwxJPle9N2OcH4tqM44pMGZKT8G6OkzXkz0urvqQ9LKkoQdTS6w0ztYfmLdUE27R4UUmx0+y2gEz4Jug==", | |
| 344 | "license": "Apache-2.0", | |
| 345 | "dependencies": { | |
| 346 | "b4a": "^1.6.7", | |
| 347 | "bare-fs": "^4.0.1", | |
| 348 | "bare-path": "^3.0.0", | |
| 349 | "fd-lock": "^2.1.0", | |
| 350 | "fs-native-extensions": "^1.4.0", | |
| 351 | "ready-resource": "^1.2.0" | |
| 352 | } | |
| 353 | }, | |
| 354 | "node_modules/dht-rpc": { | |
| 355 | "version": "6.27.0", | |
| 356 | "resolved": "https://registry.npmjs.org/dht-rpc/-/dht-rpc-6.27.0.tgz", | |
| 357 | "integrity": "sha512-NsfgRlFDQnkA2+H+hJXMPLmBOn/TSIIc0cRMV8q42M4xc1uAXWtpvIIQsONF5tYcYC6px0bslrUGideN1h4S4A==", | |
| 358 | "license": "MIT", | |
| 359 | "dependencies": { | |
| 360 | "adaptive-timeout": "^1.0.1", | |
| 361 | "b4a": "^1.6.1", | |
| 362 | "bare-events": "^2.2.0", | |
| 363 | "compact-encoding": "^3.0.0", | |
| 364 | "compact-encoding-net": "^1.2.0", | |
| 365 | "fast-fifo": "^1.1.0", | |
| 366 | "kademlia-routing-table": "^1.0.1", | |
| 367 | "nat-sampler": "^1.0.1", | |
| 368 | "sodium-universal": "^5.0.0", | |
| 369 | "streamx": "^2.13.2", | |
| 370 | "time-ordered-set": "^2.0.0", | |
| 371 | "udx-native": "^1.5.3" | |
| 372 | } | |
| 373 | }, | |
| 374 | "node_modules/events-universal": { | |
| 375 | "version": "1.0.1", | |
| 376 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 377 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 378 | "license": "Apache-2.0", | |
| 379 | "dependencies": { | |
| 380 | "bare-events": "^2.7.0" | |
| 381 | } | |
| 382 | }, | |
| 383 | "node_modules/fast-fifo": { | |
| 384 | "version": "1.3.2", | |
| 385 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 386 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 387 | "license": "MIT" | |
| 388 | }, | |
| 389 | "node_modules/fd-lock": { | |
| 390 | "version": "2.2.0", | |
| 391 | "resolved": "https://registry.npmjs.org/fd-lock/-/fd-lock-2.2.0.tgz", | |
| 392 | "integrity": "sha512-Il4jWBhjjgvUg+z8d0bC7ncXqB42caqKTUpnZ9jpcqiPmw8bkIixt7Kic1czbZPMxAQD/9kEqfZ5Dq77nSll0w==", | |
| 393 | "license": "Apache-2.0", | |
| 394 | "dependencies": { | |
| 395 | "bare-fs": "^4.5.0", | |
| 396 | "fs-native-extensions": "^1.4.4", | |
| 397 | "ready-resource": "^1.2.0", | |
| 398 | "resource-on-exit": "^1.0.0" | |
| 399 | } | |
| 400 | }, | |
| 401 | "node_modules/flat-tree": { | |
| 402 | "version": "1.13.0", | |
| 403 | "resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.13.0.tgz", | |
| 404 | "integrity": "sha512-fT3HIuCPwHhFgJ20QYzDHgUG0zMmFg5cHvFiFo5h+QMSJ28TihsEVY0f8HGliuO+pOzmvjMx1odToeaEWkTnyQ==", | |
| 405 | "license": "MIT" | |
| 406 | }, | |
| 407 | "node_modules/fs-native-extensions": { | |
| 408 | "version": "1.5.0", | |
| 409 | "resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.5.0.tgz", | |
| 410 | "integrity": "sha512-nuZLFm9mGCxvyi7Llww/J4OyifKCS21nEUTAmnlTZp3FObPOvA32aCedCmt4Z+8yk+caqfClNaSCfn/P7T7FLQ==", | |
| 411 | "license": "Apache-2.0", | |
| 412 | "dependencies": { | |
| 413 | "require-addon": "^1.1.0", | |
| 414 | "which-runtime": "^1.2.0" | |
| 415 | } | |
| 416 | }, | |
| 417 | "node_modules/generate-object-property": { | |
| 418 | "version": "2.0.0", | |
| 419 | "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-2.0.0.tgz", | |
| 420 | "integrity": "sha512-KwuURPyqn2Mz8DdV29pJwQu0Y7tcsbkULr82eeOcY/ZllFK6I9Wm8dsRByIu7CKWlFi9BdW1b3mcXMp/kQBQsw==", | |
| 421 | "license": "MIT", | |
| 422 | "dependencies": { | |
| 423 | "is-property": "^1.0.0" | |
| 424 | } | |
| 425 | }, | |
| 426 | "node_modules/generate-string": { | |
| 427 | "version": "1.0.1", | |
| 428 | "resolved": "https://registry.npmjs.org/generate-string/-/generate-string-1.0.1.tgz", | |
| 429 | "integrity": "sha512-IfTY0dKZM43ACyGvXkbG7De7WY7MxTS5VO6Juhe8oJKpCmrYYXoqp/cJMskkpi0k9H8wuXq0H+eI898/BCqvXg==", | |
| 430 | "license": "MIT" | |
| 431 | }, | |
| 432 | "node_modules/hyperbee": { | |
| 433 | "version": "2.27.3", | |
| 434 | "resolved": "https://registry.npmjs.org/hyperbee/-/hyperbee-2.27.3.tgz", | |
| 435 | "integrity": "sha512-PXURH2U4juUZyJRKHTrY5z1zX851pmI1Q0jfv5F/hCIErDt/ND8jOZuxc3hfOLM9f0W3qJEDTMlV5AJBkVPy8w==", | |
| 436 | "license": "MIT", | |
| 437 | "dependencies": { | |
| 438 | "b4a": "^1.6.0", | |
| 439 | "codecs": "^3.0.0", | |
| 440 | "debounceify": "^1.0.0", | |
| 441 | "hypercore-errors": "^1.0.0", | |
| 442 | "mutexify": "^1.4.0", | |
| 443 | "protocol-buffers-encodings": "^1.2.0", | |
| 444 | "rache": "^1.0.0", | |
| 445 | "ready-resource": "^1.0.0", | |
| 446 | "resolve-reject-promise": "^1.1.0", | |
| 447 | "safety-catch": "^1.0.2", | |
| 448 | "streamx": "^2.12.4", | |
| 449 | "unslab": "^1.2.0" | |
| 450 | } | |
| 451 | }, | |
| 452 | "node_modules/hyperblobs": { | |
| 453 | "version": "2.12.1", | |
| 454 | "resolved": "https://registry.npmjs.org/hyperblobs/-/hyperblobs-2.12.1.tgz", | |
| 455 | "integrity": "sha512-iX5sPkL/3eDphUkMxTG43xZGEER+cVLAFbuNIQpk/WVK8CTWnmaoCYP/+94RaFFh5U7HeGoDYidQbv6E7LeAvw==", | |
| 456 | "license": "Apache-2.0", | |
| 457 | "dependencies": { | |
| 458 | "b4a": "^1.6.1", | |
| 459 | "bare-events": "^2.5.0", | |
| 460 | "compact-encoding": "^3.0.0", | |
| 461 | "hypercore-crypto": "^3.6.1", | |
| 462 | "hypercore-errors": "^1.1.1", | |
| 463 | "mutexify": "^1.4.0", | |
| 464 | "speedometer": "^1.1.0", | |
| 465 | "streamx": "^2.13.2" | |
| 466 | } | |
| 467 | }, | |
| 468 | "node_modules/hypercore": { | |
| 469 | "version": "11.35.2", | |
| 470 | "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-11.35.2.tgz", | |
| 471 | "integrity": "sha512-Zk7v5z2M7dbDqPXZzPXsVKRxLTmyc2ViPhg4rOi1bTO2KtaiKaAhvX925+bGiQ5nsKxO3qdo7k9Ns8cwpMDTJw==", | |
| 472 | "license": "MIT", | |
| 473 | "dependencies": { | |
| 474 | "@hyperswarm/secret-stream": "^6.0.0", | |
| 475 | "b4a": "^1.1.0", | |
| 476 | "bare-events": "^2.2.0", | |
| 477 | "big-sparse-array": "^1.0.3", | |
| 478 | "compact-encoding": "^3.0.0", | |
| 479 | "fast-fifo": "^1.3.0", | |
| 480 | "flat-tree": "^1.9.0", | |
| 481 | "hypercore-crypto": "^3.2.1", | |
| 482 | "hypercore-errors": "^1.5.0", | |
| 483 | "hypercore-id-encoding": "^1.2.0", | |
| 484 | "hypercore-storage": "^3.2.0", | |
| 485 | "is-options": "^1.0.1", | |
| 486 | "nanoassert": "^2.0.0", | |
| 487 | "protomux": "^3.5.0", | |
| 488 | "quickbit-universal": "^2.2.0", | |
| 489 | "random-array-iterator": "^1.0.0", | |
| 490 | "safety-catch": "^1.0.1", | |
| 491 | "sodium-universal": "^5.0.1", | |
| 492 | "streamx": "^2.12.4", | |
| 493 | "unslab": "^1.3.0", | |
| 494 | "z32": "^1.0.0" | |
| 495 | } | |
| 496 | }, | |
| 497 | "node_modules/hypercore-crypto": { | |
| 498 | "version": "3.7.0", | |
| 499 | "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.7.0.tgz", | |
| 500 | "integrity": "sha512-SWxobptQf2V/+ZLCCDRTXqFzGfTPIkNIuDyLKXpEMfcpJ1/ec94N9aWgylHcWOHmRt14ng/KR7z0BugebWHK9Q==", | |
| 501 | "license": "MIT", | |
| 502 | "dependencies": { | |
| 503 | "b4a": "^1.6.6", | |
| 504 | "compact-encoding": "^3.0.0", | |
| 505 | "sodium-universal": "^5.0.0" | |
| 506 | } | |
| 507 | }, | |
| 508 | "node_modules/hypercore-errors": { | |
| 509 | "version": "1.5.0", | |
| 510 | "resolved": "https://registry.npmjs.org/hypercore-errors/-/hypercore-errors-1.5.0.tgz", | |
| 511 | "integrity": "sha512-5KQ/SuDxsvet+7qWA35Ay6zdD9WyAHQoyWHGcPUTbmJBd300gvNIJoi3oma7kp4TTCSzii6qYumNZe/s0j/saQ==", | |
| 512 | "license": "Apache-2.0", | |
| 513 | "dependencies": { | |
| 514 | "hypercore-id-encoding": "^1.3.0" | |
| 515 | } | |
| 516 | }, | |
| 517 | "node_modules/hypercore-id-encoding": { | |
| 518 | "version": "1.3.0", | |
| 519 | "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz", | |
| 520 | "integrity": "sha512-W6sHdGo5h7LXEsoWfKf/KfuROZmZRQDlGqJF2EPHW+noCK66Vvr0+zE6cL0vqQi18s0kQPeN7Sq3QyR0Ytc2VQ==", | |
| 521 | "license": "Apache-2.0", | |
| 522 | "dependencies": { | |
| 523 | "b4a": "^1.5.3", | |
| 524 | "z32": "^1.0.0" | |
| 525 | } | |
| 526 | }, | |
| 527 | "node_modules/hypercore-storage": { | |
| 528 | "version": "3.2.1", | |
| 529 | "resolved": "https://registry.npmjs.org/hypercore-storage/-/hypercore-storage-3.2.1.tgz", | |
| 530 | "integrity": "sha512-kxGLq5TloxPVxUJn99lOBv/2VVeSh9KIyTAbh4nlAzk6eKCeR8KlbCfWtQsorYayGyQY6l7iY05ueofff3j92w==", | |
| 531 | "license": "Apache-2.0", | |
| 532 | "dependencies": { | |
| 533 | "b4a": "^1.6.7", | |
| 534 | "bare-path": "^3.0.0", | |
| 535 | "compact-encoding": "^3.1.0", | |
| 536 | "device-file": "^2.1.2", | |
| 537 | "flat-tree": "^1.12.1", | |
| 538 | "hypercore-crypto": "^3.4.2", | |
| 539 | "hyperschema": "^1.21.0", | |
| 540 | "index-encoder": "^3.3.2", | |
| 541 | "resolve-reject-promise": "^1.0.0", | |
| 542 | "rocksdb-native": "^3.11.0", | |
| 543 | "scope-lock": "^1.2.4", | |
| 544 | "streamx": "^2.21.1", | |
| 545 | "xache": "^1.2.1" | |
| 546 | } | |
| 547 | }, | |
| 548 | "node_modules/hyperdht": { | |
| 549 | "version": "6.33.2", | |
| 550 | "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.33.2.tgz", | |
| 551 | "integrity": "sha512-u7sj6qtHygAUNm9qjikZtqJt4Lvt4sNIVbjnV8tniqAgQXzLto/9UtlZe1G7Cz8Hl2JKxdwsEep0t+ELV4MrXQ==", | |
| 552 | "license": "MIT", | |
| 553 | "dependencies": { | |
| 554 | "@hyperswarm/secret-stream": "^6.6.2", | |
| 555 | "b4a": "^1.3.1", | |
| 556 | "bare-events": "^2.2.0", | |
| 557 | "blind-relay": "^1.3.0", | |
| 558 | "bogon": "^1.0.0", | |
| 559 | "compact-encoding": "^3.0.0", | |
| 560 | "dht-rpc": "^6.15.1", | |
| 561 | "hypercore-crypto": "^3.3.0", | |
| 562 | "hypercore-id-encoding": "^1.2.0", | |
| 563 | "hyperdht-address": "^1.0.1", | |
| 564 | "noise-curve-ed": "^2.0.0", | |
| 565 | "noise-handshake": "^4.0.0", | |
| 566 | "record-cache": "^1.1.1", | |
| 567 | "safety-catch": "^1.0.1", | |
| 568 | "signal-promise": "^1.0.3", | |
| 569 | "sodium-universal": "^5.0.1", | |
| 570 | "streamx": "^2.16.1", | |
| 571 | "unslab": "^1.3.0", | |
| 572 | "xache": "^1.1.0" | |
| 573 | }, | |
| 574 | "bin": { | |
| 575 | "hyperdht": "bin.js" | |
| 576 | } | |
| 577 | }, | |
| 578 | "node_modules/hyperdht-address": { | |
| 579 | "version": "1.1.1", | |
| 580 | "resolved": "https://registry.npmjs.org/hyperdht-address/-/hyperdht-address-1.1.1.tgz", | |
| 581 | "integrity": "sha512-Mu/+7SW2cwvHxMXswa5mOrhrS5BJyntViAuB25k8d8wNtA8eAPs4LaIq6TwN1SS/KQY02h1r+gM8cQeN/k360w==", | |
| 582 | "license": "Apache-2.0", | |
| 583 | "dependencies": { | |
| 584 | "compact-encoding": "^3.0.0", | |
| 585 | "hyperschema": "^1.20.1" | |
| 586 | } | |
| 587 | }, | |
| 588 | "node_modules/hyperdrive": { | |
| 589 | "version": "13.3.3", | |
| 590 | "resolved": "https://registry.npmjs.org/hyperdrive/-/hyperdrive-13.3.3.tgz", | |
| 591 | "integrity": "sha512-nLqBbBUg1OPsjl8vn5T3wxhtLbzQz+JxUK1i1YOKTX4tdDFET8H4HxdA17knv5P4lC9v9eVC0mlPKdFlYxzDfw==", | |
| 592 | "license": "Apache-2.0", | |
| 593 | "dependencies": { | |
| 594 | "hyperbee": "^2.11.1", | |
| 595 | "hyperblobs": "^2.9.0", | |
| 596 | "hypercore": "^11.0.0", | |
| 597 | "hypercore-errors": "^1.0.0", | |
| 598 | "is-options": "^1.0.2", | |
| 599 | "mirror-drive": "^1.2.0", | |
| 600 | "ready-resource": "^1.0.0", | |
| 601 | "safety-catch": "^1.0.2", | |
| 602 | "speedometer": "^1.1.0", | |
| 603 | "streamx": "^2.12.4", | |
| 604 | "sub-encoder": "^2.1.1", | |
| 605 | "unix-path-resolve": "^1.0.2" | |
| 606 | } | |
| 607 | }, | |
| 608 | "node_modules/hyperschema": { | |
| 609 | "version": "1.22.0", | |
| 610 | "resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.22.0.tgz", | |
| 611 | "integrity": "sha512-h4D00e+tVyT4cDcb68p7ioaQxphBN5UOtiZVQv6RDPdtaKHLdngUWVoG/PL+RJvw/AS/OdZpiReGpLhx9pDHWg==", | |
| 612 | "license": "Apache-2.0", | |
| 613 | "dependencies": { | |
| 614 | "bare-fs": "^4.0.1", | |
| 615 | "compact-encoding": "^3.0.0", | |
| 616 | "generate-object-property": "^2.0.0", | |
| 617 | "generate-string": "^1.0.1" | |
| 618 | } | |
| 619 | }, | |
| 620 | "node_modules/hyperswarm": { | |
| 621 | "version": "4.17.0", | |
| 622 | "resolved": "https://registry.npmjs.org/hyperswarm/-/hyperswarm-4.17.0.tgz", | |
| 623 | "integrity": "sha512-oe86sK961Ueg7rvDN/veFwG8xH+Iv6vObPhGDkPJcDVxk/NduW41ZhAcVDnHzRbm7S0eLU7WaDUvehOYoKSpRQ==", | |
| 624 | "license": "MIT", | |
| 625 | "dependencies": { | |
| 626 | "b4a": "^1.3.1", | |
| 627 | "bare-events": "^2.2.0", | |
| 628 | "hyperdht": "^6.21.0", | |
| 629 | "safety-catch": "^1.0.2", | |
| 630 | "shuffled-priority-queue": "^2.1.0", | |
| 631 | "streamx": "^2.22.1", | |
| 632 | "unslab": "^1.3.0" | |
| 633 | } | |
| 634 | }, | |
| 635 | "node_modules/index-encoder": { | |
| 636 | "version": "3.5.0", | |
| 637 | "resolved": "https://registry.npmjs.org/index-encoder/-/index-encoder-3.5.0.tgz", | |
| 638 | "integrity": "sha512-idZ1cxtZz2dRV6rUiaP9Xo99UjXbSzjcMacoQmxUMu/A7fEQcNPngvwDJYeWelQUS5XFlY71/or70lKn6XnwbQ==", | |
| 639 | "license": "Apache-2.0", | |
| 640 | "dependencies": { | |
| 641 | "b4a": "^1.6.4" | |
| 642 | } | |
| 643 | }, | |
| 644 | "node_modules/is-options": { | |
| 645 | "version": "1.0.2", | |
| 646 | "resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz", | |
| 647 | "integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==", | |
| 648 | "license": "MIT", | |
| 649 | "dependencies": { | |
| 650 | "b4a": "^1.1.1" | |
| 651 | } | |
| 652 | }, | |
| 653 | "node_modules/is-property": { | |
| 654 | "version": "1.0.2", | |
| 655 | "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", | |
| 656 | "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", | |
| 657 | "license": "MIT" | |
| 658 | }, | |
| 659 | "node_modules/kademlia-routing-table": { | |
| 660 | "version": "1.0.6", | |
| 661 | "resolved": "https://registry.npmjs.org/kademlia-routing-table/-/kademlia-routing-table-1.0.6.tgz", | |
| 662 | "integrity": "sha512-Ve6jwIlUCYvUzBnXnzVRHDZCFgXURW9gmF3r7n05kZs/2rNbLHXwGdcq0qIaSwdmJCvtosgR4JensnVU65hzNQ==", | |
| 663 | "license": "MIT", | |
| 664 | "dependencies": { | |
| 665 | "bare-events": "^2.2.0" | |
| 666 | } | |
| 667 | }, | |
| 668 | "node_modules/mirror-drive": { | |
| 669 | "version": "1.14.2", | |
| 670 | "resolved": "https://registry.npmjs.org/mirror-drive/-/mirror-drive-1.14.2.tgz", | |
| 671 | "integrity": "sha512-1ZtS/TonGXWX0eDAGK90JapGyzOaz8IxX7mARZWPgUuZ9eEIvaoSY7bxv6/4FOW26wL8g/YcpqJpt8mMZ/QZCA==", | |
| 672 | "license": "Apache-2.0", | |
| 673 | "dependencies": { | |
| 674 | "bare-events": "^2.8.2", | |
| 675 | "binary-stream-equals": "^1.0.0", | |
| 676 | "rabin-stream": "^2.0.0", | |
| 677 | "same-data": "^1.0.0", | |
| 678 | "speedometer": "^1.1.0", | |
| 679 | "streamx": "^2.22.1", | |
| 680 | "unix-path-resolve": "^1.0.2" | |
| 681 | } | |
| 682 | }, | |
| 683 | "node_modules/mutexify": { | |
| 684 | "version": "1.4.0", | |
| 685 | "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz", | |
| 686 | "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==", | |
| 687 | "license": "MIT", | |
| 688 | "dependencies": { | |
| 689 | "queue-tick": "^1.0.0" | |
| 690 | } | |
| 691 | }, | |
| 692 | "node_modules/nanoassert": { | |
| 693 | "version": "2.0.0", | |
| 694 | "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", | |
| 695 | "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==", | |
| 696 | "license": "ISC" | |
| 697 | }, | |
| 698 | "node_modules/nat-sampler": { | |
| 699 | "version": "1.0.1", | |
| 700 | "resolved": "https://registry.npmjs.org/nat-sampler/-/nat-sampler-1.0.1.tgz", | |
| 701 | "integrity": "sha512-yQvyNN7xbqR8crTKk3U8gRgpcV1Az+vfCEijiHu9oHHsnIl8n3x+yXNHl42M6L3czGynAVoOT9TqBfS87gDdcw==", | |
| 702 | "license": "MIT" | |
| 703 | }, | |
| 704 | "node_modules/noise-curve-ed": { | |
| 705 | "version": "2.1.0", | |
| 706 | "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz", | |
| 707 | "integrity": "sha512-zAzJx+VwZM3w6EA1hTmDhJfvAnCeBQn/1FAeZ0LtGxCcCtlAK/uJXQVF/eDVUOaAZ286lHlx77WJ+qj9SmsRRg==", | |
| 708 | "license": "ISC", | |
| 709 | "dependencies": { | |
| 710 | "b4a": "^1.1.0", | |
| 711 | "nanoassert": "^2.0.0", | |
| 712 | "sodium-universal": "^5.0.0" | |
| 713 | } | |
| 714 | }, | |
| 715 | "node_modules/noise-handshake": { | |
| 716 | "version": "4.2.0", | |
| 717 | "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz", | |
| 718 | "integrity": "sha512-9O/VTNX/E2/AToyMTTDU0J/4WhaXMTdqc2DHs9vf+snoZ0cenSBq0dNYTVV1snYYEkmo6QeRrYMxtqtoYnY+LA==", | |
| 719 | "license": "Apache-2.0", | |
| 720 | "dependencies": { | |
| 721 | "b4a": "^1.1.0", | |
| 722 | "nanoassert": "^2.0.0", | |
| 723 | "sodium-universal": "^5.0.0" | |
| 724 | } | |
| 725 | }, | |
| 726 | "node_modules/protocol-buffers-encodings": { | |
| 727 | "version": "1.2.0", | |
| 728 | "resolved": "https://registry.npmjs.org/protocol-buffers-encodings/-/protocol-buffers-encodings-1.2.0.tgz", | |
| 729 | "integrity": "sha512-daeNPuKh1NlLD1uDfbLpD+xyUTc07nEtfHwmBZmt/vH0B7VOM+JOCOpDcx9ZRpqHjAiIkGqyTDi+wfGSl17R9w==", | |
| 730 | "license": "MIT", | |
| 731 | "dependencies": { | |
| 732 | "b4a": "^1.6.0", | |
| 733 | "signed-varint": "^2.0.1", | |
| 734 | "varint": "5.0.0" | |
| 735 | } | |
| 736 | }, | |
| 737 | "node_modules/protomux": { | |
| 738 | "version": "3.11.0", | |
| 739 | "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.11.0.tgz", | |
| 740 | "integrity": "sha512-Ze43XaNHbL6zQ/zJwVYvue7Aj8pDB1HNnISsrFhAur3291Y+Iu2fMhOpySD73J/32BoBIUYrhl07lJsBR/fUVA==", | |
| 741 | "license": "MIT", | |
| 742 | "dependencies": { | |
| 743 | "b4a": "^1.3.1", | |
| 744 | "compact-encoding": "^3.0.0", | |
| 745 | "queue-tick": "^1.0.0", | |
| 746 | "safety-catch": "^1.0.1", | |
| 747 | "unslab": "^1.3.0" | |
| 748 | } | |
| 749 | }, | |
| 750 | "node_modules/queue-tick": { | |
| 751 | "version": "1.0.1", | |
| 752 | "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", | |
| 753 | "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", | |
| 754 | "license": "MIT" | |
| 755 | }, | |
| 756 | "node_modules/quickbit-native": { | |
| 757 | "version": "2.4.8", | |
| 758 | "resolved": "https://registry.npmjs.org/quickbit-native/-/quickbit-native-2.4.8.tgz", | |
| 759 | "integrity": "sha512-FcCcqI+nIAWGknqhtrYT5TSD7t/N+Xd8ctM+2PrIIBuwOi5hx0SxAvuPtzLIEMfT/2h9+fhBakUe2uALOHX6yw==", | |
| 760 | "license": "Apache-2.0", | |
| 761 | "optional": true, | |
| 762 | "dependencies": { | |
| 763 | "require-addon": "^1.1.0" | |
| 764 | } | |
| 765 | }, | |
| 766 | "node_modules/quickbit-universal": { | |
| 767 | "version": "2.2.0", | |
| 768 | "resolved": "https://registry.npmjs.org/quickbit-universal/-/quickbit-universal-2.2.0.tgz", | |
| 769 | "integrity": "sha512-w02i1R8n7+6pEKTud8DfF8zbFY9o7RtPlUc3jWbtCkDKvhbx/AvV7oNnz4/TcmsPGpSJS+fq5Ud6RH6+YPvSGg==", | |
| 770 | "license": "ISC", | |
| 771 | "dependencies": { | |
| 772 | "b4a": "^1.6.0", | |
| 773 | "simdle-universal": "^1.1.0" | |
| 774 | }, | |
| 775 | "optionalDependencies": { | |
| 776 | "quickbit-native": "^2.2.0" | |
| 777 | } | |
| 778 | }, | |
| 779 | "node_modules/rabin-native": { | |
| 780 | "version": "2.0.0", | |
| 781 | "resolved": "https://registry.npmjs.org/rabin-native/-/rabin-native-2.0.0.tgz", | |
| 782 | "integrity": "sha512-x1BlYdIWh+nk9G0scvxyscrYiDPJ7vQZepqqPlVUSInIko1Zxooi8cm6lzBghEGI9aN3DXiunC8FXLgy6kke3Q==", | |
| 783 | "license": "Apache-2.0", | |
| 784 | "dependencies": { | |
| 785 | "require-addon": "^1.1.0" | |
| 786 | } | |
| 787 | }, | |
| 788 | "node_modules/rabin-stream": { | |
| 789 | "version": "2.0.0", | |
| 790 | "resolved": "https://registry.npmjs.org/rabin-stream/-/rabin-stream-2.0.0.tgz", | |
| 791 | "integrity": "sha512-EzS2Ig/qsMBSk1PahC0O0IPdRZe3bspHHLWChhqW1feKz+J46tlVL3g4wWr4lrQdXGzABIMsOhvdT6bjXYMuUw==", | |
| 792 | "license": "Apache-2.0", | |
| 793 | "dependencies": { | |
| 794 | "rabin-native": "^2.0.0", | |
| 795 | "streamx": "^2.23.0" | |
| 796 | } | |
| 797 | }, | |
| 798 | "node_modules/rache": { | |
| 799 | "version": "1.0.0", | |
| 800 | "resolved": "https://registry.npmjs.org/rache/-/rache-1.0.0.tgz", | |
| 801 | "integrity": "sha512-e0k0g0w/8jOCB+7YqCIlOa+OJ38k0wrYS4x18pMSmqOvLKoyhmMhmQyCcvfY6VaP8D75cqkEnlakXs+RYYLqNg==", | |
| 802 | "license": "Apache-2.0" | |
| 803 | }, | |
| 804 | "node_modules/random-array-iterator": { | |
| 805 | "version": "1.0.0", | |
| 806 | "resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz", | |
| 807 | "integrity": "sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q==", | |
| 808 | "license": "MIT" | |
| 809 | }, | |
| 810 | "node_modules/ready-resource": { | |
| 811 | "version": "1.2.0", | |
| 812 | "resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz", | |
| 813 | "integrity": "sha512-nfcco/8iAFV0M+2PYnmIc+/xY0iRb35d42HFHQ7AfjulbGEAFa+XWpByfwSyeVeiBoMLLFVMv1HixxNCqzSQ1g==", | |
| 814 | "license": "MIT", | |
| 815 | "dependencies": { | |
| 816 | "bare-events": "^2.2.0" | |
| 817 | } | |
| 818 | }, | |
| 819 | "node_modules/record-cache": { | |
| 820 | "version": "1.2.0", | |
| 821 | "resolved": "https://registry.npmjs.org/record-cache/-/record-cache-1.2.0.tgz", | |
| 822 | "integrity": "sha512-kyy3HWCez2WrotaL3O4fTn0rsIdfRKOdQQcEJ9KpvmKmbffKVvwsloX063EgRUlpJIXHiDQFhJcTbZequ2uTZw==", | |
| 823 | "license": "MIT", | |
| 824 | "dependencies": { | |
| 825 | "b4a": "^1.3.1" | |
| 826 | } | |
| 827 | }, | |
| 828 | "node_modules/refcounter": { | |
| 829 | "version": "1.0.0", | |
| 830 | "resolved": "https://registry.npmjs.org/refcounter/-/refcounter-1.0.0.tgz", | |
| 831 | "integrity": "sha512-1WosVzUy0kPUaPMEtlNDwm99UsteALIhXXR8rerELoa63WkYIXAl0hxgwPFrIYBRWZPGUyekQ04FRtPJ7dHk9w==", | |
| 832 | "license": "Apache-2.0" | |
| 833 | }, | |
| 834 | "node_modules/require-addon": { | |
| 835 | "version": "1.2.0", | |
| 836 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 837 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 838 | "license": "Apache-2.0", | |
| 839 | "dependencies": { | |
| 840 | "bare-addon-resolve": "^1.3.0" | |
| 841 | }, | |
| 842 | "engines": { | |
| 843 | "bare": ">=1.10.0" | |
| 844 | } | |
| 845 | }, | |
| 846 | "node_modules/resolve-reject-promise": { | |
| 847 | "version": "1.1.0", | |
| 848 | "resolved": "https://registry.npmjs.org/resolve-reject-promise/-/resolve-reject-promise-1.1.0.tgz", | |
| 849 | "integrity": "sha512-LWsTOA91AqzBTjSGgX79Tc130pwcBK6xjpJEO+qRT5IKZ6bGnHKcc8QL3upUBcWuU8OTIDzKK2VNSwmmlqvAVg==", | |
| 850 | "license": "MIT" | |
| 851 | }, | |
| 852 | "node_modules/resource-on-exit": { | |
| 853 | "version": "1.0.0", | |
| 854 | "resolved": "https://registry.npmjs.org/resource-on-exit/-/resource-on-exit-1.0.0.tgz", | |
| 855 | "integrity": "sha512-ViJwJAknCkLRJRPR+9SISQQ7R5eRgtdIHLJsM2hHx1MweAJbJxJ5XnMjjq0Lc7ZGv44ufzAqds1nKxiVkdy4ag==", | |
| 856 | "license": "Apache-2.0" | |
| 857 | }, | |
| 858 | "node_modules/rocksdb-native": { | |
| 859 | "version": "3.17.4", | |
| 860 | "resolved": "https://registry.npmjs.org/rocksdb-native/-/rocksdb-native-3.17.4.tgz", | |
| 861 | "integrity": "sha512-Y9iFKhgxT4mS8+uXxThBvvijCUl+ykFtNH++zbVZaE3ih30KyZ7Hz57BSwO6Q5VK08VkF3LiPzao3Xgr4Pi4NA==", | |
| 862 | "license": "Apache-2.0", | |
| 863 | "dependencies": { | |
| 864 | "compact-encoding": "^3.0.0", | |
| 865 | "ready-resource": "^1.0.0", | |
| 866 | "refcounter": "^1.0.0", | |
| 867 | "require-addon": "^1.0.2", | |
| 868 | "resolve-reject-promise": "^1.1.0", | |
| 869 | "signal-promise": "^1.0.3", | |
| 870 | "streamx": "^2.16.1" | |
| 871 | }, | |
| 872 | "engines": { | |
| 873 | "bare": ">=1.16.0" | |
| 874 | } | |
| 875 | }, | |
| 876 | "node_modules/safety-catch": { | |
| 877 | "version": "1.0.3", | |
| 878 | "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.3.tgz", | |
| 879 | "integrity": "sha512-Zq+J1TefpoEq/HTUabo0YXX5MNvttjWYODGohgPBO2jfko8Wqx3JYMgE823szDFVamdH5PlpByvfiWScTdSYDA==", | |
| 880 | "license": "MIT" | |
| 881 | }, | |
| 882 | "node_modules/same-data": { | |
| 883 | "version": "1.0.0", | |
| 884 | "resolved": "https://registry.npmjs.org/same-data/-/same-data-1.0.0.tgz", | |
| 885 | "integrity": "sha512-Eqn7N2yV+aKMlUHTRqUwYG1Iv0cJqjlvLKj/GoP5PozJn361QaOYX14+v87r7NqQUZC22noP/LfLrSQiPwAygw==", | |
| 886 | "license": "MIT" | |
| 887 | }, | |
| 888 | "node_modules/scope-lock": { | |
| 889 | "version": "1.2.4", | |
| 890 | "resolved": "https://registry.npmjs.org/scope-lock/-/scope-lock-1.2.4.tgz", | |
| 891 | "integrity": "sha512-BpSd8VCuCxW9ZitcdIC/vjs3gMaP9bRBL5nkHcyfX2VrS52n13/rHuBA2xJ/S/4DPuRdAO/Bk8pWd8eD/gHCIA==", | |
| 892 | "license": "Apache-2.0" | |
| 893 | }, | |
| 894 | "node_modules/shuffled-priority-queue": { | |
| 895 | "version": "2.1.0", | |
| 896 | "resolved": "https://registry.npmjs.org/shuffled-priority-queue/-/shuffled-priority-queue-2.1.0.tgz", | |
| 897 | "integrity": "sha512-xhdh7fHyMsr0m/w2kDfRJuBFRS96b9l8ZPNWGaQ+PMvnUnZ/Eh+gJJ9NsHBd7P9k0399WYlCLzsy18EaMfyadA==", | |
| 898 | "license": "MIT", | |
| 899 | "dependencies": { | |
| 900 | "unordered-set": "^2.0.1" | |
| 901 | } | |
| 902 | }, | |
| 903 | "node_modules/signal-promise": { | |
| 904 | "version": "1.0.3", | |
| 905 | "resolved": "https://registry.npmjs.org/signal-promise/-/signal-promise-1.0.3.tgz", | |
| 906 | "integrity": "sha512-WBgv0UnIq2C+Aeh0/n+IRpP6967eIx9WpynTUoiW3isPpfe1zu2LJzyfXdo9Tgef8yR/sGjcMvoUXD7EYdiz+g==", | |
| 907 | "license": "MIT" | |
| 908 | }, | |
| 909 | "node_modules/signed-varint": { | |
| 910 | "version": "2.0.1", | |
| 911 | "resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz", | |
| 912 | "integrity": "sha512-abgDPg1106vuZZOvw7cFwdCABddfJRz5akcCcchzTbhyhYnsG31y4AlZEgp315T7W3nQq5P4xeOm186ZiPVFzw==", | |
| 913 | "license": "MIT", | |
| 914 | "dependencies": { | |
| 915 | "varint": "~5.0.0" | |
| 916 | } | |
| 917 | }, | |
| 918 | "node_modules/simdle-native": { | |
| 919 | "version": "1.3.9", | |
| 920 | "resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz", | |
| 921 | "integrity": "sha512-Isc8sP4OiiIU0mpslD4GHEnR0VQWvR/54WN7YtwEDkNdTJVWtpmvsSvsgRlw5BNGxdYXlVRegdnrSu10H/PhvA==", | |
| 922 | "license": "Apache-2.0", | |
| 923 | "optional": true, | |
| 924 | "dependencies": { | |
| 925 | "b4a": "^1.6.0", | |
| 926 | "require-addon": "^1.1.0" | |
| 927 | } | |
| 928 | }, | |
| 929 | "node_modules/simdle-universal": { | |
| 930 | "version": "1.1.2", | |
| 931 | "resolved": "https://registry.npmjs.org/simdle-universal/-/simdle-universal-1.1.2.tgz", | |
| 932 | "integrity": "sha512-3n3w1bs+uwgHKQjt6arez83EywNlhZzYvNOhvAASTl/8KqNIcqr6aHyGt3JRlfuUC7iB0tomJRPlJ2cRGIpBzA==", | |
| 933 | "license": "ISC", | |
| 934 | "dependencies": { | |
| 935 | "b4a": "^1.6.0" | |
| 936 | }, | |
| 937 | "optionalDependencies": { | |
| 938 | "simdle-native": "^1.1.1" | |
| 939 | } | |
| 940 | }, | |
| 941 | "node_modules/sodium-native": { | |
| 942 | "version": "5.1.0", | |
| 943 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz", | |
| 944 | "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==", | |
| 945 | "license": "MIT", | |
| 946 | "dependencies": { | |
| 947 | "bare-assert": "^1.2.0", | |
| 948 | "require-addon": "^1.1.0", | |
| 949 | "which-runtime": "^1.2.1" | |
| 950 | }, | |
| 951 | "engines": { | |
| 952 | "bare": ">=1.16.0" | |
| 953 | } | |
| 954 | }, | |
| 955 | "node_modules/sodium-secretstream": { | |
| 956 | "version": "1.2.0", | |
| 957 | "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz", | |
| 958 | "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==", | |
| 959 | "license": "MIT", | |
| 960 | "dependencies": { | |
| 961 | "b4a": "^1.1.1", | |
| 962 | "sodium-universal": "^5.0.0" | |
| 963 | } | |
| 964 | }, | |
| 965 | "node_modules/sodium-universal": { | |
| 966 | "version": "5.0.1", | |
| 967 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 968 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 969 | "license": "MIT", | |
| 970 | "dependencies": { | |
| 971 | "sodium-native": "^5.0.1" | |
| 972 | }, | |
| 973 | "peerDependencies": { | |
| 974 | "sodium-javascript": "~0.8.0" | |
| 975 | }, | |
| 976 | "peerDependenciesMeta": { | |
| 977 | "sodium-javascript": { | |
| 978 | "optional": true | |
| 979 | } | |
| 980 | } | |
| 981 | }, | |
| 982 | "node_modules/speedometer": { | |
| 983 | "version": "1.1.0", | |
| 984 | "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-1.1.0.tgz", | |
| 985 | "integrity": "sha512-z/wAiTESw2XVPssY2XRcme4niTc4S5FkkJ4gknudtVoc33Zil8TdTxHy5torRcgqMqksJV2Yz8HQcvtbsnw0mQ==", | |
| 986 | "license": "MIT" | |
| 987 | }, | |
| 988 | "node_modules/streamx": { | |
| 989 | "version": "2.28.0", | |
| 990 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", | |
| 991 | "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", | |
| 992 | "license": "MIT", | |
| 993 | "dependencies": { | |
| 994 | "events-universal": "^1.0.0", | |
| 995 | "fast-fifo": "^1.3.2", | |
| 996 | "text-decoder": "^1.1.0" | |
| 997 | } | |
| 998 | }, | |
| 999 | "node_modules/sub-encoder": { | |
| 1000 | "version": "2.1.3", | |
| 1001 | "resolved": "https://registry.npmjs.org/sub-encoder/-/sub-encoder-2.1.3.tgz", | |
| 1002 | "integrity": "sha512-Xxx04ygZo/1J3yHvaSA6VhDmiSaBQkw/PmO3YnnYFXle+tfOGToC6FcDpIfMztWZXJzuKG14b/57HMkiL58C6A==", | |
| 1003 | "license": "Apache-2.0", | |
| 1004 | "dependencies": { | |
| 1005 | "b4a": "^1.6.0", | |
| 1006 | "codecs": "^3.1.0" | |
| 1007 | } | |
| 1008 | }, | |
| 1009 | "node_modules/teex": { | |
| 1010 | "version": "1.0.1", | |
| 1011 | "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", | |
| 1012 | "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", | |
| 1013 | "license": "MIT", | |
| 1014 | "dependencies": { | |
| 1015 | "streamx": "^2.12.5" | |
| 1016 | } | |
| 1017 | }, | |
| 1018 | "node_modules/text-decoder": { | |
| 1019 | "version": "1.2.7", | |
| 1020 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", | |
| 1021 | "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", | |
| 1022 | "license": "Apache-2.0", | |
| 1023 | "dependencies": { | |
| 1024 | "b4a": "^1.6.4" | |
| 1025 | } | |
| 1026 | }, | |
| 1027 | "node_modules/time-ordered-set": { | |
| 1028 | "version": "2.0.1", | |
| 1029 | "resolved": "https://registry.npmjs.org/time-ordered-set/-/time-ordered-set-2.0.1.tgz", | |
| 1030 | "integrity": "sha512-VJEKmgSN2UiOLB8BpN8Sh2b9LGMHTP5OPrQRpnKjvOheOyzk0mufbjzjKTIG2gO4A+Y+vDJ+0TcLbpUmMLsg8A==", | |
| 1031 | "license": "MIT" | |
| 1032 | }, | |
| 1033 | "node_modules/timeout-refresh": { | |
| 1034 | "version": "2.0.1", | |
| 1035 | "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz", | |
| 1036 | "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==", | |
| 1037 | "license": "MIT" | |
| 1038 | }, | |
| 1039 | "node_modules/udx-native": { | |
| 1040 | "version": "1.21.1", | |
| 1041 | "resolved": "https://registry.npmjs.org/udx-native/-/udx-native-1.21.1.tgz", | |
| 1042 | "integrity": "sha512-Z35jgnF9+4wHRaR9ulypUKOSEwNqksPxW/bJDOzNN4ERyVxzhPOvokjY3Zoi7B+rnD/LLXDYGiPMnPUxXL961g==", | |
| 1043 | "license": "Apache-2.0", | |
| 1044 | "dependencies": { | |
| 1045 | "b4a": "^1.5.0", | |
| 1046 | "bare-events": "^2.2.0", | |
| 1047 | "require-addon": "^1.1.0", | |
| 1048 | "streamx": "^2.22.0" | |
| 1049 | }, | |
| 1050 | "engines": { | |
| 1051 | "bare": ">=1.17.4" | |
| 1052 | } | |
| 1053 | }, | |
| 1054 | "node_modules/unix-path-resolve": { | |
| 1055 | "version": "1.0.2", | |
| 1056 | "resolved": "https://registry.npmjs.org/unix-path-resolve/-/unix-path-resolve-1.0.2.tgz", | |
| 1057 | "integrity": "sha512-kG4g5nobBBaMnH2XbrS4sLUXEpx4nY2J3C6KAlAUcnahG2HChxSPVKWYrqEq76iTo+cyMkLUjqxGaQR2tz097Q==", | |
| 1058 | "license": "MIT" | |
| 1059 | }, | |
| 1060 | "node_modules/unordered-set": { | |
| 1061 | "version": "2.0.1", | |
| 1062 | "resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz", | |
| 1063 | "integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==", | |
| 1064 | "license": "MIT" | |
| 1065 | }, | |
| 1066 | "node_modules/unslab": { | |
| 1067 | "version": "1.3.0", | |
| 1068 | "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz", | |
| 1069 | "integrity": "sha512-YATkfKAFj47kTzmiQrWXMyRvaVrHsW6MEALa4bm+FhiA2YG4oira+Z3DXN6LrYOYn2Y8eO94Lwl9DOHjs1FpoQ==", | |
| 1070 | "license": "Apache-2.0", | |
| 1071 | "dependencies": { | |
| 1072 | "b4a": "^1.6.6" | |
| 1073 | } | |
| 1074 | }, | |
| 1075 | "node_modules/varint": { | |
| 1076 | "version": "5.0.0", | |
| 1077 | "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz", | |
| 1078 | "integrity": "sha512-gC13b/bWrqQoKY2EmROCZ+AR0jitc6DnDGaQ6Ls9QpKmuSgJB1eQ7H3KETtQm7qSdMWMKCmsshyCmUwMLh3OAA==", | |
| 1079 | "license": "MIT" | |
| 1080 | }, | |
| 1081 | "node_modules/which-runtime": { | |
| 1082 | "version": "1.4.0", | |
| 1083 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.4.0.tgz", | |
| 1084 | "integrity": "sha512-0ugbP4CJW4e2D20jvEcC4973dCgIaHI4Rw1PT+26U9zEve7FyYdWAIwUnoeOYvoCfn+wXHoHTKb1KhkYlb60Pw==", | |
| 1085 | "license": "Apache-2.0" | |
| 1086 | }, | |
| 1087 | "node_modules/xache": { | |
| 1088 | "version": "1.3.0", | |
| 1089 | "resolved": "https://registry.npmjs.org/xache/-/xache-1.3.0.tgz", | |
| 1090 | "integrity": "sha512-ekAknjLTiyXJcuezqwa/cOMfFenVVY2toL4J7D3AfTn7UEDYb8JSgRnv3CK8khF7u38XZ4NjS1MXEw4RwqJ4oA==", | |
| 1091 | "license": "MIT" | |
| 1092 | }, | |
| 1093 | "node_modules/z32": { | |
| 1094 | "version": "1.1.0", | |
| 1095 | "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz", | |
| 1096 | "integrity": "sha512-1WUHy+VS6d0HPNspDxvLssBbeQjXMjSnpv0vH82vRAUfg847NmX3OXozp/hRP5jPhxBbrVzrgvAt+UsGNzRFQQ==", | |
| 1097 | "license": "MIT", | |
| 1098 | "dependencies": { | |
| 1099 | "b4a": "^1.5.3" | |
| 1100 | } | |
| 1101 | } | |
| 1102 | } | |
| 1103 | } |
| … | … | @@ -0,0 +1,24 @@ |
| 1 | { | |
| 2 | "name": "hyper-vdrive", | |
| 3 | "version": "0.0.1", | |
| 4 | "description": "A real Hyperdrive that stores no blob bytes — blocks are materialised on demand from a content-addressed store on disk", | |
| 5 | "main": "lib/storage.cjs", | |
| 6 | "exports": { | |
| 7 | ".": "./lib/storage.cjs", | |
| 8 | "./passthrough": "./lib/passthrough.cjs" | |
| 9 | }, | |
| 10 | "scripts": { | |
| 11 | "test:passthrough": "node test/pass-through.mjs ./work/passthrough", | |
| 12 | "test:gc": "node test/gc-bitfield.mjs ./work/gc" | |
| 13 | }, | |
| 14 | "dependencies": { | |
| 15 | "corestore": "^7.12.0", | |
| 16 | "hypercore": "^11.35.2", | |
| 17 | "hypercore-crypto": "^3.7.0", | |
| 18 | "hypercore-id-encoding": "^1.3.0", | |
| 19 | "hypercore-storage": "^3.2.1", | |
| 20 | "hyperdrive": "^13.3.3", | |
| 21 | "hyperswarm": "^4.17.0" | |
| 22 | }, | |
| 23 | "license": "AGPL-3.0-or-later" | |
| 24 | } |
| … | … | @@ -0,0 +1,152 @@ |
| 1 | // Step 2 — GC / bitfield pairing. | |
| 2 | // Q1: after the overlay drops bytes, does the core still advertise the block? | |
| 3 | // Q2: what does a PEER experience when it requests an advertised-but-missing block? | |
| 4 | // Q3: does core.clear() on that range make the lie go away? | |
| 5 | import { createRequire } from 'module' | |
| 6 | const require = createRequire(import.meta.url) | |
| 7 | ||
| 8 | const fs = require('fs') | |
| 9 | const crypto = require('hypercore-crypto') | |
| 10 | const Corestore = require('corestore') | |
| 11 | const Hyperdrive = require('hyperdrive') | |
| 12 | const { ContentStore, build, stats } = require('../lib/storage.cjs') | |
| 13 | const { chunk } = await import('../../hyper-overlay/lib/chunker.js') | |
| 14 | ||
| 15 | const ROOT = process.argv[2] | |
| 16 | ||
| 17 | // A block we advertise but cannot produce surfaces as an UNCAUGHT REQUEST_TIMEOUT | |
| 18 | // from the replicator's timer, not as a rejection of get(). Capture it so the | |
| 19 | // probe can finish and report rather than dying. | |
| 20 | const uncaught = [] | |
| 21 | process.on('uncaughtException', (err) => { | |
| 22 | uncaught.push(err.code || err.message) | |
| 23 | }) | |
| 24 | ||
| 25 | const srcDir = ROOT + '/src' | |
| 26 | fs.mkdirSync(srcDir, { recursive: true }) | |
| 27 | ||
| 28 | const FILES = [ | |
| 29 | { name: '/keep.bin', bytes: crypto.randomBytes(3 * 1024 * 1024) }, | |
| 30 | { name: '/victim.bin', bytes: crypto.randomBytes(3 * 1024 * 1024) } | |
| 31 | ] | |
| 32 | for (const f of FILES) { | |
| 33 | f.file = srcDir + f.name | |
| 34 | fs.writeFileSync(f.file, f.bytes) | |
| 35 | f.chunks = chunk(f.bytes) | |
| 36 | } | |
| 37 | ||
| 38 | const content = new ContentStore() | |
| 39 | for (const f of FILES) for (const c of f.chunks) content.add(c.hash, f.file, c.offset, c.length) | |
| 40 | ||
| 41 | const Storage = build(content) | |
| 42 | const hostDir = ROOT + '/host' | |
| 43 | ||
| 44 | function write (ws, buf) { | |
| 45 | return new Promise((resolve) => { | |
| 46 | if (ws.write(buf)) return resolve() | |
| 47 | ws.once('drain', resolve) | |
| 48 | }) | |
| 49 | } | |
| 50 | ||
| 51 | async function openHost () { | |
| 52 | const store = new Corestore(new Storage(hostDir), { primaryKey: Buffer.alloc(32, 7), unsafe: true }) | |
| 53 | const drive = new Hyperdrive(store) | |
| 54 | await drive.ready() | |
| 55 | await drive.getBlobs() | |
| 56 | return { store, drive } | |
| 57 | } | |
| 58 | ||
| 59 | // publish | |
| 60 | { | |
| 61 | const { store, drive } = await openHost() | |
| 62 | for (const f of FILES) { | |
| 63 | const ws = drive.createWriteStream(f.name, { dedup: true }) | |
| 64 | for (const c of f.chunks) await write(ws, c.data) | |
| 65 | await new Promise((resolve, reject) => { | |
| 66 | ws.once('error', reject) | |
| 67 | ws.once('close', resolve) | |
| 68 | ws.end() | |
| 69 | }) | |
| 70 | } | |
| 71 | await drive.close() | |
| 72 | await store.close() | |
| 73 | } | |
| 74 | ||
| 75 | const host = await openHost() | |
| 76 | const driveKey = host.drive.key | |
| 77 | ||
| 78 | // which blocks belong to /victim.bin? | |
| 79 | const victimEntry = await host.drive.entry('/victim.bin') | |
| 80 | const victimBlob = victimEntry.value.blob | |
| 81 | const victimMap = await host.drive.blobs.getBlockMap(victimBlob) | |
| 82 | const victimBlocks = victimMap.blocks.map((b) => b.index) | |
| 83 | ||
| 84 | // peer setup | |
| 85 | const client = new Corestore(ROOT + '/peer', { primaryKey: Buffer.alloc(32, 9), unsafe: true }) | |
| 86 | const clientDrive = new Hyperdrive(client, driveKey) | |
| 87 | await clientDrive.ready() | |
| 88 | const s1 = host.store.replicate(true) | |
| 89 | const s2 = client.replicate(false) | |
| 90 | s1.pipe(s2).pipe(s1) | |
| 91 | await clientDrive.core.update({ wait: true }) | |
| 92 | ||
| 93 | const result = {} | |
| 94 | ||
| 95 | // baseline: peer can fetch a file normally | |
| 96 | const keep = await clientDrive.get('/keep.bin') | |
| 97 | result.peerFetchedKeepOk = keep !== null && keep.equals(FILES[0].bytes) | |
| 98 | ||
| 99 | // Q1 — the overlay GCs victim.bin's bytes | |
| 100 | for (const c of FILES[1].chunks) content.locations.delete(c.hash) | |
| 101 | result.hostStillAdvertises_afterBytesGone = await host.drive.blobs.core.has(victimBlocks[0]) | |
| 102 | ||
| 103 | // Q2 — peer requests the advertised-but-missing block | |
| 104 | const t0 = Date.now() | |
| 105 | try { | |
| 106 | const got = await clientDrive.get('/victim.bin', { timeout: 5000 }) | |
| 107 | result.peerResult = got === null ? 'null' : 'bytes:' + got.length | |
| 108 | } catch (err) { | |
| 109 | result.peerResult = 'throw:' + (err.code || err.message) | |
| 110 | } | |
| 111 | result.peerWaitedMs = Date.now() - t0 | |
| 112 | ||
| 113 | // Q3 — pair the GC with a clear of those blocks | |
| 114 | await host.drive.blobs.core.clear(victimBlocks[0], victimBlocks[victimBlocks.length - 1] + 1) | |
| 115 | result.hostAdvertises_afterClear = await host.drive.blobs.core.has(victimBlocks[0]) | |
| 116 | ||
| 117 | // peer asks again, from a clean session | |
| 118 | const client2 = new Corestore(ROOT + '/peer2', { primaryKey: Buffer.alloc(32, 11), unsafe: true }) | |
| 119 | const clientDrive2 = new Hyperdrive(client2, driveKey) | |
| 120 | await clientDrive2.ready() | |
| 121 | const s3 = host.store.replicate(true) | |
| 122 | const s4 = client2.replicate(false) | |
| 123 | s3.pipe(s4).pipe(s3) | |
| 124 | await clientDrive2.core.update({ wait: true }) | |
| 125 | ||
| 126 | const t1 = Date.now() | |
| 127 | try { | |
| 128 | const got = await clientDrive2.get('/victim.bin', { timeout: 5000 }) | |
| 129 | result.peerResultAfterClear = got === null ? 'null' : 'bytes:' + got.length | |
| 130 | } catch (err) { | |
| 131 | result.peerResultAfterClear = 'throw:' + (err.code || err.message) | |
| 132 | } | |
| 133 | result.peerWaitedMsAfterClear = Date.now() - t1 | |
| 134 | ||
| 135 | // and the file that was never GC'd still works | |
| 136 | const keep2 = await clientDrive2.get('/keep.bin', { timeout: 5000 }) | |
| 137 | result.keepStillFetchableAfterClear = keep2 !== null && keep2.equals(FILES[0].bytes) | |
| 138 | ||
| 139 | result.victimBlockCount = victimBlocks.length | |
| 140 | result.stats = { ...stats } | |
| 141 | result.uncaught = uncaught | |
| 142 | ||
| 143 | for (const s of [s1, s2, s3, s4]) s.destroy() | |
| 144 | await clientDrive2.close() | |
| 145 | await client2.close() | |
| 146 | await clientDrive.close() | |
| 147 | await client.close() | |
| 148 | await host.drive.close() | |
| 149 | await host.store.close() | |
| 150 | content.close() | |
| 151 | ||
| 152 | console.log(JSON.stringify(result, null, 2)) |
| … | … | @@ -0,0 +1,160 @@ |
| 1 | // T2 — does a pass-through storage wrapper survive real hyperdrive use? | |
| 2 | // Pass = wrapped and unwrapped runs produce identical drive keys and identical | |
| 3 | // bytes, content survives a reopen from disk, and a plain peer can replicate | |
| 4 | // the wrapped drive. | |
| 5 | import { createRequire } from 'module' | |
| 6 | const require = createRequire(import.meta.url) | |
| 7 | ||
| 8 | const fs = require('fs') | |
| 9 | const crypto = require('hypercore-crypto') | |
| 10 | const Corestore = require('corestore') | |
| 11 | const Hyperdrive = require('hyperdrive') | |
| 12 | const { WrappedCorestoreStorage, stats } = require('../lib/passthrough.cjs') | |
| 13 | const { chunk } = await import('../../hyper-overlay/lib/chunker.js') | |
| 14 | ||
| 15 | const ROOT = process.argv[2] | |
| 16 | const PRIMARY = Buffer.alloc(32, 7) | |
| 17 | ||
| 18 | const FILES = [ | |
| 19 | ['/small.txt', Buffer.from('hello pear\n')], | |
| 20 | ['/empty.bin', Buffer.alloc(0)], | |
| 21 | ['/mid.bin', crypto.randomBytes(300 * 1024)], | |
| 22 | ['/big.bin', crypto.randomBytes(6 * 1024 * 1024)] | |
| 23 | ] | |
| 24 | ||
| 25 | function write (ws, buf) { | |
| 26 | return new Promise((resolve) => { | |
| 27 | if (ws.write(buf)) return resolve() | |
| 28 | ws.once('drain', resolve) | |
| 29 | }) | |
| 30 | } | |
| 31 | ||
| 32 | function openStore (dir, wrapped) { | |
| 33 | const storage = wrapped ? new WrappedCorestoreStorage(dir) : dir | |
| 34 | return new Corestore(storage, { primaryKey: PRIMARY, unsafe: true }) | |
| 35 | } | |
| 36 | ||
| 37 | async function put (drive, name, buf) { | |
| 38 | // NB: 0-byte files must NOT take the dedup path — hyperblobs 2.12.1 writes an | |
| 39 | // empty block map, then seekBlockMap() reads map.blocks === null and throws. | |
| 40 | // Reproduced on a plain store, so it is upstream, not the wrapper. | |
| 41 | const ws = drive.createWriteStream(name, buf.length === 0 ? {} : { dedup: true }) | |
| 42 | for (const c of chunk(buf)) await write(ws, c.data) | |
| 43 | await new Promise((resolve, reject) => { | |
| 44 | ws.once('error', reject) | |
| 45 | ws.once('close', resolve) | |
| 46 | ws.end() | |
| 47 | }) | |
| 48 | } | |
| 49 | ||
| 50 | async function build (dir, wrapped) { | |
| 51 | const store = openStore(dir, wrapped) | |
| 52 | const drive = new Hyperdrive(store) | |
| 53 | await drive.ready() | |
| 54 | for (const [name, buf] of FILES) await put(drive, name, buf) | |
| 55 | await drive.getBlobs() | |
| 56 | ||
| 57 | const out = { | |
| 58 | driveKey: drive.key.toString('hex'), | |
| 59 | blobsKey: drive.blobs.core.key.toString('hex'), | |
| 60 | coreLength: drive.core.length, | |
| 61 | blobsLength: drive.blobs.core.length, | |
| 62 | rootHash: (await drive.core.treeHash()).toString('hex'), | |
| 63 | digests: {} | |
| 64 | } | |
| 65 | for (const [name, buf] of FILES) { | |
| 66 | const got = await drive.get(name) | |
| 67 | const bytes = got === null ? Buffer.alloc(0) : got | |
| 68 | out.digests[name] = crypto.hash(bytes).toString('hex') | |
| 69 | if (!bytes.equals(buf)) throw new Error('READBACK MISMATCH ' + name + ' (' + (wrapped ? 'wrapped' : 'plain') + ')') | |
| 70 | } | |
| 71 | await drive.close() | |
| 72 | await store.close() | |
| 73 | return out | |
| 74 | } | |
| 75 | ||
| 76 | async function reopen (dir, wrapped) { | |
| 77 | const store = openStore(dir, wrapped) | |
| 78 | const drive = new Hyperdrive(store) | |
| 79 | await drive.ready() | |
| 80 | const digests = {} | |
| 81 | for (const [name, buf] of FILES) { | |
| 82 | const got = await drive.get(name) | |
| 83 | const bytes = got === null ? Buffer.alloc(0) : got | |
| 84 | digests[name] = crypto.hash(bytes).toString('hex') | |
| 85 | if (!bytes.equals(buf)) throw new Error('REOPEN MISMATCH ' + name) | |
| 86 | } | |
| 87 | const key = drive.key.toString('hex') | |
| 88 | await drive.close() | |
| 89 | await store.close() | |
| 90 | return { key, digests } | |
| 91 | } | |
| 92 | ||
| 93 | // replicate wrapped host -> plain client, in memory | |
| 94 | async function replicate (hostDir, clientDir, driveKey) { | |
| 95 | const host = openStore(hostDir, true) | |
| 96 | const hostDrive = new Hyperdrive(host) | |
| 97 | await hostDrive.ready() | |
| 98 | await hostDrive.getBlobs() | |
| 99 | ||
| 100 | const client = openStore(clientDir, false) | |
| 101 | const clientDrive = new Hyperdrive(client, Buffer.from(driveKey, 'hex')) | |
| 102 | await clientDrive.ready() | |
| 103 | ||
| 104 | const s1 = host.replicate(true) | |
| 105 | const s2 = client.replicate(false) | |
| 106 | s1.pipe(s2).pipe(s1) | |
| 107 | ||
| 108 | await clientDrive.core.update({ wait: true }) | |
| 109 | const digests = {} | |
| 110 | for (const [name, buf] of FILES) { | |
| 111 | const got = await clientDrive.get(name) | |
| 112 | const bytes = got === null ? Buffer.alloc(0) : got | |
| 113 | digests[name] = crypto.hash(bytes).toString('hex') | |
| 114 | if (!bytes.equals(buf)) throw new Error('REPLICATION MISMATCH ' + name) | |
| 115 | } | |
| 116 | ||
| 117 | s1.destroy() | |
| 118 | s2.destroy() | |
| 119 | await clientDrive.close() | |
| 120 | await client.close() | |
| 121 | await hostDrive.close() | |
| 122 | await host.close() | |
| 123 | return digests | |
| 124 | } | |
| 125 | ||
| 126 | const plain = await build(ROOT + '/plain', false) | |
| 127 | const before = { ...stats } | |
| 128 | const wrapped = await build(ROOT + '/wrapped', true) | |
| 129 | const after = { ...stats } | |
| 130 | ||
| 131 | const same = | |
| 132 | plain.driveKey === wrapped.driveKey && | |
| 133 | plain.blobsKey === wrapped.blobsKey && | |
| 134 | plain.rootHash === wrapped.rootHash && | |
| 135 | JSON.stringify(plain.digests) === JSON.stringify(wrapped.digests) | |
| 136 | ||
| 137 | const re = await reopen(ROOT + '/wrapped', true) | |
| 138 | const rep = await replicate(ROOT + '/wrapped', ROOT + '/client', wrapped.driveKey) | |
| 139 | ||
| 140 | console.log( | |
| 141 | JSON.stringify( | |
| 142 | { | |
| 143 | identical: same, | |
| 144 | plainKey: plain.driveKey, | |
| 145 | wrappedKey: wrapped.driveKey, | |
| 146 | rootHashMatch: plain.rootHash === wrapped.rootHash, | |
| 147 | reopenOk: re.key === wrapped.driveKey && JSON.stringify(re.digests) === JSON.stringify(wrapped.digests), | |
| 148 | replicationOk: JSON.stringify(rep) === JSON.stringify(wrapped.digests), | |
| 149 | wrapperTraffic: { | |
| 150 | coreStoragesWrapped: after.wrapped - before.wrapped, | |
| 151 | readBatches: after.read - before.read, | |
| 152 | writeBatches: after.write - before.write, | |
| 153 | getBlock: after.getBlock - before.getBlock, | |
| 154 | putBlock: after.putBlock - before.putBlock | |
| 155 | } | |
| 156 | }, | |
| 157 | null, | |
| 158 | 2 | |
| 159 | ) | |
| 160 | ) |
| … | … | @@ -0,0 +1,142 @@ |
| 1 | // T3 — correctness of the block-redirecting drive. | |
| 2 | // Pass = same drive key as a stock drive, same bytes on read, survives reopen, | |
| 3 | // replicates to a stock peer, and stores no blob bytes. | |
| 4 | import { createRequire } from 'module' | |
| 5 | const require = createRequire(import.meta.url) | |
| 6 | ||
| 7 | const fs = require('fs') | |
| 8 | const { execFileSync } = require('child_process') | |
| 9 | const crypto = require('hypercore-crypto') | |
| 10 | const Corestore = require('corestore') | |
| 11 | const Hyperdrive = require('hyperdrive') | |
| 12 | const { ContentStore, build, stats } = require('../lib/storage.cjs') | |
| 13 | const { chunk } = await import('../../hyper-overlay/lib/chunker.js') | |
| 14 | ||
| 15 | const ROOT = process.argv[2] | |
| 16 | const SRC = process.argv.slice(3) // real files on disk = our "overlay" | |
| 17 | const PRIMARY = Buffer.alloc(32, 7) | |
| 18 | const du = (p) => Number(execFileSync('du', ['-sb', p]).toString().split('\t')[0]) | |
| 19 | ||
| 20 | function write (ws, buf) { | |
| 21 | return new Promise((resolve) => { | |
| 22 | if (ws.write(buf)) return resolve() | |
| 23 | ws.once('drain', resolve) | |
| 24 | }) | |
| 25 | } | |
| 26 | ||
| 27 | // Index the source files exactly as the overlay does: FastCDC chunks, hash -> location. | |
| 28 | const content = new ContentStore() | |
| 29 | const sources = [] | |
| 30 | for (const [i, file] of SRC.entries()) { | |
| 31 | const buf = fs.readFileSync(file) | |
| 32 | const chunks = chunk(buf) | |
| 33 | for (const c of chunks) content.add(c.hash, file, c.offset, c.length) | |
| 34 | // distinct path per source — same basename would overwrite the earlier entry | |
| 35 | sources.push({ file, name: '/v' + i + '-' + file.split('/').slice(-1)[0], buf, chunks }) | |
| 36 | } | |
| 37 | ||
| 38 | async function makeDrive (dir, redirect) { | |
| 39 | const pointers = null | |
| 40 | const Storage = redirect ? build(content) : null | |
| 41 | const store = new Corestore(redirect ? new Storage(dir) : dir, { | |
| 42 | primaryKey: PRIMARY, | |
| 43 | unsafe: true | |
| 44 | }) | |
| 45 | const drive = new Hyperdrive(store) | |
| 46 | await drive.ready() | |
| 47 | return { store, drive, pointers } | |
| 48 | } | |
| 49 | ||
| 50 | async function publish (dir, redirect) { | |
| 51 | const { store, drive, pointers } = await makeDrive(dir, redirect) | |
| 52 | for (const s of sources) { | |
| 53 | const ws = drive.createWriteStream(s.name, { dedup: true }) | |
| 54 | for (const c of s.chunks) await write(ws, c.data) | |
| 55 | await new Promise((resolve, reject) => { | |
| 56 | ws.once('error', reject) | |
| 57 | ws.once('close', resolve) | |
| 58 | ws.end() | |
| 59 | }) | |
| 60 | } | |
| 61 | await drive.getBlobs() | |
| 62 | const out = { | |
| 63 | driveKey: drive.key.toString('hex'), | |
| 64 | blobsKey: drive.blobs.core.key.toString('hex'), | |
| 65 | rootHash: (await drive.core.treeHash()).toString('hex'), | |
| 66 | blobsLength: drive.blobs.core.length | |
| 67 | } | |
| 68 | await drive.close() | |
| 69 | await store.close() | |
| 70 | return out | |
| 71 | } | |
| 72 | ||
| 73 | async function verifyReads (dir, redirect) { | |
| 74 | const { store, drive } = await makeDrive(dir, redirect) | |
| 75 | for (const s of sources) { | |
| 76 | const got = await drive.get(s.name) | |
| 77 | if (got === null || !got.equals(s.buf)) throw new Error('MISMATCH ' + s.name) | |
| 78 | } | |
| 79 | await drive.close() | |
| 80 | await store.close() | |
| 81 | return true | |
| 82 | } | |
| 83 | ||
| 84 | async function replicateToStockPeer (hostDir, clientDir, driveKey) { | |
| 85 | const { store: host, drive: hostDrive } = await makeDrive(hostDir, true) | |
| 86 | await hostDrive.getBlobs() | |
| 87 | ||
| 88 | const client = new Corestore(clientDir, { primaryKey: Buffer.alloc(32, 9), unsafe: true }) | |
| 89 | const clientDrive = new Hyperdrive(client, Buffer.from(driveKey, 'hex')) | |
| 90 | await clientDrive.ready() | |
| 91 | ||
| 92 | const s1 = host.replicate(true) | |
| 93 | const s2 = client.replicate(false) | |
| 94 | s1.pipe(s2).pipe(s1) | |
| 95 | ||
| 96 | await clientDrive.core.update({ wait: true }) | |
| 97 | for (const s of sources) { | |
| 98 | const got = await clientDrive.get(s.name) | |
| 99 | if (got === null || !got.equals(s.buf)) throw new Error('REPLICATION MISMATCH ' + s.name) | |
| 100 | } | |
| 101 | ||
| 102 | s1.destroy() | |
| 103 | s2.destroy() | |
| 104 | await clientDrive.close() | |
| 105 | await client.close() | |
| 106 | await hostDrive.close() | |
| 107 | await host.close() | |
| 108 | return true | |
| 109 | } | |
| 110 | ||
| 111 | const stock = await publish(ROOT + '/stock', false) | |
| 112 | const stockBytes = du(ROOT + '/stock') | |
| 113 | ||
| 114 | const virt = await publish(ROOT + '/virtual', true) | |
| 115 | const virtBytes = du(ROOT + '/virtual') | |
| 116 | const pointerBytes = 0 | |
| 117 | ||
| 118 | const reopenOk = await verifyReads(ROOT + '/virtual', true) | |
| 119 | const replicationOk = await replicateToStockPeer(ROOT + '/virtual', ROOT + '/peer', virt.driveKey) | |
| 120 | const peerBytes = du(ROOT + '/peer') | |
| 121 | content.close() | |
| 122 | ||
| 123 | console.log( | |
| 124 | JSON.stringify( | |
| 125 | { | |
| 126 | sameDriveKey: stock.driveKey === virt.driveKey, | |
| 127 | sameBlobsKey: stock.blobsKey === virt.blobsKey, | |
| 128 | sameRootHash: stock.rootHash === virt.rootHash, | |
| 129 | blobsLength: { stock: stock.blobsLength, virtual: virt.blobsLength }, | |
| 130 | reopenOk, | |
| 131 | replicationOk, | |
| 132 | inputBytes: sources.reduce((a, s) => a + s.buf.length, 0), | |
| 133 | stockStoreBytes: stockBytes, | |
| 134 | virtualStoreBytes: virtBytes, | |
| 135 | pointerFileBytes: pointerBytes, | |
| 136 | peerStoreBytes: peerBytes, | |
| 137 | stats | |
| 138 | }, | |
| 139 | null, | |
| 140 | 2 | |
| 141 | ) | |
| 142 | ) |
| … | … | @@ -0,0 +1,67 @@ |
| 1 | // N successive releases into ONE virtual drive, at the same path. | |
| 2 | // Measures store size and publish time per release. | |
| 3 | // node test/releases.mjs <workDir> <file...> | |
| 4 | import { createRequire } from 'module' | |
| 5 | const require = createRequire(import.meta.url) | |
| 6 | ||
| 7 | const fs = require('fs') | |
| 8 | const { execFileSync } = require('child_process') | |
| 9 | const Corestore = require('corestore') | |
| 10 | const Hyperdrive = require('hyperdrive') | |
| 11 | const { ContentStore, build, stats } = require('../lib/storage.cjs') | |
| 12 | const { chunk } = await import('../../hyper-overlay/lib/chunker.js') | |
| 13 | ||
| 14 | const ROOT = process.argv[2] | |
| 15 | const SRC = process.argv.slice(3) | |
| 16 | const NAME = '/app.bin' | |
| 17 | const PRIMARY = Buffer.alloc(32, 7) | |
| 18 | const du = (p) => Number(execFileSync('du', ['-sb', p]).toString().split('\t')[0]) | |
| 19 | ||
| 20 | const content = new ContentStore() | |
| 21 | const Storage = build(content) | |
| 22 | const dir = ROOT + '/virtual' | |
| 23 | ||
| 24 | function write (ws, buf) { | |
| 25 | return new Promise((resolve) => { | |
| 26 | if (ws.write(buf)) return resolve() | |
| 27 | ws.once('drain', resolve) | |
| 28 | }) | |
| 29 | } | |
| 30 | ||
| 31 | for (const [i, file] of SRC.entries()) { | |
| 32 | const t0 = Date.now() | |
| 33 | const buf = fs.readFileSync(file) | |
| 34 | const chunks = chunk(buf) | |
| 35 | for (const c of chunks) content.add(c.hash, file, c.offset, c.length) | |
| 36 | ||
| 37 | const store = new Corestore(new Storage(dir), { primaryKey: PRIMARY, unsafe: true }) | |
| 38 | const drive = new Hyperdrive(store) | |
| 39 | await drive.ready() | |
| 40 | ||
| 41 | const ws = drive.createWriteStream(NAME, { dedup: true }) | |
| 42 | for (const c of chunks) await write(ws, c.data) | |
| 43 | await new Promise((resolve, reject) => { | |
| 44 | ws.once('error', reject) | |
| 45 | ws.once('close', resolve) | |
| 46 | ws.end() | |
| 47 | }) | |
| 48 | ||
| 49 | await drive.getBlobs() | |
| 50 | const blobsLength = drive.blobs.core.length | |
| 51 | await drive.close() | |
| 52 | await store.close() | |
| 53 | ||
| 54 | console.log( | |
| 55 | JSON.stringify({ | |
| 56 | release: i + 1, | |
| 57 | input: buf.length, | |
| 58 | blobsLength, | |
| 59 | redirectedBlocks: stats.putRedirected, | |
| 60 | literalBlocks: stats.putStored, | |
| 61 | storeBytes: du(dir), | |
| 62 | elapsedMs: Date.now() - t0 | |
| 63 | }) | |
| 64 | ) | |
| 65 | } | |
| 66 | ||
| 67 | content.close() |