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 +4 -4
- package/index.js +12 -5
- package/lib/batch.js +62 -8
- package/lib/core.js +55 -28
- package/lib/replicator.js +21 -5
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
215
|
-
// in
|
|
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
|
|
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
|
-
|
|
68
|
-
this.
|
|
69
|
-
|
|
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.
|
|
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 =
|
|
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.
|
|
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 =
|
|
224
|
+
let pos = 0
|
|
225
225
|
|
|
226
|
-
while (pos
|
|
227
|
-
const segmentStart = pos
|
|
228
|
-
const segmentEnd = src.bitfield.
|
|
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 =
|
|
245
|
+
pos = segmentEnd + 1
|
|
245
246
|
}
|
|
246
247
|
|
|
247
|
-
|
|
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.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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.
|
|
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
|
|