hypercore-storage 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js ADDED
@@ -0,0 +1,742 @@
1
+ const RocksDB = require('rocksdb-native')
2
+ const c = require('compact-encoding')
3
+ const { UINT } = require('index-encoder')
4
+ const RW = require('read-write-mutexify')
5
+ const b4a = require('b4a')
6
+ const flat = require('flat-tree')
7
+ const assert = require('nanoassert')
8
+ const m = require('./lib/messages')
9
+ const DependencyStream = require('./lib/dependency-stream')
10
+
11
+ const INF = b4a.from([0xff])
12
+
13
+ // <TL_INFO> = { version, free, total }
14
+ // <TL_LOCAL_SEED> = seed
15
+ // <TL_CORE_INFO><discovery-key-32-bytes> = { version, owner, core, data }
16
+
17
+ // <core><CORE_MANIFEST> = { key, manifest? }
18
+ // <core><CORE_LOCAL_SEED> = seed
19
+ // <core><CORE_ENCRYPTION_KEY> = encryptionKey // should come later, not important initially
20
+ // <core><CORE_HEAD><data> = { fork, length, byteLength, signature }
21
+ // <core><CORE_BATCHES><name> = <data>
22
+
23
+ // <data><CORE_INFO> = { version }
24
+ // <data><CORE_UPDATES> = { contiguousLength, blocks }
25
+ // <data><CORE_DEPENDENCY = { data, length, roots }
26
+ // <data><CORE_HINTS> = { reorg } // should come later, not important initially
27
+ // <data><CORE_TREE><index> = { index, size, hash }
28
+ // <data><CORE_BITFIELD><index> = <4kb buffer>
29
+ // <data><CORE_BLOCKS><index> = <buffer>
30
+ // <data><CORE_USER_DATA><key> = <value>
31
+
32
+ // top level prefixes
33
+ const TL = {
34
+ STORAGE_INFO: 0,
35
+ LOCAL_SEED: 1,
36
+ DKEYS: 2,
37
+ CORE: 3,
38
+ DATA: 4,
39
+ DEFAULT_KEY: 5
40
+ }
41
+
42
+ // core prefixes
43
+ const CORE = {
44
+ MANIFEST: 0,
45
+ LOCAL_SEED: 1,
46
+ ENCRYPTION_KEY: 2,
47
+ HEAD: 3,
48
+ BATCHES: 4
49
+ }
50
+
51
+ // data prefixes
52
+ const DATA = {
53
+ INFO: 0,
54
+ UPDATES: 1,
55
+ DEPENDENCY: 2,
56
+ HINTS: 3,
57
+ TREE: 4,
58
+ BITFIELD: 5,
59
+ BLOCK: 6,
60
+ USER_DATA: 7
61
+ }
62
+
63
+ const SLAB = {
64
+ start: 0,
65
+ end: 65536,
66
+ buffer: b4a.allocUnsafe(65536)
67
+ }
68
+
69
+ // PREFIX + BATCH + TYPE + INDEX
70
+
71
+ class WriteBatch {
72
+ constructor (storage, write) {
73
+ this.storage = storage
74
+ this.write = write
75
+ }
76
+
77
+ setCoreHead (head) {
78
+ this.write.tryPut(encodeCoreIndex(this.storage.corePointer, CORE.HEAD, this.storage.dataPointer), c.encode(m.CoreHead, head))
79
+ }
80
+
81
+ setCoreAuth ({ key, manifest }) {
82
+ this.write.tryPut(encodeCoreIndex(this.storage.corePointer, CORE.MANIFEST), c.encode(m.CoreAuth, { key, manifest }))
83
+ }
84
+
85
+ setBatchPointer (name, pointer) {
86
+ this.write.tryPut(encodeBatch(this.storage.corePointer, CORE.BATCHES, name), encode(m.DataPointer, pointer))
87
+ }
88
+
89
+ setDataDependency ({ data, length }) {
90
+ this.write.tryPut(encodeDataIndex(this.storage.dataPointer, DATA.DEPENDENCY), encode(m.DataDependency, { data, length }))
91
+ }
92
+
93
+ setLocalKeyPair (keyPair) {
94
+ this.write.tryPut(encodeCoreIndex(this.storage.corePointer, CORE.LOCAL_SEED), encode(m.KeyPair, keyPair))
95
+ }
96
+
97
+ setEncryptionKey (encryptionKey) {
98
+ this.write.tryPut(encodeCoreIndex(this.storage.corePointer, CORE.ENCRYPTION_KEY), encryptionKey)
99
+ }
100
+
101
+ setDataInfo (info) {
102
+ if (info.version !== 0) throw new Error('Version > 0 is not supported')
103
+ this.write.tryPut(encodeDataIndex(this.storage.dataPointer, DATA.INFO), encode(m.DataInfo, info))
104
+ }
105
+
106
+ setUserData (key, value) {
107
+ this.write.tryPut(encodeUserDataIndex(this.storage.dataPointer, DATA.USER_DATA, key), value)
108
+ }
109
+
110
+ putBlock (index, data) {
111
+ this.write.tryPut(encodeDataIndex(this.storage.dataPointer, DATA.BLOCK, index), data)
112
+ }
113
+
114
+ deleteBlock (index) {
115
+ this.write.tryDelete(encodeDataIndex(this.storage.dataPointer, DATA.BLOCK, index))
116
+ }
117
+
118
+ deleteBlockRange (start, end) {
119
+ return this._deleteRange(DATA.BLOCK, start, end)
120
+ }
121
+
122
+ putTreeNode (node) {
123
+ this.write.tryPut(encodeDataIndex(this.storage.dataPointer, DATA.TREE, node.index), encode(m.TreeNode, node))
124
+ }
125
+
126
+ deleteTreeNode (index) {
127
+ this.write.tryDelete(encodeDataIndex(this.storage.dataPointer, DATA.TREE, index))
128
+ }
129
+
130
+ deleteTreeNodeRange (start, end) {
131
+ return this._deleteRange(DATA.TREE, start, end)
132
+ }
133
+
134
+ putBitfieldPage (index, page) {
135
+ this.write.tryPut(encodeDataIndex(this.storage.dataPointer, DATA.BITFIELD, index), page)
136
+ }
137
+
138
+ deleteBitfieldPage (index) {
139
+ this.write.tryDelete(encodeDataIndex(this.storage.dataPointer, DATA.BITFIELD, index))
140
+ }
141
+
142
+ deleteBitfieldPageRange (start, end) {
143
+ return this._deleteRange(DATA.BITFIELD, start, end)
144
+ }
145
+
146
+ _deleteRange (type, start, end) {
147
+ const s = encodeDataIndex(this.storage.dataPointer, type, start)
148
+ const e = encodeDataIndex(this.storage.dataPointer, type, end === -1 ? Infinity : end)
149
+
150
+ return this.write.deleteRange(s, e)
151
+ }
152
+
153
+ destroy () {
154
+ this.write.destroy()
155
+ }
156
+
157
+ flush () {
158
+ return this.write.flush()
159
+ }
160
+ }
161
+
162
+ class ReadBatch {
163
+ constructor (storage, read) {
164
+ this.storage = storage
165
+ this.read = read
166
+ }
167
+
168
+ async getCoreHead () {
169
+ return this._get(encodeCoreIndex(this.storage.corePointer, CORE.HEAD, this.storage.dataPointer), m.CoreHead)
170
+ }
171
+
172
+ async getCoreAuth () {
173
+ return this._get(encodeCoreIndex(this.storage.corePointer, CORE.MANIFEST), m.CoreAuth)
174
+ }
175
+
176
+ async getLocalKeyPair () {
177
+ return this._get(encodeCoreIndex(this.storage.corePointer, CORE.LOCAL_SEED), m.KeyPair)
178
+ }
179
+
180
+ async getEncryptionKey () {
181
+ return this._get(encodeCoreIndex(this.storage.corePointer, CORE.ENCRYPTION_KEY), null)
182
+ }
183
+
184
+ getDataInfo (info) {
185
+ return this._get(encodeDataIndex(this.storage.dataPointer, DATA.INFO), m.DataInfo)
186
+ }
187
+
188
+ getUserData (key) {
189
+ return this._get(encodeUserDataIndex(this.storage.dataPointer, DATA.USER_DATA, key), null)
190
+ }
191
+
192
+ async hasBlock (index) {
193
+ return this._has(encodeDataIndex(this.storage.dataPointer, DATA.BLOCK, index))
194
+ }
195
+
196
+ async getBlock (index, error) {
197
+ const dependency = findBlockDependency(this.storage.dependencies, index)
198
+ const dataPointer = dependency !== null ? dependency : this.storage.dataPointer
199
+
200
+ const key = encodeDataIndex(dataPointer, DATA.BLOCK, index)
201
+ const block = await this._get(key, null)
202
+
203
+ if (block === null && error === true) {
204
+ throw new Error('Node not found: ' + index)
205
+ }
206
+
207
+ return block
208
+ }
209
+
210
+ async hasTreeNode (index) {
211
+ return this._has(encodeDataIndex(this.storage.dataPointer, DATA.TREE, index))
212
+ }
213
+
214
+ async getTreeNode (index, error) {
215
+ const dependency = findTreeDependency(this.storage.dependencies, index)
216
+ const dataPointer = dependency !== null ? dependency : this.storage.dataPointer
217
+
218
+ const key = encodeDataIndex(dataPointer, DATA.TREE, index)
219
+ const node = await this._get(key, m.TreeNode)
220
+
221
+ if (node === null && error === true) {
222
+ throw new Error('Node not found: ' + index)
223
+ }
224
+
225
+ return node
226
+ }
227
+
228
+ async getBitfieldPage (index) {
229
+ const key = encodeDataIndex(this.storage.dataPointer, DATA.BITFIELD, index)
230
+ return this._get(key, null)
231
+ }
232
+
233
+ async _has (key) {
234
+ return (await this.read.get(key)) !== null
235
+ }
236
+
237
+ async _get (key, enc) {
238
+ const buffer = await this.read.get(key)
239
+ if (buffer === null) return null
240
+
241
+ if (enc) return c.decode(enc, buffer)
242
+
243
+ return buffer
244
+ }
245
+
246
+ destroy () {
247
+ this.read.destroy()
248
+ }
249
+
250
+ flush () {
251
+ return this.read.flush()
252
+ }
253
+
254
+ tryFlush () {
255
+ this.read.tryFlush()
256
+ }
257
+ }
258
+
259
+ module.exports = class CoreStorage {
260
+ constructor (dir, { autoClose = true } = {}) {
261
+ this.db = new RocksDB(dir)
262
+ this.mutex = new RW()
263
+ this.autoClose = !!autoClose
264
+
265
+ this.sessions = 0
266
+ }
267
+
268
+ // just a helper to make tests easier
269
+ static async clear (dir) {
270
+ const s = new this(dir)
271
+ await s.clear()
272
+ return s
273
+ }
274
+
275
+ async setLocalSeed (seed, overwrite) {
276
+ if (!overwrite) {
277
+ const existing = await getLocalSeed(this.db)
278
+ if (existing) return b4a.equals(existing, seed)
279
+ }
280
+
281
+ await this.mutex.write.lock()
282
+
283
+ try {
284
+ const b = this.db.write()
285
+ b.tryPut(b4a.from([TL.LOCAL_SEED]), seed)
286
+ await b.flush()
287
+
288
+ return true
289
+ } finally {
290
+ this.mutex.write.unlock()
291
+ }
292
+ }
293
+
294
+ getLocalSeed () {
295
+ return getLocalSeed(this.db)
296
+ }
297
+
298
+ info () {
299
+ return getStorageInfo(this.db)
300
+ }
301
+
302
+ list () {
303
+ const s = this.db.iterator({
304
+ gt: b4a.from([TL.DKEYS]),
305
+ lt: b4a.from([TL.DKEYS + 1])
306
+ })
307
+
308
+ s._readableState.map = mapOnlyDiscoveryKey
309
+ return s
310
+ }
311
+
312
+ async idle () {
313
+ if (this.isIdle()) return
314
+
315
+ do {
316
+ await new Promise(setImmediate)
317
+ await this.db.idle()
318
+ await new Promise(setImmediate)
319
+ } while (!this.isIdle())
320
+ }
321
+
322
+ isIdle () {
323
+ return this.db.isIdle()
324
+ }
325
+
326
+ ready () {
327
+ return this.db.ready()
328
+ }
329
+
330
+ close () {
331
+ return this.db.close()
332
+ }
333
+
334
+ async clear () {
335
+ const b = this.db.write()
336
+ b.tryDeleteRange(b4a.from([TL.STORAGE_INFO]), INF)
337
+ await b.flush()
338
+ }
339
+
340
+ async has (discoveryKey) {
341
+ return !!(await this.db.get(encodeDiscoveryKey(discoveryKey)))
342
+ }
343
+
344
+ async resume (discoveryKey) {
345
+ if (!discoveryKey) {
346
+ discoveryKey = await getDefaultKey(this.db)
347
+ if (!discoveryKey) return null
348
+ }
349
+
350
+ const val = await this.db.get(encodeDiscoveryKey(discoveryKey))
351
+ if (val === null) return null
352
+
353
+ const { core, data } = c.decode(m.CorePointer, val)
354
+
355
+ return new HypercoreStorage(this, discoveryKey, core, data, null)
356
+ }
357
+
358
+ async create ({ key, manifest, keyPair, encryptionKey, discoveryKey }) {
359
+ await this.mutex.write.lock()
360
+
361
+ try {
362
+ const existing = await this.resume(discoveryKey)
363
+
364
+ if (existing) {
365
+ // todo: verify key/manifest etc.
366
+ return existing
367
+ }
368
+
369
+ if (!key) throw new Error('No key was provided')
370
+
371
+ let info = await getStorageInfo(this.db)
372
+
373
+ const write = this.db.write()
374
+
375
+ if (!info) {
376
+ write.tryPut(b4a.from([TL.DEFAULT_KEY]), discoveryKey)
377
+ info = { free: 0, total: 0 }
378
+ }
379
+
380
+ const core = info.total++
381
+ const data = info.free++
382
+
383
+ write.tryPut(encodeDiscoveryKey(discoveryKey), encode(m.CorePointer, { core, data }))
384
+ write.tryPut(b4a.from([TL.STORAGE_INFO]), encode(m.StorageInfo, info))
385
+
386
+ const storage = new HypercoreStorage(this, discoveryKey, core, data, null)
387
+ const batch = new WriteBatch(storage, write)
388
+
389
+ initialiseCoreInfo(batch, { key, manifest, keyPair, encryptionKey })
390
+ initialiseCoreData(batch)
391
+
392
+ await batch.flush()
393
+ return storage
394
+ } finally {
395
+ this.mutex.write.unlock()
396
+ }
397
+ }
398
+
399
+ _onclose () {
400
+ if (--this.sessions > 0 || !this.autoClose) return Promise.resolve()
401
+ return this.close()
402
+ }
403
+ }
404
+
405
+ class HypercoreStorage {
406
+ constructor (root, discoveryKey, core, data, snapshot) {
407
+ this.root = root
408
+ this.db = root.db
409
+ this.dbSnapshot = snapshot
410
+ this.mutex = root.mutex
411
+
412
+ this.root.sessions++
413
+
414
+ this.discoveryKey = discoveryKey
415
+
416
+ this.dependencies = []
417
+
418
+ // pointers
419
+ this.corePointer = core
420
+ this.dataPointer = data
421
+
422
+ this.closed = false
423
+ }
424
+
425
+ get snapshotted () {
426
+ return this.dbSnapshot !== null
427
+ }
428
+
429
+ async registerBatch (name, length, overwrite) {
430
+ // todo: make sure opened
431
+ const existing = await this.db.get(encodeBatch(this.corePointer, CORE.BATCHES, name))
432
+ const storage = new HypercoreStorage(this.root, this.discoveryKey, this.corePointer, this.dataPointer, null)
433
+
434
+ if (existing && !overwrite) {
435
+ const dataPointer = c.decode(m.DataPointer, existing)
436
+ storage.dataPointer = dataPointer
437
+ storage.dependencies = await addDependencies(this.db, storage.dataPointer, length)
438
+ return storage
439
+ }
440
+
441
+ await this.mutex.write.lock()
442
+
443
+ try {
444
+ const info = await getStorageInfo(this.db)
445
+
446
+ const write = this.db.write()
447
+
448
+ storage.dataPointer = info.free++
449
+
450
+ write.tryPut(b4a.from([TL.STORAGE_INFO]), encode(m.StorageInfo, info))
451
+
452
+ const batch = new WriteBatch(storage, write)
453
+
454
+ initialiseCoreData(batch)
455
+
456
+ batch.setDataDependency({ data: this.dataPointer, length })
457
+ batch.setBatchPointer(name, storage.dataPointer)
458
+
459
+ await write.flush()
460
+
461
+ storage.dependencies = await addDependencies(this.db, storage.dataPointer, length)
462
+ return storage
463
+ } finally {
464
+ this.mutex.write.unlock()
465
+ }
466
+ }
467
+
468
+ snapshot () {
469
+ assert(this.closed === false)
470
+ return new HypercoreStorage(this.root, this.discoveryKey, this.corePointer, this.dataPointer, this.db.snapshot())
471
+ }
472
+
473
+ createReadBatch (opts) {
474
+ assert(this.closed === false)
475
+
476
+ const snapshot = this.dbSnapshot
477
+ return new ReadBatch(this, this.db.read({ snapshot }))
478
+ }
479
+
480
+ createWriteBatch () {
481
+ assert(this.closed === false)
482
+
483
+ return new WriteBatch(this, this.db.write())
484
+ }
485
+
486
+ createBlockStream (opts = {}) {
487
+ assert(this.closed === false)
488
+ return createStream(this, createBlockStream, opts)
489
+ }
490
+
491
+ createUserDataStream (opts = {}) {
492
+ assert(this.closed === false)
493
+
494
+ const r = encodeIndexRange(this.dataPointer, DATA.USER_DATA, this.dbSnapshot, opts)
495
+ const s = this.db.iterator(r)
496
+ s._readableState.map = mapStreamUserData
497
+ return s
498
+ }
499
+
500
+ createTreeNodeStream (opts = {}) {
501
+ assert(this.closed === false)
502
+
503
+ const r = encodeIndexRange(this.dataPointer, DATA.TREE, this.dbSnapshot, opts)
504
+ const s = this.db.iterator(r)
505
+ s._readableState.map = mapStreamTreeNode
506
+ return s
507
+ }
508
+
509
+ createBitfieldPageStream (opts = {}) {
510
+ assert(this.closed === false)
511
+
512
+ const r = encodeIndexRange(this.dataPointer, DATA.BITFIELD, this.dbSnapshot, opts)
513
+ const s = this.db.iterator(r)
514
+ s._readableState.map = mapStreamBitfieldPage
515
+ return s
516
+ }
517
+
518
+ async peakLastTreeNode () {
519
+ assert(this.closed === false)
520
+
521
+ const last = await this.db.peek(encodeIndexRange(this.dataPointer, DATA.TREE, this.dbSnapshot, { reverse: true }))
522
+ if (last === null) return null
523
+ return c.decode(m.TreeNode, last.value)
524
+ }
525
+
526
+ async peakLastBitfieldPage () {
527
+ assert(this.closed === false)
528
+
529
+ const last = await this.db.peek(encodeIndexRange(this.dataPointer, DATA.BITFIELD, this.dbSnapshot, { reverse: true }))
530
+ if (last === null) return null
531
+ return mapStreamBitfieldPage(last)
532
+ }
533
+
534
+ close () {
535
+ if (this.closed) return Promise.resolve()
536
+ this.closed = true
537
+
538
+ if (this.dbSnapshot) this.dbSnapshot.destroy()
539
+ this.dbSnapshot = null
540
+
541
+ return this.root._onclose()
542
+ }
543
+ }
544
+
545
+ function createStream (storage, createStreamType, opts) {
546
+ return storage.dependencies.length === 0
547
+ ? createStreamType(storage.db, storage.dbSnapshot, storage.dataPointer, opts)
548
+ : new DependencyStream(storage, createStreamType, opts)
549
+ }
550
+
551
+ function createBlockStream (db, snap, data, opts) {
552
+ const r = encodeIndexRange(data, DATA.BLOCK, snap, opts)
553
+ const s = db.iterator(r)
554
+ s._readableState.map = mapStreamBlock
555
+ return s
556
+ }
557
+
558
+ function mapStreamUserData (data) {
559
+ const state = { start: 0, end: data.key.byteLength, buffer: data.key }
560
+
561
+ UINT.decode(state) // TL.DATA
562
+ UINT.decode(state) // pointer
563
+ UINT.decode(state) // DATA.USER_DATA
564
+
565
+ const key = c.string.decode(state)
566
+
567
+ return { key, value: data.value }
568
+ }
569
+
570
+ function mapStreamTreeNode (data) {
571
+ return c.decode(m.TreeNode, data.value)
572
+ }
573
+
574
+ function mapStreamBitfieldPage (data) {
575
+ const state = { start: 0, end: data.key.byteLength, buffer: data.key }
576
+
577
+ UINT.decode(state) // TL.DATA
578
+ UINT.decode(state) // pointer
579
+ UINT.decode(state) // DATA.BITFIELD
580
+
581
+ const index = UINT.decode(state)
582
+
583
+ return { index, page: data.value }
584
+ }
585
+
586
+ function mapStreamBlock (data) {
587
+ const state = { start: 0, end: data.key.byteLength, buffer: data.key }
588
+
589
+ UINT.decode(state) // TL.DATA
590
+ UINT.decode(state) // pointer
591
+ UINT.decode(state) // DATA.BITFIELD
592
+
593
+ const index = UINT.decode(state)
594
+ return { index, value: data.value }
595
+ }
596
+
597
+ function mapOnlyDiscoveryKey (data) {
598
+ return data.key.subarray(1)
599
+ }
600
+
601
+ async function getDefaultKey (db) {
602
+ return db.get(b4a.from([TL.DEFAULT_KEY]))
603
+ }
604
+
605
+ async function getLocalSeed (db) {
606
+ return db.get(b4a.from([TL.LOCAL_SEED]))
607
+ }
608
+
609
+ async function getStorageInfo (db) {
610
+ const value = await db.get(b4a.from([TL.STORAGE_INFO]))
611
+ if (value === null) return null
612
+ return c.decode(m.StorageInfo, value)
613
+ }
614
+
615
+ function ensureSlab (size) {
616
+ if (SLAB.buffer.byteLength - SLAB.start < size) {
617
+ SLAB.buffer = b4a.allocUnsafe(SLAB.end)
618
+ SLAB.start = 0
619
+ }
620
+
621
+ return SLAB
622
+ }
623
+
624
+ function encodeIndexRange (pointer, type, snapshot, opts) {
625
+ const bounded = { snapshot, gt: null, gte: null, lte: null, lt: null, reverse: !!opts.reverse, limit: toLimit(opts.limit) }
626
+
627
+ if (opts.gt || opts.gt === 0) bounded.gt = encodeDataIndex(pointer, type, opts.gt)
628
+ else if (opts.gte) bounded.gte = encodeDataIndex(pointer, type, opts.gte)
629
+ else bounded.gte = encodeDataIndex(pointer, type, 0)
630
+
631
+ if (opts.lt || opts.lt === 0) bounded.lt = encodeDataIndex(pointer, type, opts.lt)
632
+ else if (opts.lte) bounded.lte = encodeDataIndex(pointer, type, opts.lte)
633
+ else bounded.lte = encodeDataIndex(pointer, type, Infinity) // infinity
634
+
635
+ return bounded
636
+ }
637
+
638
+ function toLimit (n) {
639
+ return n === 0 ? 0 : (n || Infinity)
640
+ }
641
+
642
+ function encode (encoding, value) {
643
+ const state = ensureSlab(128)
644
+ const start = state.start
645
+ encoding.encode(state, value)
646
+
647
+ assert(state.start <= state.end)
648
+
649
+ return state.buffer.subarray(start, state.start)
650
+ }
651
+
652
+ function encodeBatch (pointer, type, name) {
653
+ const end = 128 + name.length
654
+ const state = { start: 0, end, buffer: b4a.allocUnsafe(end) }
655
+ const start = state.start
656
+ UINT.encode(state, TL.CORE)
657
+ UINT.encode(state, pointer)
658
+ UINT.encode(state, type)
659
+ c.string.encode(state, name)
660
+
661
+ return state.buffer.subarray(start, state.start)
662
+ }
663
+
664
+ function encodeCoreIndex (pointer, type, index) {
665
+ const state = ensureSlab(128)
666
+ const start = state.start
667
+ UINT.encode(state, TL.CORE)
668
+ UINT.encode(state, pointer)
669
+ UINT.encode(state, type)
670
+ if (index !== undefined) UINT.encode(state, index)
671
+
672
+ return state.buffer.subarray(start, state.start)
673
+ }
674
+
675
+ function encodeDataIndex (pointer, type, index) {
676
+ const state = ensureSlab(128)
677
+ const start = state.start
678
+ UINT.encode(state, TL.DATA)
679
+ UINT.encode(state, pointer)
680
+ UINT.encode(state, type)
681
+ if (index !== undefined) UINT.encode(state, index)
682
+
683
+ return state.buffer.subarray(start, state.start)
684
+ }
685
+
686
+ function encodeUserDataIndex (pointer, type, key) {
687
+ const end = 128 + key.length
688
+ const state = { start: 0, end, buffer: b4a.allocUnsafe(end) }
689
+ const start = state.start
690
+ UINT.encode(state, TL.DATA)
691
+ UINT.encode(state, pointer)
692
+ UINT.encode(state, type)
693
+ c.string.encode(state, key)
694
+
695
+ return state.buffer.subarray(start, state.start)
696
+ }
697
+
698
+ function encodeDiscoveryKey (discoveryKey) {
699
+ const state = ensureSlab(128)
700
+ const start = state.start
701
+ UINT.encode(state, TL.DKEYS)
702
+ c.fixed32.encode(state, discoveryKey)
703
+ return state.buffer.subarray(start, state.start)
704
+ }
705
+
706
+ async function addDependencies (db, dataPointer, treeLength) {
707
+ const dependencies = []
708
+
709
+ let dep = await db.get(encodeDataIndex(dataPointer, DATA.DEPENDENCY))
710
+ while (dep) {
711
+ const { data, length } = c.decode(m.DataDependency, dep)
712
+ if (length <= treeLength) dependencies.push({ data, length })
713
+
714
+ dep = await db.get(encodeDataIndex(data, DATA.DEPENDENCY))
715
+ }
716
+
717
+ return dependencies
718
+ }
719
+
720
+ function findBlockDependency (dependencies, index) {
721
+ for (const { data, length } of dependencies) {
722
+ if (index < length) return data
723
+ }
724
+ return null
725
+ }
726
+
727
+ function findTreeDependency (dependencies, index) {
728
+ for (const { data, length } of dependencies) {
729
+ if (flat.rightSpan(index) <= (length - 1) * 2) return data
730
+ }
731
+ return null
732
+ }
733
+
734
+ function initialiseCoreInfo (db, { key, manifest, keyPair, encryptionKey }) {
735
+ db.setCoreAuth({ key, manifest })
736
+ if (keyPair) db.setLocalKeyPair(keyPair)
737
+ if (encryptionKey) db.setEncryptionKey(encryptionKey)
738
+ }
739
+
740
+ function initialiseCoreData (db) {
741
+ db.setDataInfo({ version: 0 })
742
+ }
@@ -0,0 +1,111 @@
1
+ const { Readable, isEnded, getStreamError } = require('streamx')
2
+
3
+ module.exports = class DependencyStream extends Readable {
4
+ constructor (storage, createStream, opts = {}) {
5
+ super()
6
+
7
+ this.storage = storage
8
+ this.createStream = createStream
9
+
10
+ let max = 0
11
+
12
+ const reverse = !!opts.reverse
13
+ const limit = opts.limit === 0 ? 0 : (opts.limit || Infinity)
14
+ const gte = typeof opts.gte === 'number' ? opts.gte : typeof opts.gt === 'number' ? opts.gt + 1 : 0
15
+ const lt = typeof opts.lt === 'number' ? opts.lt : typeof opts.lte === 'number' ? opts.lte + 1 : Infinity
16
+
17
+ const streams = []
18
+
19
+ for (let i = 0; i < storage.dependencies.length; i++) {
20
+ const min = max
21
+ max += storage.dependencies[i].length
22
+
23
+ streams.push({
24
+ data: storage.dependencies[i].data,
25
+ gte: Math.max(gte, min),
26
+ lt: Math.min(lt, max)
27
+ })
28
+ }
29
+
30
+ streams.push({
31
+ data: storage.dataPointer,
32
+ gte: Math.max(gte, max),
33
+ lt
34
+ })
35
+
36
+ this._streams = reverse ? streams.reverse() : streams
37
+ this._reverse = reverse
38
+ this._limit = limit
39
+ this._next = 0
40
+ this._active = null
41
+ this._pendingDestroy = null
42
+ this._ondataBound = this._ondata.bind(this)
43
+ this._oncloseBound = this._onclose.bind(this)
44
+
45
+ this._nextStream()
46
+ }
47
+
48
+ _read (cb) {
49
+ this._active.resume()
50
+ cb(null)
51
+ }
52
+
53
+ _predestroy () {
54
+ if (this._active) this._active.destroy()
55
+ }
56
+
57
+ _destroy (cb) {
58
+ if (this._active === null) cb(null)
59
+ else this._pendingDestroy = cb
60
+ }
61
+
62
+ _ondata (data) {
63
+ if (this._limit > 0) this._limit--
64
+ if (this.push(data) === false) this._active.pause()
65
+ }
66
+
67
+ _onclose () {
68
+ if (!isEnded(this._active)) {
69
+ const error = getStreamError(this._active)
70
+ this._active = null
71
+ this.destroy(error)
72
+ this._continueDestroy(error)
73
+ return
74
+ }
75
+
76
+ if (this._next >= this._streams.length || this._limit === 0) {
77
+ this._active = null
78
+ this.push(null)
79
+ this._continueDestroy(null)
80
+ return
81
+ }
82
+
83
+ this._nextStream()
84
+ }
85
+
86
+ _continueDestroy (err) {
87
+ if (this._pendingDestroy === null) return
88
+ const cb = this._pendingDestroy
89
+ this._pendingDestroy = null
90
+ cb(err)
91
+ }
92
+
93
+ _nextStream () {
94
+ const { data, gte, lt } = this._streams[this._next++]
95
+
96
+ const stream = this.createStream(this.storage.db, this.storage.dbSnapshot, data, {
97
+ reverse: this._reverse,
98
+ limit: this._limit,
99
+ gte,
100
+ lt
101
+ })
102
+
103
+ this._active = stream
104
+
105
+ stream.on('data', this._ondataBound)
106
+ stream.on('error', noop) // handled in onclose
107
+ stream.on('close', this._oncloseBound)
108
+ }
109
+ }
110
+
111
+ function noop () {}
@@ -0,0 +1,190 @@
1
+ const c = require('compact-encoding')
2
+
3
+ exports.DataPointer = c.uint
4
+
5
+ exports.KeyPair = {
6
+ preencode (state, m) {
7
+ c.uint.preencode(state, m.secretKey ? 1 : 0)
8
+ c.fixed32.preencode(state, m.publicKey)
9
+ if (m.secretKey) c.fixed64.preencode(state, m.secretKey)
10
+ },
11
+ encode (state, m) {
12
+ c.uint.encode(state, m.secretKey ? 1 : 0)
13
+ c.fixed32.encode(state, m.publicKey)
14
+ if (m.secretKey) c.fixed64.encode(state, m.secretKey)
15
+ },
16
+ decode (state) {
17
+ const flags = c.uint.decode(state)
18
+
19
+ return {
20
+ publicKey: c.fixed32.decode(state),
21
+ secretKey: flags & 1 ? c.fixed64.decode(state) : null
22
+ }
23
+ }
24
+ }
25
+
26
+ exports.StorageInfo = {
27
+ preencode (state, m) {
28
+ c.uint.preencode(state, m.version)
29
+ c.uint.preencode(state, 0) // flags, reserved
30
+ c.uint.preencode(state, m.free)
31
+ c.uint.preencode(state, m.total)
32
+ },
33
+ encode (state, m) {
34
+ c.uint.encode(state, m.version)
35
+ c.uint.encode(state, 0) // flags, reserved
36
+ c.uint.encode(state, m.free)
37
+ c.uint.encode(state, m.total)
38
+ },
39
+ decode (state, m) {
40
+ const v = c.uint.decode(state)
41
+ if (v !== 0) throw new Error('Invalid version: ' + v)
42
+
43
+ c.uint.decode(state) // flags, ignore
44
+
45
+ return {
46
+ free: c.uint.decode(state),
47
+ total: c.uint.decode(state)
48
+ }
49
+ }
50
+ }
51
+
52
+ exports.CorePointer = {
53
+ preencode (state, m) {
54
+ c.uint.preencode(state, m.core)
55
+ c.uint.preencode(state, m.data)
56
+ },
57
+ encode (state, m) {
58
+ c.uint.encode(state, m.core)
59
+ c.uint.encode(state, m.data)
60
+ },
61
+ decode (state) {
62
+ return {
63
+ core: c.uint.decode(state),
64
+ data: c.uint.decode(state)
65
+ }
66
+ }
67
+ }
68
+
69
+ exports.CoreHead = {
70
+ preencode (state, m) {
71
+ c.uint.preencode(state, m.fork)
72
+ c.uint.preencode(state, m.length)
73
+ c.fixed32.preencode(state, m.rootHash)
74
+ c.buffer.preencode(state, m.signature)
75
+ },
76
+ encode (state, m) {
77
+ c.uint.encode(state, m.fork)
78
+ c.uint.encode(state, m.length)
79
+ c.fixed32.encode(state, m.rootHash)
80
+ c.buffer.encode(state, m.signature)
81
+ },
82
+ decode (state) {
83
+ return {
84
+ fork: c.uint.decode(state),
85
+ length: c.uint.decode(state),
86
+ rootHash: c.fixed32.decode(state),
87
+ signature: c.buffer.decode(state)
88
+ }
89
+ }
90
+ }
91
+
92
+ exports.TreeNode = {
93
+ preencode (state, m) {
94
+ c.uint.preencode(state, m.index)
95
+ c.uint.preencode(state, m.size)
96
+ c.fixed32.preencode(state, m.hash)
97
+ },
98
+ encode (state, m) {
99
+ c.uint.encode(state, m.index)
100
+ c.uint.encode(state, m.size)
101
+ c.fixed32.encode(state, m.hash)
102
+ },
103
+ decode (state) {
104
+ return {
105
+ index: c.uint.decode(state),
106
+ size: c.uint.decode(state),
107
+ hash: c.fixed32.decode(state)
108
+ }
109
+ }
110
+ }
111
+
112
+ exports.CoreAuth = {
113
+ preencode (state, m) {
114
+ c.uint.preencode(state, m.manifest ? 1 : 0)
115
+ c.fixed32.preencode(state, m.key)
116
+ if (m.manifest) c.buffer.preencode(state, m.manifest)
117
+ },
118
+ encode (state, m) {
119
+ c.uint.encode(state, m.manifest ? 1 : 0)
120
+ c.fixed32.encode(state, m.key)
121
+ if (m.manifest) c.buffer.encode(state, m.manifest)
122
+ },
123
+ decode (state) {
124
+ const flags = c.uint.decode(state)
125
+
126
+ return {
127
+ key: c.fixed32.decode(state),
128
+ manifest: flags & 1 ? c.buffer.decode(state) : null
129
+ }
130
+ }
131
+ }
132
+
133
+ exports.DataDependency = {
134
+ preencode (state, m) {
135
+ c.uint.preencode(state, m.data)
136
+ c.uint.preencode(state, m.length)
137
+ },
138
+ encode (state, m) {
139
+ c.uint.encode(state, m.data)
140
+ c.uint.encode(state, m.length)
141
+ },
142
+ decode (state) {
143
+ return {
144
+ data: c.uint.decode(state),
145
+ length: c.uint.decode(state)
146
+ }
147
+ }
148
+ }
149
+
150
+ exports.CoreSeed = {
151
+ preencode (state, m) {
152
+ c.fixed32.preencode(state, m.seed)
153
+ },
154
+ encode (state, m) {
155
+ c.fixed32.encode(state, m.seed)
156
+ },
157
+ decode (state) {
158
+ return {
159
+ seed: c.fixed32.decode(state)
160
+ }
161
+ }
162
+ }
163
+
164
+ exports.CoreEncryptionKey = {
165
+ preencode (state, m) {
166
+ c.fixed32.preencode(state, m.encryptionKey)
167
+ },
168
+ encode (state, m) {
169
+ c.fixed32.encode(state, m.encryptionKey)
170
+ },
171
+ decode (state) {
172
+ return {
173
+ encryptionKey: c.fixed32.decode(state)
174
+ }
175
+ }
176
+ }
177
+
178
+ exports.DataInfo = {
179
+ preencode (state, m) {
180
+ c.uint.preencode(state, m.version)
181
+ },
182
+ encode (state, m) {
183
+ c.uint.encode(state, m.version)
184
+ },
185
+ decode (state) {
186
+ return {
187
+ version: c.uint.decode(state)
188
+ }
189
+ }
190
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "hypercore-storage",
3
+ "version": "0.0.21",
4
+ "main": "index.js",
5
+ "files": [
6
+ "index.js",
7
+ "lib/*.js"
8
+ ],
9
+ "scripts": {
10
+ "test": "standard && brittle test/*.js"
11
+ },
12
+ "author": "Holepunch",
13
+ "license": "Apache-2.0",
14
+ "description": "RocksDB storage driver for Hypercore",
15
+ "dependencies": {
16
+ "b4a": "^1.6.6",
17
+ "compact-encoding": "^2.15.0",
18
+ "flat-tree": "^1.10.0",
19
+ "index-encoder": "^3.0.1",
20
+ "nanoassert": "^2.0.0",
21
+ "read-write-mutexify": "^2.1.0",
22
+ "rocksdb-native": "^2.2.0",
23
+ "streamx": "^2.20.1"
24
+ },
25
+ "devDependencies": {
26
+ "brittle": "^3.5.2",
27
+ "standard": "^17.1.0",
28
+ "test-tmp": "^1.2.1"
29
+ }
30
+ }