hypercore 10.0.0-alpha.9 → 10.2.0

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/lib/core.js CHANGED
@@ -1,13 +1,15 @@
1
1
  const hypercoreCrypto = require('hypercore-crypto')
2
+ const b4a = require('b4a')
2
3
  const Oplog = require('./oplog')
3
4
  const Mutex = require('./mutex')
4
5
  const MerkleTree = require('./merkle-tree')
5
6
  const BlockStore = require('./block-store')
6
7
  const Bitfield = require('./bitfield')
7
- const { oplogHeader, oplogEntry } = require('./messages')
8
+ const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_SIGNATURE } = require('./errors')
9
+ const m = require('./messages')
8
10
 
9
11
  module.exports = class Core {
10
- constructor (header, crypto, oplog, tree, blocks, bitfield, sign, onupdate) {
12
+ constructor (header, crypto, oplog, tree, blocks, bitfield, auth, legacy, onupdate) {
11
13
  this.onupdate = onupdate
12
14
  this.header = header
13
15
  this.crypto = crypto
@@ -15,7 +17,7 @@ module.exports = class Core {
15
17
  this.tree = tree
16
18
  this.blocks = blocks
17
19
  this.bitfield = bitfield
18
- this.defaultSign = sign
20
+ this.defaultAuth = auth
19
21
  this.truncating = 0
20
22
 
21
23
  this._maxOplogSize = 65536
@@ -23,6 +25,7 @@ module.exports = class Core {
23
25
  this._verifies = null
24
26
  this._verifiesFlushed = null
25
27
  this._mutex = new Mutex()
28
+ this._legacy = legacy
26
29
  }
27
30
 
28
31
  static async open (storage, opts = {}) {
@@ -34,42 +37,51 @@ module.exports = class Core {
34
37
  try {
35
38
  return await this.resume(oplogFile, treeFile, bitfieldFile, dataFile, opts)
36
39
  } catch (err) {
37
- return new Promise((resolve, reject) => {
38
- let missing = 4
39
-
40
- oplogFile.close(done)
41
- treeFile.close(done)
42
- bitfieldFile.close(done)
43
- dataFile.close(done)
44
-
45
- function done () {
46
- if (--missing === 0) reject(err)
47
- }
48
- })
40
+ await closeAll(oplogFile, treeFile, bitfieldFile, dataFile)
41
+ throw err
49
42
  }
50
43
  }
51
44
 
52
- // TODO: we should prob have a general "auth" abstraction instead somewhere?
53
- static createSigner (crypto, { publicKey, secretKey }) {
54
- if (!crypto.validateKeyPair({ publicKey, secretKey })) throw new Error('Invalid key pair')
55
- return signable => crypto.sign(signable, secretKey)
45
+ static createAuth (crypto, { publicKey, secretKey }, opts = {}) {
46
+ if (secretKey && !crypto.validateKeyPair({ publicKey, secretKey })) {
47
+ throw BAD_ARGUMENT('Invalid key pair')
48
+ }
49
+
50
+ const sign = opts.sign
51
+ ? opts.sign
52
+ : secretKey
53
+ ? (signable) => crypto.sign(signable, secretKey)
54
+ : undefined
55
+
56
+ return {
57
+ sign,
58
+ verify (signable, signature) {
59
+ return crypto.verify(signable, signature, publicKey)
60
+ }
61
+ }
56
62
  }
57
63
 
58
64
  static async resume (oplogFile, treeFile, bitfieldFile, dataFile, opts) {
59
- const overwrite = opts.overwrite === true
65
+ let overwrite = opts.overwrite === true
66
+
67
+ const force = opts.force === true
60
68
  const createIfMissing = opts.createIfMissing !== false
61
69
  const crypto = opts.crypto || hypercoreCrypto
62
70
 
63
71
  const oplog = new Oplog(oplogFile, {
64
- headerEncoding: oplogHeader,
65
- entryEncoding: oplogEntry
72
+ headerEncoding: m.oplog.header,
73
+ entryEncoding: m.oplog.entry
66
74
  })
67
75
 
68
76
  let { header, entries } = await oplog.open()
69
77
 
70
- if (!header || overwrite === true) {
78
+ if (force && opts.keyPair && header && header.signer && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
79
+ overwrite = true
80
+ }
81
+
82
+ if (!header || overwrite) {
71
83
  if (!createIfMissing) {
72
- throw new Error('No hypercore is stored here')
84
+ throw STORAGE_EMPTY('No Hypercore is stored here')
73
85
  }
74
86
 
75
87
  header = {
@@ -84,14 +96,15 @@ module.exports = class Core {
84
96
  signer: opts.keyPair || crypto.keyPair(),
85
97
  hints: {
86
98
  reorgs: []
87
- }
99
+ },
100
+ contiguousLength: 0
88
101
  }
89
102
 
90
103
  await oplog.flush(header)
91
104
  }
92
105
 
93
- if (opts.keyPair && !header.signer.publicKey.equals(opts.keyPair.publicKey)) {
94
- throw new Error('Another hypercore is stored here')
106
+ if (opts.keyPair && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
107
+ throw STORAGE_CONFLICT('Another Hypercore is stored here')
95
108
  }
96
109
 
97
110
  const tree = await MerkleTree.open(treeFile, { crypto, ...header.tree })
@@ -102,9 +115,18 @@ module.exports = class Core {
102
115
  await tree.clear()
103
116
  await blocks.clear()
104
117
  await bitfield.clear()
118
+ entries = []
119
+ } else if (bitfield.resumed && header.tree.length === 0) {
120
+ // If this was an old bitfield, reset it since it loads based on disk size atm (TODO: change that)
121
+ await bitfield.clear()
105
122
  }
106
123
 
107
- const sign = opts.sign || (header.signer.secretKey ? this.createSigner(crypto, header.signer) : null)
124
+ // compat from earlier version that do not store contig length
125
+ if (header.contiguousLength === 0) {
126
+ while (bitfield.get(header.contiguousLength)) header.contiguousLength++
127
+ }
128
+
129
+ const auth = opts.auth || this.createAuth(crypto, header.signer)
108
130
 
109
131
  for (const e of entries) {
110
132
  if (e.userData) {
@@ -118,7 +140,8 @@ module.exports = class Core {
118
140
  }
119
141
 
120
142
  if (e.bitfield) {
121
- bitfield.setRange(e.bitfield.start, e.bitfield.length)
143
+ bitfield.setRange(e.bitfield.start, e.bitfield.length, !e.bitfield.drop)
144
+ updateContig(header, e.bitfield, bitfield)
122
145
  }
123
146
 
124
147
  if (e.treeUpgrade) {
@@ -135,7 +158,7 @@ module.exports = class Core {
135
158
  }
136
159
  }
137
160
 
138
- return new this(header, crypto, oplog, tree, blocks, bitfield, sign, opts.onupdate || noop)
161
+ return new this(header, crypto, oplog, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop)
139
162
  }
140
163
 
141
164
  _shouldFlush () {
@@ -176,7 +199,7 @@ module.exports = class Core {
176
199
 
177
200
  for (const u of this.header.userData) {
178
201
  if (u.key !== key) continue
179
- if (value && u.value.equals(value)) return
202
+ if (value && b4a.equals(u.value, value)) return
180
203
  empty = false
181
204
  break
182
205
  }
@@ -200,13 +223,13 @@ module.exports = class Core {
200
223
  }
201
224
  }
202
225
 
203
- async truncate (length, fork, sign = this.defaultSign) {
226
+ async truncate (length, fork, auth = this.defaultAuth) {
204
227
  this.truncating++
205
228
  await this._mutex.lock()
206
229
 
207
230
  try {
208
231
  const batch = await this.tree.truncate(length, fork)
209
- batch.signature = await sign(batch.signable())
232
+ batch.signature = await auth.sign(batch.signable())
210
233
  await this._truncate(batch, null)
211
234
  } finally {
212
235
  this.truncating--
@@ -214,19 +237,63 @@ module.exports = class Core {
214
237
  }
215
238
  }
216
239
 
217
- async append (values, sign = this.defaultSign, hooks = {}) {
240
+ async clear (start, end) {
241
+ await this._mutex.lock()
242
+
243
+ const entry = {
244
+ userData: null,
245
+ treeNodes: null,
246
+ treeUpgrade: null,
247
+ bitfield: {
248
+ start,
249
+ length: end - start,
250
+ drop: true
251
+ }
252
+ }
253
+
254
+ await this.oplog.append([entry], false)
255
+
256
+ this.bitfield.setRange(start, end - start, false)
257
+
258
+ if (start < this.header.contiguousLength) {
259
+ this.header.contiguousLength = start
260
+ }
261
+
262
+ start = this.bitfield.lastSet(start) + 1
263
+ end = this.bitfield.firstSet(end)
264
+
265
+ if (end === -1) end = this.tree.length
266
+
267
+ try {
268
+ const offset = await this.tree.byteOffset(start * 2)
269
+ const [byteEnd, byteEndLength] = await this.tree.byteRange((end - 1) * 2)
270
+ const length = (byteEnd + byteEndLength) - offset
271
+
272
+ await this.blocks.clear(offset, length)
273
+
274
+ this.onupdate(0, entry.bitfield, null, null)
275
+
276
+ if (this._shouldFlush()) await this._flushOplog()
277
+ } finally {
278
+ this._mutex.unlock()
279
+ }
280
+ }
281
+
282
+ async append (values, auth = this.defaultAuth, hooks = {}) {
218
283
  await this._mutex.lock()
219
284
 
220
285
  try {
221
286
  if (hooks.preappend) await hooks.preappend(values)
222
287
 
223
- if (!values.length) return this.tree.length
288
+ if (!values.length) {
289
+ return { length: this.tree.length, byteLength: this.tree.byteLength }
290
+ }
224
291
 
225
292
  const batch = this.tree.batch()
226
293
  for (const val of values) batch.append(val)
227
294
 
228
295
  const hash = batch.hash()
229
- batch.signature = await sign(batch.signable(hash))
296
+ batch.signature = await auth.sign(this._legacy ? batch.signableLegacy(hash) : batch.signable(hash))
230
297
 
231
298
  const entry = {
232
299
  userData: null,
@@ -239,7 +306,8 @@ module.exports = class Core {
239
306
  }
240
307
  }
241
308
 
242
- await this._appendBlocks(values)
309
+ const byteLength = await this._appendBlocks(values)
310
+
243
311
  await this.oplog.append([entry], false)
244
312
 
245
313
  this.bitfield.setRange(batch.ancestors, batch.length - batch.ancestors, true)
@@ -248,21 +316,28 @@ module.exports = class Core {
248
316
  this.header.tree.length = batch.length
249
317
  this.header.tree.rootHash = hash
250
318
  this.header.tree.signature = batch.signature
251
- this.onupdate(0b01, entry.bitfield, null, null)
319
+
320
+ const status = 0b0001 | updateContig(this.header, entry.bitfield, this.bitfield)
321
+ this.onupdate(status, entry.bitfield, null, null)
252
322
 
253
323
  if (this._shouldFlush()) await this._flushOplog()
254
324
 
255
- return batch.ancestors
325
+ return { length: batch.length, byteLength }
256
326
  } finally {
257
327
  this._mutex.unlock()
258
328
  }
259
329
  }
260
330
 
331
+ _signed (batch, hash, auth = this.defaultAuth) {
332
+ const signable = this._legacy ? batch.signableLegacy(hash) : batch.signable(hash)
333
+ return auth.verify(signable, batch.signature)
334
+ }
335
+
261
336
  async _verifyExclusive ({ batch, bitfield, value, from }) {
262
337
  // TODO: move this to tree.js
263
338
  const hash = batch.hash()
264
- if (!batch.signature || !this.crypto.verify(batch.signable(hash), batch.signature, this.header.signer.publicKey)) {
265
- throw new Error('Remote signature does not match')
339
+ if (!batch.signature || !this._signed(batch, hash)) {
340
+ throw INVALID_SIGNATURE('Proof contains an invalid signature')
266
341
  }
267
342
 
268
343
  await this._mutex.lock()
@@ -281,14 +356,21 @@ module.exports = class Core {
281
356
 
282
357
  await this.oplog.append([entry], false)
283
358
 
284
- if (bitfield) this.bitfield.set(bitfield.start, true)
359
+ let status = 0b0001
360
+
361
+ if (bitfield) {
362
+ this.bitfield.set(bitfield.start, true)
363
+ status |= updateContig(this.header, bitfield, this.bitfield)
364
+ }
365
+
285
366
  batch.commit()
286
367
 
287
368
  this.header.tree.fork = batch.fork
288
369
  this.header.tree.length = batch.length
289
370
  this.header.tree.rootHash = batch.rootHash
290
371
  this.header.tree.signature = batch.signature
291
- this.onupdate(0b01, bitfield, value, from)
372
+
373
+ this.onupdate(status, bitfield, value, from)
292
374
 
293
375
  if (this._shouldFlush()) await this._flushOplog()
294
376
  } finally {
@@ -335,9 +417,16 @@ module.exports = class Core {
335
417
  continue
336
418
  }
337
419
 
338
- if (bitfield) this.bitfield.set(bitfield.start, true)
420
+ let status = 0
421
+
422
+ if (bitfield) {
423
+ this.bitfield.set(bitfield.start, true)
424
+ status = updateContig(this.header, bitfield, this.bitfield)
425
+ }
426
+
339
427
  batch.commit()
340
- this.onupdate(0, bitfield, value, from)
428
+
429
+ this.onupdate(status, bitfield, value, from)
341
430
  }
342
431
 
343
432
  if (this._shouldFlush()) await this._flushOplog()
@@ -415,13 +504,15 @@ module.exports = class Core {
415
504
  addReorgHint(this.header.hints.reorgs, this.tree, batch)
416
505
  batch.commit()
417
506
 
418
- const appended = batch.length > batch.ancestors
507
+ const contigStatus = updateContig(this.header, entry.bitfield, this.bitfield)
508
+ const status = ((batch.length > batch.ancestors) ? 0b0011 : 0b0010) | contigStatus
419
509
 
420
510
  this.header.tree.fork = batch.fork
421
511
  this.header.tree.length = batch.length
422
512
  this.header.tree.rootHash = batch.hash()
423
513
  this.header.tree.signature = batch.signature
424
- this.onupdate(appended ? 0b11 : 0b10, entry.bitfield, null, from)
514
+
515
+ this.onupdate(status, entry.bitfield, null, from)
425
516
 
426
517
  // TODO: there is a bug in the merkle tree atm where it cannot handle unflushed
427
518
  // truncates if we append or download anything after the truncation point later on
@@ -442,6 +533,36 @@ module.exports = class Core {
442
533
  }
443
534
  }
444
535
 
536
+ function updateContig (header, upd, bitfield) {
537
+ const end = upd.start + upd.length
538
+
539
+ let c = header.contiguousLength
540
+
541
+ if (upd.drop) {
542
+ // If we dropped a block in the current contig range, "downgrade" it
543
+ if (c <= end && c > upd.start) {
544
+ c = upd.start
545
+ }
546
+ } else {
547
+ if (c <= end && c >= upd.start) {
548
+ c = end
549
+ while (bitfield.get(c)) c++
550
+ }
551
+ }
552
+
553
+ if (c === header.contiguousLength) {
554
+ return 0b0000
555
+ }
556
+
557
+ if (c > header.contiguousLength) {
558
+ header.contiguousLength = c
559
+ return 0b0100
560
+ }
561
+
562
+ header.contiguousLength = c
563
+ return 0b1000
564
+ }
565
+
445
566
  function addReorgHint (list, tree, batch) {
446
567
  if (tree.length === 0 || tree.fork === batch.fork) return
447
568
 
@@ -465,4 +586,25 @@ function updateUserData (list, key, value) {
465
586
  if (value) list.push({ key, value })
466
587
  }
467
588
 
589
+ function closeAll (...storages) {
590
+ let missing = 1
591
+ let error = null
592
+
593
+ return new Promise((resolve, reject) => {
594
+ for (const s of storages) {
595
+ missing++
596
+ s.close(done)
597
+ }
598
+
599
+ done(null)
600
+
601
+ function done (err) {
602
+ if (err) error = err
603
+ if (--missing) return
604
+ if (error) reject(error)
605
+ else resolve()
606
+ }
607
+ })
608
+ }
609
+
468
610
  function noop () {}
@@ -0,0 +1,22 @@
1
+ module.exports = class Download {
2
+ constructor (req) {
3
+ this.req = req
4
+ }
5
+
6
+ async done () {
7
+ return (await this.req).promise
8
+ }
9
+
10
+ /**
11
+ * Deprecated. Use `range.done()`.
12
+ */
13
+ downloaded () {
14
+ return this.done()
15
+ }
16
+
17
+ destroy () {
18
+ this.req.then(req => req.context && req.context.detach(req), noop)
19
+ }
20
+ }
21
+
22
+ function noop () {}
package/lib/errors.js ADDED
@@ -0,0 +1,50 @@
1
+ module.exports = class HypercoreError extends Error {
2
+ constructor (msg, code, fn = HypercoreError) {
3
+ super(`${code}: ${msg}`)
4
+ this.code = code
5
+
6
+ if (Error.captureStackTrace) {
7
+ Error.captureStackTrace(this, fn)
8
+ }
9
+ }
10
+
11
+ get name () {
12
+ return 'HypercoreError'
13
+ }
14
+
15
+ static BAD_ARGUMENT (msg) {
16
+ return new HypercoreError(msg, 'BAD_ARGUMENT', HypercoreError.BAD_ARGUMENT)
17
+ }
18
+
19
+ static STORAGE_EMPTY (msg) {
20
+ return new HypercoreError(msg, 'STORAGE_EMPTY', HypercoreError.STORAGE_EMPTY)
21
+ }
22
+
23
+ static STORAGE_CONFLICT (msg) {
24
+ return new HypercoreError(msg, 'STORAGE_CONFLICT', HypercoreError.STORAGE_CONFLICT)
25
+ }
26
+
27
+ static INVALID_SIGNATURE (msg) {
28
+ return new HypercoreError(msg, 'INVALID_SIGNATURE', HypercoreError.INVALID_SIGNATURE)
29
+ }
30
+
31
+ static INVALID_CAPABILITY (msg) {
32
+ return new HypercoreError(msg, 'INVALID_CAPABILITY', HypercoreError.INVALID_CAPABILITY)
33
+ }
34
+
35
+ static SNAPSHOT_NOT_AVAILABLE (msg = 'Snapshot is not available') {
36
+ return new HypercoreError(msg, 'SNAPSHOT_NOT_AVAILABLE', HypercoreError.SNAPSHOT_NOT_AVAILABLE)
37
+ }
38
+
39
+ static REQUEST_CANCELLED (msg = 'Request was cancelled') {
40
+ return new HypercoreError(msg, 'REQUEST_CANCELLED', HypercoreError.REQUEST_CANCELLED)
41
+ }
42
+
43
+ static SESSION_NOT_WRITABLE (msg = 'Session is not writable') {
44
+ return new HypercoreError(msg, 'SESSION_NOT_WRITABLE', HypercoreError.SESSION_NOT_WRITABLE)
45
+ }
46
+
47
+ static SESSION_CLOSED (msg = 'Session is closed') {
48
+ return new HypercoreError(msg, 'SESSION_CLOSED', HypercoreError.SESSION_CLOSED)
49
+ }
50
+ }
package/lib/info.js ADDED
@@ -0,0 +1,24 @@
1
+ module.exports = class Info {
2
+ constructor (opts = {}) {
3
+ this.length = opts.length || 0
4
+ this.contiguousLength = opts.contiguousLength || 0
5
+ this.byteLength = opts.byteLength || 0
6
+ this.padding = opts.padding || 0
7
+ }
8
+
9
+ static async from (core, padding, snapshot) {
10
+ return new Info({
11
+ key: core.key,
12
+ discoveryKey: core.discoveryKey,
13
+ length: snapshot
14
+ ? snapshot.length
15
+ : core.tree.length,
16
+ contiguousLength: core.header.contiguousLength,
17
+ byteLength: snapshot
18
+ ? snapshot.byteLength
19
+ : (core.tree.byteLength - (core.tree.length * padding)),
20
+ fork: core.tree.fork,
21
+ padding
22
+ })
23
+ }
24
+ }