hypercore 10.0.0-alpha.1 → 10.0.0-alpha.5
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/.github/workflows/test-node.yml +1 -2
- package/index.js +21 -4
- package/lib/protocol.js +3 -1
- package/lib/replicator.js +34 -8
- package/package.json +3 -2
- package/test/replicate.js +37 -0
package/index.js
CHANGED
|
@@ -3,6 +3,7 @@ const raf = require('random-access-file')
|
|
|
3
3
|
const isOptions = require('is-options')
|
|
4
4
|
const hypercoreCrypto = require('hypercore-crypto')
|
|
5
5
|
const c = require('compact-encoding')
|
|
6
|
+
const Xache = require('xache')
|
|
6
7
|
const NoiseSecretStream = require('@hyperswarm/secret-stream')
|
|
7
8
|
const codecs = require('codecs')
|
|
8
9
|
|
|
@@ -44,6 +45,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
44
45
|
this.core = null
|
|
45
46
|
this.replicator = null
|
|
46
47
|
this.extensions = opts.extensions || new Extensions()
|
|
48
|
+
this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
|
|
47
49
|
|
|
48
50
|
this.valueEncoding = null
|
|
49
51
|
this.key = key || null
|
|
@@ -89,8 +91,9 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
89
91
|
const directory = storage
|
|
90
92
|
const toLock = opts.lock || 'oplog'
|
|
91
93
|
return function createFile (name) {
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
+
const locked = name === toLock || name.endsWith('/' + toLock)
|
|
95
|
+
const lock = locked ? fsctl.lock : null
|
|
96
|
+
const sparse = locked ? null : null // fsctl.sparse, disable sparse on windows - seems to fail for some people. TODO: investigate
|
|
94
97
|
return raf(name, { directory, lock, sparse })
|
|
95
98
|
}
|
|
96
99
|
}
|
|
@@ -280,8 +283,13 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
280
283
|
_oncoreupdate (status, bitfield, value, from) {
|
|
281
284
|
if (status !== 0) {
|
|
282
285
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
283
|
-
if ((status & 0b10) !== 0)
|
|
284
|
-
|
|
286
|
+
if ((status & 0b10) !== 0) {
|
|
287
|
+
if (this.cache) this.cache.clear()
|
|
288
|
+
this.sessions[i].emit('truncate', this.core.tree.fork)
|
|
289
|
+
}
|
|
290
|
+
if ((status & 0b01) !== 0) {
|
|
291
|
+
this.sessions[i].emit('append')
|
|
292
|
+
}
|
|
285
293
|
}
|
|
286
294
|
|
|
287
295
|
this.replicator.broadcastInfo()
|
|
@@ -345,6 +353,15 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
345
353
|
|
|
346
354
|
async get (index, opts) {
|
|
347
355
|
if (this.opened === false) await this.opening
|
|
356
|
+
const c = this.cache && this.cache.get(index)
|
|
357
|
+
if (c) return c
|
|
358
|
+
const fork = this.core.tree.fork
|
|
359
|
+
const b = await this._get(index, opts)
|
|
360
|
+
if (this.cache && fork === this.core.tree.fork && b) this.cache.set(index, b)
|
|
361
|
+
return b
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async _get (index, opts) {
|
|
348
365
|
const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
|
|
349
366
|
|
|
350
367
|
if (this.core.bitfield.get(index)) return decode(encoding, await this.core.blocks.get(index))
|
package/lib/protocol.js
CHANGED
|
@@ -125,6 +125,7 @@ class Peer {
|
|
|
125
125
|
this.resend = false
|
|
126
126
|
this.state = state
|
|
127
127
|
this.extensions = new Map()
|
|
128
|
+
this.destroyed = false
|
|
128
129
|
|
|
129
130
|
this._destroyer = this._safeDestroy.bind(this)
|
|
130
131
|
}
|
|
@@ -229,6 +230,7 @@ class Peer {
|
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
destroy (err) {
|
|
233
|
+
this.destroyed = true
|
|
232
234
|
return this.protocol.unregisterPeer(this, err)
|
|
233
235
|
}
|
|
234
236
|
}
|
|
@@ -296,7 +298,7 @@ module.exports = class Protocol {
|
|
|
296
298
|
peer.remoteAlias = -1
|
|
297
299
|
}
|
|
298
300
|
|
|
299
|
-
peer.handlers.onunregister(
|
|
301
|
+
peer.handlers.onunregister(peer, err)
|
|
300
302
|
|
|
301
303
|
if (err) this.noiseStream.destroy(err)
|
|
302
304
|
}
|
package/lib/replicator.js
CHANGED
|
@@ -25,11 +25,12 @@ class InvertedPromise {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
class Request {
|
|
28
|
-
constructor (index, seek) {
|
|
28
|
+
constructor (index, seek, nodes) {
|
|
29
29
|
this.peer = null
|
|
30
30
|
this.index = index
|
|
31
31
|
this.seek = seek
|
|
32
32
|
this.value = seek === 0
|
|
33
|
+
this.nodes = nodes
|
|
33
34
|
this.promises = []
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -278,7 +279,9 @@ class RequestPool {
|
|
|
278
279
|
|
|
279
280
|
_updateSeek (peer, seek) {
|
|
280
281
|
if (seek.request) return false
|
|
281
|
-
|
|
282
|
+
// We have to snapshot the nodes here now, due to the caching of the request...
|
|
283
|
+
const nodes = log2(seek.seeker.end - seek.seeker.start)
|
|
284
|
+
seek.request = this._requestRange(peer, seek.seeker.start, seek.seeker.end, seek.seeker.bytes, nodes)
|
|
282
285
|
return seek.request !== null
|
|
283
286
|
}
|
|
284
287
|
|
|
@@ -296,10 +299,10 @@ class RequestPool {
|
|
|
296
299
|
const end = range.end === -1 ? peer.state.length : range.end
|
|
297
300
|
if (end <= range._start) return false
|
|
298
301
|
|
|
299
|
-
if (range.linear) return !!this._requestRange(peer, range._start, end, 0)
|
|
302
|
+
if (range.linear) return !!this._requestRange(peer, range._start, end, 0, 0)
|
|
300
303
|
|
|
301
304
|
const r = range._start + Math.floor(Math.random() * (end - range._start))
|
|
302
|
-
return !!(this._requestRange(peer, r, end, 0) || this._requestRange(peer, range._start, r, 0))
|
|
305
|
+
return !!(this._requestRange(peer, r, end, 0, 0) || this._requestRange(peer, range._start, r, 0, 0))
|
|
303
306
|
}
|
|
304
307
|
|
|
305
308
|
_updateUpgrade (peer) {
|
|
@@ -328,7 +331,7 @@ class RequestPool {
|
|
|
328
331
|
this.pendingUpgrade = null
|
|
329
332
|
}
|
|
330
333
|
|
|
331
|
-
_requestRange (peer, start, end, seek) {
|
|
334
|
+
_requestRange (peer, start, end, seek, nodes) {
|
|
332
335
|
const remote = peer.state.bitfield
|
|
333
336
|
const local = this.core.bitfield
|
|
334
337
|
|
|
@@ -341,7 +344,7 @@ class RequestPool {
|
|
|
341
344
|
if (this.requests.has(i)) continue
|
|
342
345
|
|
|
343
346
|
// TODO: if seeking and i >= core.length, let that takes precendance in the upgrade req
|
|
344
|
-
const req = new Request(i, i < this.core.tree.length ? seek : 0)
|
|
347
|
+
const req = new Request(i, i < this.core.tree.length ? seek : 0, nodes)
|
|
345
348
|
this.requests.set(i, req)
|
|
346
349
|
this.send(peer, req)
|
|
347
350
|
return req
|
|
@@ -390,16 +393,28 @@ class RequestPool {
|
|
|
390
393
|
|
|
391
394
|
if (data.block.index < this.core.tree.length || this.core.truncating > 0) {
|
|
392
395
|
try {
|
|
393
|
-
data.block.nodes = await this.core.tree.nodes(data.block.index * 2)
|
|
396
|
+
data.block.nodes = Math.max(req.nodes, await this.core.tree.nodes(data.block.index * 2))
|
|
394
397
|
} catch (err) {
|
|
395
398
|
console.error('TODO handle me:', err.stack)
|
|
396
399
|
}
|
|
397
400
|
}
|
|
398
401
|
|
|
402
|
+
if (peer.destroyed) {
|
|
403
|
+
req.peer = null
|
|
404
|
+
this.pending.push(req)
|
|
405
|
+
if (upgrading) {
|
|
406
|
+
this.upgrading.resolve()
|
|
407
|
+
this.upgrading = null
|
|
408
|
+
}
|
|
409
|
+
this.replicator.updateAll()
|
|
410
|
+
return
|
|
411
|
+
}
|
|
412
|
+
|
|
399
413
|
if (fork !== this.core.tree.fork || paused(peer, this.core.tree.fork) || this.core.truncating > 0) {
|
|
400
414
|
if (peer.state.inflight > 0) peer.state.inflight--
|
|
401
415
|
if (req.promises.length) { // someone is eagerly waiting for this request
|
|
402
416
|
req.peer = null // resend on some other peer
|
|
417
|
+
this.pending.push(req)
|
|
403
418
|
} else { // otherwise delete the request
|
|
404
419
|
this.requests.delete(req.index)
|
|
405
420
|
}
|
|
@@ -594,7 +609,7 @@ class RequestPool {
|
|
|
594
609
|
return e.createPromise()
|
|
595
610
|
}
|
|
596
611
|
|
|
597
|
-
const r = new Request(index, 0)
|
|
612
|
+
const r = new Request(index, 0, 0)
|
|
598
613
|
|
|
599
614
|
this.requests.set(index, r)
|
|
600
615
|
this.pending.push(r)
|
|
@@ -810,3 +825,14 @@ function pages (core) {
|
|
|
810
825
|
}
|
|
811
826
|
|
|
812
827
|
function noop () {}
|
|
828
|
+
|
|
829
|
+
function log2 (n) {
|
|
830
|
+
let res = 1
|
|
831
|
+
|
|
832
|
+
while (n > 2) {
|
|
833
|
+
n /= 2
|
|
834
|
+
res++
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return res
|
|
838
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hypercore",
|
|
3
|
-
"version": "10.0.0-alpha.
|
|
3
|
+
"version": "10.0.0-alpha.5",
|
|
4
4
|
"description": "Hypercore 10",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"random-access-file": "^2.1.4",
|
|
40
40
|
"random-array-iterator": "^1.0.0",
|
|
41
41
|
"safety-catch": "^1.0.1",
|
|
42
|
-
"uint64le": "^1.0.0"
|
|
42
|
+
"uint64le": "^1.0.0",
|
|
43
|
+
"xache": "^1.0.0"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"brittle": "^1.4.1",
|
package/test/replicate.js
CHANGED
|
@@ -294,3 +294,40 @@ test('multiplexing multiple times over the same stream', async function (t) {
|
|
|
294
294
|
t.is(b1.length, a1.length, 'same length')
|
|
295
295
|
t.end()
|
|
296
296
|
})
|
|
297
|
+
|
|
298
|
+
test('destroying a stream and re-replicating works', async function (t) {
|
|
299
|
+
const core = await create()
|
|
300
|
+
|
|
301
|
+
while (core.length < 33) await core.append(Buffer.from('#' + core.length))
|
|
302
|
+
|
|
303
|
+
const clone = await create(core.key)
|
|
304
|
+
|
|
305
|
+
let s1 = core.replicate(true)
|
|
306
|
+
let s2 = clone.replicate(false)
|
|
307
|
+
|
|
308
|
+
s1.pipe(s2).pipe(s1)
|
|
309
|
+
|
|
310
|
+
await s2.opened
|
|
311
|
+
|
|
312
|
+
const all = []
|
|
313
|
+
for (let i = 0; i < 33; i++) {
|
|
314
|
+
all.push(clone.get(i))
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
clone.once('download', function () {
|
|
318
|
+
// simulate stream failure in the middle of bulk downloading
|
|
319
|
+
s1.destroy()
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
await new Promise((resolve) => s1.once('close', resolve))
|
|
323
|
+
|
|
324
|
+
// retry
|
|
325
|
+
s1 = core.replicate(true)
|
|
326
|
+
s2 = clone.replicate(false)
|
|
327
|
+
|
|
328
|
+
s1.pipe(s2).pipe(s1)
|
|
329
|
+
|
|
330
|
+
const blocks = await Promise.all(all)
|
|
331
|
+
|
|
332
|
+
t.is(blocks.length, 33, 'downloaded 33 blocks')
|
|
333
|
+
})
|