hypercore 10.24.11 → 10.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -283,15 +283,15 @@ A range can have the following properties:
283
283
  }
284
284
  ```
285
285
 
286
- To download the full core continously (often referred to as non sparse mode) do
286
+ To download the full core continuously (often referred to as non sparse mode) do
287
287
 
288
288
  ``` js
289
- // Note that this will never be consider downloaded as the range
289
+ // Note that this will never be considered downloaded as the range
290
290
  // will keep waiting for new blocks to be appended.
291
291
  core.download({ start: 0, end: -1 })
292
292
  ```
293
293
 
294
- To downloaded a discrete range of blocks pass a list of indices.
294
+ To download a discrete range of blocks pass a list of indices.
295
295
 
296
296
  ```js
297
297
  core.download({ blocks: [4, 9, 7] })
@@ -434,7 +434,7 @@ How much padding is applied to each block of this core? Will be `0` unless block
434
434
 
435
435
  Create a replication stream. You should pipe this to another Hypercore instance.
436
436
 
437
- The `isInitiator` argument is a boolean indicating whether you are the iniatior of the connection (ie the client)
437
+ The `isInitiator` argument is a boolean indicating whether you are the initiator of the connection (ie the client)
438
438
  or if you are the passive part (ie the server).
439
439
 
440
440
  If you are using a P2P swarm like [Hyperswarm](https://github.com/hyperswarm/hyperswarm) you can know this by checking if the swarm connection is a client socket or server socket. In Hyperswarm you can check that using the [client property on the peer details object](https://github.com/hyperswarm/hyperswarm#swarmonconnection-socket-details--)
package/index.js CHANGED
@@ -211,8 +211,8 @@ module.exports = class Hypercore extends EventEmitter {
211
211
 
212
212
  session (opts = {}) {
213
213
  if (this.closing) {
214
- // This makes the closing logic alot easier. If this turns out to be a problem
215
- // in practive, open an issue and we'll try to make a solution for it.
214
+ // This makes the closing logic a lot easier. If this turns out to be a problem
215
+ // in practice, open an issue and we'll try to make a solution for it.
216
216
  throw SESSION_CLOSED('Cannot make sessions on a closing core')
217
217
  }
218
218
 
@@ -511,6 +511,9 @@ module.exports = class Hypercore extends EventEmitter {
511
511
  // because it doesn't really make a lot of sense.
512
512
  if (Protomux.isProtomux(isInitiator)) return this._attachToMuxer(isInitiator, opts)
513
513
 
514
+ // if same stream is passed twice, ignore the 2nd one before we make sessions etc
515
+ if (isStream(isInitiator) && this._isAttached(isInitiator)) return isInitiator
516
+
514
517
  const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
515
518
  const noiseStream = protocolStream.noiseStream
516
519
  const protocol = noiseStream.userData
@@ -521,6 +524,10 @@ module.exports = class Hypercore extends EventEmitter {
521
524
  return protocolStream
522
525
  }
523
526
 
527
+ _isAttached (stream) {
528
+ return stream.userData && this.replicator && this.replicator.attached(stream.userData)
529
+ }
530
+
524
531
  _attachToMuxer (mux, useSession) {
525
532
  if (this.opened) {
526
533
  this._attachToMuxerOpened(mux, useSession)
@@ -771,9 +778,9 @@ module.exports = class Hypercore extends EventEmitter {
771
778
  return true
772
779
  }
773
780
 
774
- batch ({ autoClose = true, session = true } = {}) {
781
+ batch ({ checkout = -1, autoClose = true, session = true } = {}) {
775
782
  if (this._batch !== null) throw BATCH_ALREADY_EXISTS()
776
- const batch = new Batch(session ? this.session() : this, autoClose)
783
+ const batch = new Batch(session ? this.session() : this, checkout, autoClose)
777
784
  for (const session of this.sessions) session._batch = batch
778
785
  return batch
779
786
  }
@@ -1093,7 +1100,7 @@ function preappend (blocks) {
1093
1100
 
1094
1101
  function ensureEncryption (core, opts) {
1095
1102
  if (!opts.encryptionKey) return
1096
- // Only override the block encryption if its either not already set or if
1103
+ // Only override the block encryption if it's either not already set or if
1097
1104
  // the caller provided a different key.
1098
1105
  if (core.encryption && b4a.equals(core.encryption.key, opts.encryptionKey)) return
1099
1106
  core.encryption = new BlockEncryption(opts.encryptionKey, core.key)
package/lib/batch.js CHANGED
@@ -1,9 +1,10 @@
1
1
  const { BLOCK_NOT_AVAILABLE, SESSION_CLOSED } = require('hypercore-errors')
2
2
  const EventEmitter = require('events')
3
3
  const c = require('compact-encoding')
4
+ const b4a = require('b4a')
4
5
 
5
6
  module.exports = class HypercoreBatch extends EventEmitter {
6
- constructor (session, autoClose) {
7
+ constructor (session, checkoutLength, autoClose) {
7
8
  super()
8
9
 
9
10
  this.session = session
@@ -16,6 +17,8 @@ module.exports = class HypercoreBatch extends EventEmitter {
16
17
  this.fork = 0
17
18
 
18
19
  this._appends = []
20
+ this._appendsActual = null
21
+ this._checkoutLength = checkoutLength
19
22
  this._byteLength = 0
20
23
  this._sessionLength = 0
21
24
  this._sessionByteLength = 0
@@ -64,9 +67,22 @@ module.exports = class HypercoreBatch extends EventEmitter {
64
67
  async ready () {
65
68
  await this.session.ready()
66
69
  if (this.opened) return
67
- this._sessionLength = this.session.length
68
- this._sessionByteLength = this.session.byteLength
69
- this._sessionBatch = this.session.createTreeBatch()
70
+
71
+ if (this._checkoutLength !== -1) {
72
+ const batch = await this.session.core.tree.truncate(this._checkoutLength, this.session.fork)
73
+ batch.upgraded = false
74
+ batch.treeLength = batch.ancestors = this._checkoutLength
75
+ if (this.opened) return
76
+ this._sessionLength = batch.length
77
+ this._sessionByteLength = batch.byteLength
78
+ this._sessionBatch = batch
79
+ } else {
80
+ this._sessionLength = this.session.length
81
+ this._sessionByteLength = this.session.byteLength
82
+ this._sessionBatch = this.session.createTreeBatch()
83
+ }
84
+
85
+ this._appendsActual = this.session.encryption ? [] : this._appends
70
86
  this.fork = this.session.fork
71
87
  this.opened = true
72
88
  this.emit('ready')
@@ -77,6 +93,10 @@ module.exports = class HypercoreBatch extends EventEmitter {
77
93
  await this.session.update(opts)
78
94
  }
79
95
 
96
+ treeHash () {
97
+ return this._sessionBatch.hash()
98
+ }
99
+
80
100
  setUserData (key, value, opts) {
81
101
  return this.session.setUserData(key, value, opts)
82
102
  }
@@ -157,13 +177,13 @@ module.exports = class HypercoreBatch extends EventEmitter {
157
177
  if (len < this._sessionLength || length > maxLength) return null
158
178
 
159
179
  for (let i = 0; i < len - this._sessionLength; i++) {
160
- b.append(this._appends[i])
180
+ b.append(this._appendsActual[i])
161
181
  }
162
182
 
163
183
  if (len < this.length) return b
164
184
 
165
185
  for (let i = 0; i < length - len; i++) {
166
- b.append(blocks[i])
186
+ b.append(this._appendsActual === this._appends ? blocks[i] : this._encrypt(b.length + i, blocks[i]))
167
187
  }
168
188
 
169
189
  return b
@@ -215,11 +235,16 @@ module.exports = class HypercoreBatch extends EventEmitter {
215
235
 
216
236
  if (session.encodeBatch === null) {
217
237
  for (let i = 0; i < blocks.length; i++) {
218
- const buffer = session._encode(session.valueEncoding, blocks[i])
238
+ const buffer = this._encode(session.valueEncoding, blocks[i])
219
239
  buffers[i] = buffer
220
240
  this._byteLength += buffer.byteLength
221
241
  }
222
242
  }
243
+ if (this._appends !== this._appendsActual) {
244
+ for (let i = 0; i < buffers.length; i++) {
245
+ this._appendsActual.push(this._encrypt(this._sessionLength + this._appendsActual.length, buffers[i]))
246
+ }
247
+ }
223
248
 
224
249
  this._appends.push(...buffers)
225
250
 
@@ -229,6 +254,35 @@ module.exports = class HypercoreBatch extends EventEmitter {
229
254
  return info
230
255
  }
231
256
 
257
+ _encode (enc, val) {
258
+ const state = { start: 0, end: 0, buffer: null }
259
+
260
+ if (b4a.isBuffer(val)) {
261
+ if (state.start === 0) return val
262
+ state.end += val.byteLength
263
+ } else if (enc) {
264
+ enc.preencode(state, val)
265
+ } else {
266
+ val = b4a.from(val)
267
+ if (state.start === 0) return val
268
+ state.end += val.byteLength
269
+ }
270
+
271
+ state.buffer = b4a.allocUnsafe(state.end)
272
+
273
+ if (enc) enc.encode(state, val)
274
+ else state.buffer.set(val, state.start)
275
+
276
+ return state.buffer
277
+ }
278
+
279
+ _encrypt (index, buffer) {
280
+ const block = b4a.allocUnsafe(buffer.byteLength + 8)
281
+ block.set(buffer, 8)
282
+ this.session.encryption.encrypt(index, block, this.fork)
283
+ return block
284
+ }
285
+
232
286
  async flush (opts = {}) {
233
287
  if (this.opened === false) await this.opening
234
288
  if (this.closing) throw SESSION_CLOSED()
@@ -258,7 +312,7 @@ module.exports = class HypercoreBatch extends EventEmitter {
258
312
  const batch = this.createTreeBatch(this._sessionLength + flushingLength)
259
313
  if (batch === null) return false
260
314
 
261
- const info = await this.core.insertBatch(batch, this._appends, { keyPair, signature })
315
+ const info = await this.core.insertBatch(batch, this._appendsActual, { keyPair, signature })
262
316
  if (info === null) return false
263
317
 
264
318
  const delta = info.byteLength - this._sessionByteLength
package/lib/core.js CHANGED
@@ -217,20 +217,21 @@ module.exports = class Core {
217
217
  return false
218
218
  }
219
219
 
220
- async copyFrom (src, signature) {
220
+ async copyFrom (src, signature, { length = src.tree.length } = {}) {
221
221
  await this._mutex.lock()
222
222
 
223
223
  try {
224
- let pos = src.bitfield.firstSet(0)
224
+ let pos = 0
225
225
 
226
- while (pos >= 0) {
227
- const segmentStart = pos
228
- const segmentEnd = src.bitfield.firstUnset(pos)
226
+ while (pos < length) {
227
+ const segmentStart = maximumSegmentStart(pos, src.bitfield, this.bitfield)
228
+ const segmentEnd = minimumSegmentEnd(segmentStart, src.bitfield, this.bitfield)
229
229
 
230
- if (segmentStart < 0) break
230
+ if (segmentStart >= length || segmentStart < 0) break
231
231
 
232
232
  const segment = []
233
233
 
234
+ pos = segmentStart
234
235
  while (pos < segmentEnd) {
235
236
  const val = await src.blocks.get(pos++)
236
237
  segment.push(val)
@@ -241,13 +242,10 @@ module.exports = class Core {
241
242
 
242
243
  this.bitfield.setRange(segmentStart, segmentEnd, true)
243
244
 
244
- pos = src.bitfield.firstSet(segmentEnd + 1)
245
+ pos = segmentEnd + 1
245
246
  }
246
247
 
247
- // TODO: is it an issue to move the nodes directly?
248
- // TODO: make flat iterator that computes needed nodes
249
-
250
- for (let i = 0; i <= src.tree.length * 2; i++) {
248
+ for (let i = 0; i < length * 2; i++) {
251
249
  const node = await src.tree.get(i, false)
252
250
  if (node === null) continue
253
251
 
@@ -256,25 +254,35 @@ module.exports = class Core {
256
254
 
257
255
  await this.tree.flush()
258
256
 
259
- this.tree.fork = src.tree.fork
260
- this.tree.roots = [...src.tree.roots]
261
- this.tree.length = src.tree.length
262
- this.tree.byteLength = src.tree.byteLength
263
-
264
- try {
265
- const batch = this.tree.batch()
266
- batch.signature = signature
267
- this._verifyBatchUpgrade(batch, this.header.manifest)
268
- this.tree.signature = signature
269
- } catch (err) {
270
- this.tree.signature = null
271
- // TODO: how to handle signature failure?
272
- throw err
257
+ if (length > this.tree.length) {
258
+ this.tree.fork = src.tree.fork
259
+ this.tree.roots = [...src.tree.roots]
260
+ this.tree.length = src.tree.length
261
+ this.tree.byteLength = src.tree.byteLength
262
+
263
+ if (length < this.tree.length) {
264
+ const batch = await src.tree.truncate(length)
265
+ this.tree.roots = [...batch.roots]
266
+ this.tree.length = batch.length
267
+ this.tree.byteLength = batch.byteLength
268
+ }
269
+
270
+ try {
271
+ const batch = this.tree.batch()
272
+ batch.signature = signature
273
+ this._verifyBatchUpgrade(batch, this.header.manifest)
274
+ this.tree.signature = signature
275
+ } catch (err) {
276
+ this.tree.signature = null
277
+ // TODO: how to handle signature failure?
278
+ throw err
279
+ }
280
+
281
+ this.header.tree.length = this.tree.length
282
+ this.header.tree.rootHash = this.tree.hash()
283
+ this.header.tree.signature = this.tree.signature
273
284
  }
274
285
 
275
- this.header.tree.length = this.tree.length
276
- this.header.tree.rootHash = this.tree.hash()
277
- this.header.tree.signature = this.tree.signature
278
286
  this.header.userData = src.header.userData // should copy?
279
287
 
280
288
  await this._flushOplog()
@@ -457,6 +465,7 @@ module.exports = class Core {
457
465
 
458
466
  batch.upgraded = batch.length > this.tree.length
459
467
  batch.treeLength = this.tree.length
468
+ batch.ancestors = this.tree.length
460
469
  if (batch.upgraded) batch.signature = signature || this.verifier.sign(batch, keyPair)
461
470
 
462
471
  let byteOffset = batch.byteLength
@@ -897,3 +906,21 @@ async function flushHeader (oplog, bigHeader, header) {
897
906
  }
898
907
 
899
908
  function noop () {}
909
+
910
+ function maximumSegmentStart (start, src, dst) {
911
+ const a = src.firstSet(start)
912
+ const b = dst.firstUnset(start)
913
+
914
+ if (a === -1) return -1
915
+ if (b === -1) return a
916
+ return a < b ? b : a
917
+ }
918
+
919
+ function minimumSegmentEnd (start, src, dst) {
920
+ const a = src.firstUnset(start)
921
+ const b = dst.firstSet(start)
922
+
923
+ if (a === -1) return -1
924
+ if (b === -1) return a
925
+ return a < b ? a : b
926
+ }
package/lib/replicator.js CHANGED
@@ -346,6 +346,7 @@ class Peer {
346
346
  }
347
347
 
348
348
  broadcastRange (start, length, drop) {
349
+ if (drop) this.skipList.setRange(start, length, false)
349
350
  this.wireRange.send({
350
351
  drop,
351
352
  start,
@@ -440,6 +441,16 @@ class Peer {
440
441
  }
441
442
  }
442
443
 
444
+ closeIfIdle () {
445
+ if (this.remoteDownloading === false && this.replicator.isDownloading() === false) {
446
+ // idling, shut it down...
447
+ this.channel.close()
448
+ return true
449
+ }
450
+
451
+ return false
452
+ }
453
+
443
454
  async onsync ({ fork, length, remoteLength, canUpgrade, uploading, downloading, hasManifest }) {
444
455
  const lengthChanged = length !== this.remoteLength
445
456
  const sameFork = fork === this.core.tree.fork
@@ -452,11 +463,7 @@ class Peer {
452
463
  this.remoteDownloading = downloading
453
464
  this.remoteHasManifest = hasManifest
454
465
 
455
- if (this.remoteDownloading === false && this.replicator.isDownloading() === false) {
456
- // idling, shut it down...
457
- this.channel.close()
458
- return
459
- }
466
+ if (this.closeIfIdle()) return
460
467
 
461
468
  this.lengthAcked = sameFork ? remoteLength : 0
462
469
  this.syncsProcessing++
@@ -1082,6 +1089,7 @@ module.exports = class Replicator {
1082
1089
  this.allowFork = allowFork
1083
1090
  this.onpeerupdate = onpeerupdate
1084
1091
  this.onupload = onupload
1092
+ this.ondownloading = null // optional external hook for monitoring downloading status
1085
1093
  this.peers = []
1086
1094
  this.findingPeers = 0 // updateable from the outside
1087
1095
  this.destroyed = false
@@ -1136,7 +1144,11 @@ module.exports = class Replicator {
1136
1144
  if (protomux.opened({ protocol: 'hypercore/alpha', id: this.discoveryKey })) continue
1137
1145
  this._makePeer(protomux, session && session.session({ active: false }))
1138
1146
  }
1147
+ } else {
1148
+ for (const peer of this.peers) peer.closeIfIdle()
1139
1149
  }
1150
+
1151
+ if (this.ondownloading !== null && downloading) this.ondownloading()
1140
1152
  }
1141
1153
 
1142
1154
  cork () {
@@ -1836,6 +1848,10 @@ module.exports = class Replicator {
1836
1848
  session.close().catch(noop)
1837
1849
  }
1838
1850
 
1851
+ attached (protomux) {
1852
+ return this._attached.has(protomux)
1853
+ }
1854
+
1839
1855
  attachTo (protomux, session) {
1840
1856
  const makePeer = this._makePeer.bind(this, protomux, session)
1841
1857
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.24.11",
3
+ "version": "10.25.1",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {