hypercore 10.0.0-alpha.52 → 10.0.0-alpha.55

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
@@ -152,7 +152,7 @@ core.download({ start: 0, end: -1 })
152
152
  To downloaded a discrete range of blocks pass a list of indices.
153
153
 
154
154
  ```js
155
- core.download({ blocks: [4, 9, 7] });
155
+ core.download({ blocks: [4, 9, 7] })
156
156
  ```
157
157
 
158
158
  To cancel downloading a range simply destroy the range instance.
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const { EventEmitter } = require('events')
2
- const raf = require('random-access-file')
2
+ 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')
@@ -9,8 +9,6 @@ const NoiseSecretStream = require('@hyperswarm/secret-stream')
9
9
  const Protomux = require('protomux')
10
10
  const codecs = require('codecs')
11
11
 
12
- const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
13
-
14
12
  const Replicator = require('./lib/replicator')
15
13
  const Core = require('./lib/core')
16
14
  const BlockEncryption = require('./lib/block-encryption')
@@ -162,14 +160,25 @@ module.exports = class Hypercore extends EventEmitter {
162
160
  }
163
161
 
164
162
  static defaultStorage (storage, opts = {}) {
165
- if (typeof storage !== 'string') return storage
163
+ if (typeof storage !== 'string') {
164
+ if (!isRandomAccessClass(storage)) return storage
165
+ const Cls = storage // just to satisfy standard...
166
+ return name => new Cls(name)
167
+ }
168
+
166
169
  const directory = storage
167
170
  const toLock = opts.lock || 'oplog'
168
- return function createFile (name) {
169
- const locked = name === toLock || name.endsWith('/' + toLock)
170
- const lock = locked ? fsctl.lock : null
171
- const sparse = locked ? null : null // fsctl.sparse, disable sparse on windows - seems to fail for some people. TODO: investigate
172
- return raf(name, { directory, lock, sparse })
171
+
172
+ return createFile
173
+
174
+ function createFile (name) {
175
+ const lock = isFile(name, toLock)
176
+ const sparse = isFile(name, 'data') || isFile(name, 'bitfield') || isFile(name, 'tree')
177
+ return new RAF(name, { directory, lock, sparse })
178
+ }
179
+
180
+ function isFile (name, n) {
181
+ return name === n || name.endsWith('/' + n)
173
182
  }
174
183
  }
175
184
 
@@ -303,8 +312,7 @@ module.exports = class Hypercore extends EventEmitter {
303
312
  crypto: this.crypto,
304
313
  legacy: opts.legacy,
305
314
  auth: opts.auth,
306
- onupdate: this._oncoreupdate.bind(this),
307
- oncontigupdate: this._oncorecontigupdate.bind(this)
315
+ onupdate: this._oncoreupdate.bind(this)
308
316
  })
309
317
 
310
318
  if (opts.userData) {
@@ -487,30 +495,50 @@ module.exports = class Hypercore extends EventEmitter {
487
495
 
488
496
  _oncoreupdate (status, bitfield, value, from) {
489
497
  if (status !== 0) {
490
- const truncated = (status & 0b10) !== 0
491
- const appended = (status & 0b01) !== 0
498
+ const truncatedNonSparse = (status & 0b1000) !== 0
499
+ const appendedNonSparse = (status & 0b0100) !== 0
500
+ const truncated = (status & 0b0010) !== 0
501
+ const appended = (status & 0b0001) !== 0
492
502
 
493
503
  if (truncated) {
494
504
  this.replicator.ontruncate(bitfield.start)
495
505
  }
496
506
 
507
+ if ((status & 0b0011) !== 0) {
508
+ this.replicator.onupgrade()
509
+ }
510
+
497
511
  for (let i = 0; i < this.sessions.length; i++) {
498
512
  const s = this.sessions[i]
499
513
 
500
514
  if (truncated) {
501
515
  if (s.cache) s.cache.clear()
502
- s.emit('truncate', bitfield.start, this.core.tree.fork)
516
+
503
517
  // If snapshotted, make sure to update our compat so we can fail gets
504
518
  if (s._snapshot && bitfield.start < s._snapshot.compatLength) s._snapshot.compatLength = bitfield.start
505
519
  }
506
520
 
507
- // For sparse sessions, immediately emit appends. Non-sparse sessions
508
- // are handled separately and only emit appends when their contiguous
509
- // length is updated.
510
- if (appended && s.sparse) s.emit('append')
521
+ if (s.sparse ? truncated : truncatedNonSparse) {
522
+ s.emit('truncate', bitfield.start, this.core.tree.fork)
523
+ }
524
+
525
+ // For sparse sessions, immediately emit appends. If non-sparse, emit if contig length has updated
526
+ if (s.sparse ? appended : appendedNonSparse) {
527
+ s.emit('append')
528
+ }
511
529
  }
512
530
 
513
- this.replicator.onupgrade()
531
+ const contig = this.core.header.contiguousLength
532
+
533
+ // When the contig length catches up, broadcast the non-sparse length to peers
534
+ if (appendedNonSparse && contig === this.core.tree.length) {
535
+ for (const peer of this.peers) {
536
+ if (peer.broadcastedNonSparse) continue
537
+
538
+ peer.broadcastRange(0, contig)
539
+ peer.broadcastedNonSparse = true
540
+ }
541
+ }
514
542
  }
515
543
 
516
544
  if (bitfield) {
@@ -526,16 +554,6 @@ module.exports = class Hypercore extends EventEmitter {
526
554
  }
527
555
  }
528
556
 
529
- _oncorecontigupdate () {
530
- // For non-sparse sessions, emit appends only when the contiguous length is
531
- // updated.
532
- for (let i = 0; i < this.sessions.length; i++) {
533
- const s = this.sessions[i]
534
-
535
- if (!s.sparse) s.emit('append')
536
- }
537
- }
538
-
539
557
  _onpeerupdate (added, peer) {
540
558
  const name = added ? 'peer-add' : 'peer-remove'
541
559
 
@@ -656,29 +674,25 @@ module.exports = class Hypercore extends EventEmitter {
656
674
  }
657
675
 
658
676
  async _get (index, opts) {
659
- let req
677
+ let block
660
678
 
661
679
  if (this.core.bitfield.get(index)) {
662
- req = this.core.blocks.get(index)
680
+ block = this.core.blocks.get(index)
663
681
 
664
- if (this.cache) this.cache.set(index, req)
682
+ if (this.cache) this.cache.set(index, block)
665
683
  } else {
666
684
  if (opts && opts.wait === false) return null
667
685
  if (opts && opts.onwait) opts.onwait(index, this)
668
- else if (this.onwait) this.onwait(index, this)
686
+ if (this.onwait) this.onwait(index, this)
669
687
 
670
688
  const activeRequests = (opts && opts.activeRequests) || this.activeRequests
671
689
 
672
- req = this._cacheOnResolve(
673
- index,
674
- this.replicator
675
- .addBlock(activeRequests, index)
676
- .promise,
677
- this.core.tree.fork
678
- )
690
+ const req = this.replicator.addBlock(activeRequests, index)
691
+
692
+ block = this._cacheOnResolve(index, req.promise, this.core.tree.fork)
679
693
  }
680
694
 
681
- return req
695
+ return block
682
696
  }
683
697
 
684
698
  async _cacheOnResolve (index, req, fork) {
@@ -719,7 +733,9 @@ module.exports = class Hypercore extends EventEmitter {
719
733
 
720
734
  async _download (range) {
721
735
  if (this.opened === false) await this.opening
736
+
722
737
  const activeRequests = (range && range.activeRequests) || this.activeRequests
738
+
723
739
  return this.replicator.addRange(activeRequests, range)
724
740
  }
725
741
 
@@ -852,12 +868,8 @@ function isStream (s) {
852
868
  return typeof s === 'object' && s && typeof s.pipe === 'function'
853
869
  }
854
870
 
855
- function requireMaybe (name) {
856
- try {
857
- return require(name)
858
- } catch (_) {
859
- return null
860
- }
871
+ function isRandomAccessClass (fn) {
872
+ return !!(typeof fn === 'function' && fn.prototype && typeof fn.prototype.open === 'function')
861
873
  }
862
874
 
863
875
  function toHex (buf) {
package/lib/bitfield.js CHANGED
@@ -1,7 +1,6 @@
1
- // TODO: needs massive improvements obvs
2
-
3
1
  const BigSparseArray = require('big-sparse-array')
4
2
  const b4a = require('b4a')
3
+ const bits = require('bits-to-bytes')
5
4
 
6
5
  class FixedBitfield {
7
6
  constructor (index, bitfield) {
@@ -11,26 +10,17 @@ class FixedBitfield {
11
10
  }
12
11
 
13
12
  get (index) {
14
- const j = index & 31
15
- const i = (index - j) / 32
16
-
17
- return i < this.bitfield.length && (this.bitfield[i] & (1 << j)) !== 0
13
+ return bits.get(this.bitfield, index)
18
14
  }
19
15
 
20
16
  set (index, val) {
21
- const j = index & 31
22
- const i = (index - j) / 32
23
- const v = this.bitfield[i]
24
-
25
- if (val === ((v & (1 << j)) !== 0)) return false
26
-
27
- const u = val
28
- ? v | (1 << j)
29
- : v ^ (1 << j)
30
-
31
- if (u === v) return false
17
+ return bits.set(this.bitfield, index, val)
18
+ }
32
19
 
33
- this.bitfield[i] = u
20
+ setRange (start, length, val) {
21
+ // Using fill instead of setRange is ~2 orders of magnitude faster, but does
22
+ // have the downside of not being able to tell if any bits actually changed.
23
+ bits.fill(this.bitfield, val, start, start + length)
34
24
  return true
35
25
  }
36
26
  }
@@ -44,7 +34,11 @@ module.exports = class Bitfield {
44
34
  this.resumed = !!(buf && buf.byteLength >= 4)
45
35
 
46
36
  const all = this.resumed
47
- ? new Uint32Array(buf.buffer, buf.byteOffset, Math.floor(buf.byteLength / 4))
37
+ ? new Uint32Array(
38
+ buf.buffer,
39
+ buf.byteOffset,
40
+ Math.floor(buf.byteLength / 4)
41
+ )
48
42
  : new Uint32Array(1024)
49
43
 
50
44
  for (let i = 0; i < all.length; i += 1024) {
@@ -55,40 +49,53 @@ module.exports = class Bitfield {
55
49
  }
56
50
 
57
51
  get (index) {
58
- const j = index & 32767
59
- const i = (index - j) / 32768
52
+ const j = index & (this.pageSize - 1)
53
+ const i = (index - j) / this.pageSize
54
+
60
55
  const p = this.pages.get(i)
61
56
 
62
57
  return p ? p.get(j) : false
63
58
  }
64
59
 
65
60
  set (index, val) {
66
- const j = index & 32767
67
- const i = (index - j) / 32768
61
+ const j = index & (this.pageSize - 1)
62
+ const i = (index - j) / this.pageSize
68
63
 
69
64
  let p = this.pages.get(i)
70
65
 
71
- if (!p) {
72
- if (!val) return
66
+ if (!p && val) {
73
67
  p = this.pages.set(i, new FixedBitfield(i, new Uint32Array(1024)))
74
68
  }
75
69
 
76
- if (!p.set(j, val) || p.dirty) return
77
-
78
- p.dirty = true
79
- this.unflushed.push(p)
70
+ if (p && p.set(j, val) && !p.dirty) {
71
+ p.dirty = true
72
+ this.unflushed.push(p)
73
+ }
80
74
  }
81
75
 
82
76
  setRange (start, length, val) {
83
- for (let i = 0; i < length; i++) {
84
- this.set(start + i, val)
85
- }
86
- }
77
+ let j = start & (this.pageSize - 1)
78
+ let i = (start - j) / this.pageSize
87
79
 
88
- // Should prob be removed, when/if we re-add compression
89
- page (i) {
90
- const p = this.pages.get(i)
91
- return p ? p.bitfield : new Uint32Array(1024)
80
+ while (length > 0) {
81
+ let p = this.pages.get(i)
82
+
83
+ if (!p && val) {
84
+ p = this.pages.set(i, new FixedBitfield(i, new Uint32Array(1024)))
85
+ }
86
+
87
+ const end = Math.min(j + length, this.pageSize)
88
+ const range = end - j
89
+
90
+ if (p && p.setRange(j, range, val) && !p.dirty) {
91
+ p.dirty = true
92
+ this.unflushed.push(p)
93
+ }
94
+
95
+ j = 0
96
+ i++
97
+ length -= range
98
+ }
92
99
  }
93
100
 
94
101
  clear () {
@@ -120,7 +127,12 @@ module.exports = class Bitfield {
120
127
  let error = null
121
128
 
122
129
  for (const page of this.unflushed) {
123
- const buf = b4a.from(page.bitfield.buffer, page.bitfield.byteOffset, page.bitfield.byteLength)
130
+ const buf = b4a.from(
131
+ page.bitfield.buffer,
132
+ page.bitfield.byteOffset,
133
+ page.bitfield.byteLength
134
+ )
135
+
124
136
  page.dirty = false
125
137
  this.storage.write(page.index * 4096, buf, done)
126
138
  }
@@ -151,7 +163,7 @@ module.exports = class Bitfield {
151
163
  }
152
164
 
153
165
  function ensureSize (uint32, size) {
154
- if (uint32.length === size) return uint32
166
+ if (uint32.byteLength === size) return uint32
155
167
  const a = new Uint32Array(1024)
156
168
  a.set(uint32, 0)
157
169
  return a
package/lib/core.js CHANGED
@@ -9,9 +9,8 @@ const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_SIGNATURE } = req
9
9
  const m = require('./messages')
10
10
 
11
11
  module.exports = class Core {
12
- constructor (header, crypto, oplog, tree, blocks, bitfield, auth, legacy, onupdate, oncontigupdate) {
12
+ constructor (header, crypto, oplog, tree, blocks, bitfield, auth, legacy, onupdate) {
13
13
  this.onupdate = onupdate
14
- this.oncontigupdate = oncontigupdate
15
14
  this.header = header
16
15
  this.crypto = crypto
17
16
  this.oplog = oplog
@@ -27,8 +26,6 @@ module.exports = class Core {
27
26
  this._verifiesFlushed = null
28
27
  this._mutex = new Mutex()
29
28
  this._legacy = legacy
30
-
31
- this._updateContiguousLength(header.contiguousLength)
32
29
  }
33
30
 
34
31
  static async open (storage, opts = {}) {
@@ -134,6 +131,11 @@ module.exports = class Core {
134
131
  await bitfield.clear()
135
132
  }
136
133
 
134
+ // compat from earlier version that do not store contig length
135
+ if (header.contiguousLength === 0) {
136
+ while (bitfield.get(header.contiguousLength)) header.contiguousLength++
137
+ }
138
+
137
139
  const auth = opts.auth || this.createAuth(crypto, header.signer)
138
140
 
139
141
  for (const e of entries) {
@@ -149,6 +151,7 @@ module.exports = class Core {
149
151
 
150
152
  if (e.bitfield) {
151
153
  bitfield.setRange(e.bitfield.start, e.bitfield.length, !e.bitfield.drop)
154
+ updateContig(header, e.bitfield, bitfield)
152
155
  }
153
156
 
154
157
  if (e.treeUpgrade) {
@@ -165,7 +168,7 @@ module.exports = class Core {
165
168
  }
166
169
  }
167
170
 
168
- return new this(header, crypto, oplog, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop, opts.oncontigupdate || noop)
171
+ return new this(header, crypto, oplog, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop)
169
172
  }
170
173
 
171
174
  _shouldFlush () {
@@ -196,17 +199,6 @@ module.exports = class Core {
196
199
  await this.blocks.put(index, value, byteOffset)
197
200
  }
198
201
 
199
- _updateContiguousLength (index, length = 0) {
200
- if (index === this.header.contiguousLength) {
201
- let i = this.header.contiguousLength + length
202
-
203
- while (this.bitfield.get(i)) i++
204
-
205
- this.header.contiguousLength = i
206
- this.oncontigupdate()
207
- }
208
- }
209
-
210
202
  async userData (key, value) {
211
203
  // TODO: each oplog append can set user data, so we should have a way
212
204
  // to just hitch a ride on one of the other ongoing appends?
@@ -286,12 +278,12 @@ module.exports = class Core {
286
278
  this.bitfield.setRange(batch.ancestors, batch.length - batch.ancestors, true)
287
279
  batch.commit()
288
280
 
289
- this.header.contiguousLength = batch.length
290
- this.oncontigupdate()
291
281
  this.header.tree.length = batch.length
292
282
  this.header.tree.rootHash = hash
293
283
  this.header.tree.signature = batch.signature
294
- this.onupdate(0b01, entry.bitfield, null, null)
284
+
285
+ const status = 0b0001 | updateContig(this.header, entry.bitfield, this.bitfield)
286
+ this.onupdate(status, entry.bitfield, null, null)
295
287
 
296
288
  if (this._shouldFlush()) await this._flushOplog()
297
289
 
@@ -329,9 +321,11 @@ module.exports = class Core {
329
321
 
330
322
  await this.oplog.append([entry], false)
331
323
 
324
+ let status = 0b0001
325
+
332
326
  if (bitfield) {
333
327
  this.bitfield.set(bitfield.start, true)
334
- this._updateContiguousLength(bitfield.start, bitfield.length)
328
+ status |= updateContig(this.header, bitfield, this.bitfield)
335
329
  }
336
330
 
337
331
  batch.commit()
@@ -340,7 +334,8 @@ module.exports = class Core {
340
334
  this.header.tree.length = batch.length
341
335
  this.header.tree.rootHash = batch.rootHash
342
336
  this.header.tree.signature = batch.signature
343
- this.onupdate(0b01, bitfield, value, from)
337
+
338
+ this.onupdate(status, bitfield, value, from)
344
339
 
345
340
  if (this._shouldFlush()) await this._flushOplog()
346
341
  } finally {
@@ -387,14 +382,16 @@ module.exports = class Core {
387
382
  continue
388
383
  }
389
384
 
385
+ let status = 0
386
+
390
387
  if (bitfield) {
391
388
  this.bitfield.set(bitfield.start, true)
392
- this._updateContiguousLength(bitfield.start, bitfield.length)
389
+ status = updateContig(this.header, bitfield, this.bitfield)
393
390
  }
394
391
 
395
392
  batch.commit()
396
393
 
397
- this.onupdate(0, bitfield, value, from)
394
+ this.onupdate(status, bitfield, value, from)
398
395
  }
399
396
 
400
397
  if (this._shouldFlush()) await this._flushOplog()
@@ -472,15 +469,15 @@ module.exports = class Core {
472
469
  addReorgHint(this.header.hints.reorgs, this.tree, batch)
473
470
  batch.commit()
474
471
 
475
- const appended = batch.length > batch.ancestors
472
+ const contigStatus = updateContig(this.header, entry.bitfield, this.bitfield)
473
+ const status = ((batch.length > batch.ancestors) ? 0b0011 : 0b0010) | contigStatus
476
474
 
477
- this.header.contiguousLength = Math.min(batch.ancestors, this.header.contiguousLength)
478
- this.oncontigupdate()
479
475
  this.header.tree.fork = batch.fork
480
476
  this.header.tree.length = batch.length
481
477
  this.header.tree.rootHash = batch.hash()
482
478
  this.header.tree.signature = batch.signature
483
- this.onupdate(appended ? 0b11 : 0b10, entry.bitfield, null, from)
479
+
480
+ this.onupdate(status, entry.bitfield, null, from)
484
481
 
485
482
  // TODO: there is a bug in the merkle tree atm where it cannot handle unflushed
486
483
  // truncates if we append or download anything after the truncation point later on
@@ -501,6 +498,36 @@ module.exports = class Core {
501
498
  }
502
499
  }
503
500
 
501
+ function updateContig (header, upd, bitfield) {
502
+ const end = upd.start + upd.length
503
+
504
+ let c = header.contiguousLength
505
+
506
+ if (upd.drop) {
507
+ // If we dropped a block in the current contig range, "downgrade" it
508
+ if (c <= end && c > upd.start) {
509
+ c = upd.start
510
+ }
511
+ } else {
512
+ if (c <= end && c >= upd.start) {
513
+ c = end
514
+ while (bitfield.get(c)) c++
515
+ }
516
+ }
517
+
518
+ if (c === header.contiguousLength) {
519
+ return 0b0000
520
+ }
521
+
522
+ if (c > header.contiguousLength) {
523
+ header.contiguousLength = c
524
+ return 0b0100
525
+ }
526
+
527
+ header.contiguousLength = c
528
+ return 0b1000
529
+ }
530
+
504
531
  function addReorgHint (list, tree, batch) {
505
532
  if (tree.length === 0 || tree.fork === batch.fork) return
506
533
 
@@ -1,24 +1,45 @@
1
1
  const BigSparseArray = require('big-sparse-array')
2
+ const bits = require('bits-to-bytes')
2
3
 
3
4
  module.exports = class RemoteBitfield {
4
5
  constructor () {
6
+ this.pageSize = 32768
5
7
  this.pages = new BigSparseArray()
6
8
  }
7
9
 
8
10
  get (index) {
9
- const r = index & 32767
10
- const i = (index - r) / 32768
11
+ const j = index & (this.pageSize - 1)
12
+ const i = (index - j) / this.pageSize
13
+
11
14
  const p = this.pages.get(i)
12
15
 
13
- return p ? (p[r >>> 5] & (1 << (r & 31))) !== 0 : false
16
+ return p ? bits.get(p, j) : false
14
17
  }
15
18
 
16
19
  set (index, val) {
17
- const r = index & 32767
18
- const i = (index - r) / 32768
20
+ const j = index & (this.pageSize - 1)
21
+ const i = (index - j) / this.pageSize
22
+
19
23
  const p = this.pages.get(i) || this.pages.set(i, new Uint32Array(1024))
20
24
 
21
- if (val) p[r >>> 5] |= (1 << (r & 31))
22
- else p[r >>> 5] &= ~(1 << (r & 31))
25
+ bits.set(p, j, val)
26
+ }
27
+
28
+ setRange (start, length, val) {
29
+ let j = start & (this.pageSize - 1)
30
+ let i = (start - j) / this.pageSize
31
+
32
+ while (length > 0) {
33
+ const p = this.pages.get(i) || this.pages.set(i, new Uint32Array(1024))
34
+
35
+ const end = Math.min(j + length, this.pageSize)
36
+ const range = end - j
37
+
38
+ bits.fill(p, val, j, end)
39
+
40
+ j = 0
41
+ i++
42
+ length -= range
43
+ }
23
44
  }
24
45
  }
package/lib/replicator.js CHANGED
@@ -7,6 +7,7 @@ const m = require('./messages')
7
7
  const caps = require('./caps')
8
8
 
9
9
  const DEFAULT_MAX_INFLIGHT = 32
10
+ const DEFAULT_SEGMENT_SIZE = 128 * 1024 // 128 KiB
10
11
 
11
12
  class Attachable {
12
13
  constructor () {
@@ -286,6 +287,9 @@ class Peer {
286
287
  this.remoteDownloading = true
287
288
  this.remoteSynced = false
288
289
 
290
+ this.segmentsWanted = new Set()
291
+ this.broadcastedNonSparse = false
292
+
289
293
  this.lengthAcked = 0
290
294
 
291
295
  this.extensions = new Map()
@@ -356,13 +360,13 @@ class Peer {
356
360
 
357
361
  this.sendSync()
358
362
 
359
- const p = pages(this.core)
363
+ const contig = this.core.header.contiguousLength
364
+ if (contig > 0) {
365
+ this.broadcastRange(0, contig, false)
360
366
 
361
- for (let index = 0; index < p.length; index++) {
362
- this.wireBitfield.send({
363
- start: index * this.core.bitfield.pageSize,
364
- bitfield: p[index]
365
- })
367
+ if (contig === this.core.tree.length) {
368
+ this.broadcastedNonSparse = true
369
+ }
366
370
  }
367
371
 
368
372
  this.replicator._ifAvailable--
@@ -552,8 +556,8 @@ class Peer {
552
556
  this.replicator._onnodata(this, req)
553
557
  }
554
558
 
555
- onwant () {
556
- // TODO
559
+ onwant ({ start, length }) {
560
+ this.replicator._onwant(this, start, length)
557
561
  }
558
562
 
559
563
  onunwant () {
@@ -577,9 +581,7 @@ class Peer {
577
581
  onrange ({ drop, start, length }) {
578
582
  const has = drop === false
579
583
 
580
- for (const end = start + length; start < end; start++) {
581
- this.remoteBitfield.set(start, has)
582
- }
584
+ this.remoteBitfield.setRange(start, length, has)
583
585
 
584
586
  if (drop === false) this._update()
585
587
  }
@@ -674,6 +676,7 @@ class Peer {
674
676
  return true
675
677
  }
676
678
 
679
+ this._maybeWant(s.seeker.start, len)
677
680
  return false
678
681
  }
679
682
 
@@ -685,7 +688,10 @@ class Peer {
685
688
  _requestBlock (b) {
686
689
  const { length, fork } = this.core.tree
687
690
 
688
- if (this.remoteBitfield.get(b.index) === false || fork !== this.remoteFork) return false
691
+ if (this.remoteBitfield.get(b.index) === false || fork !== this.remoteFork) {
692
+ this._maybeWant(b.index)
693
+ return false
694
+ }
689
695
 
690
696
  const req = this._makeRequest(b.index >= length)
691
697
  if (req === null) return false
@@ -743,6 +749,7 @@ class Peer {
743
749
  return true
744
750
  }
745
751
 
752
+ this._maybeWant(r.start, len)
746
753
  return false
747
754
  }
748
755
 
@@ -780,9 +787,25 @@ class Peer {
780
787
  return true
781
788
  }
782
789
 
790
+ this._maybeWant(f.batch.want.start, len)
783
791
  return false
784
792
  }
785
793
 
794
+ _maybeWant (start, length = 1) {
795
+ let i = Math.floor(start / DEFAULT_SEGMENT_SIZE)
796
+ const n = Math.ceil((start + length) / DEFAULT_SEGMENT_SIZE)
797
+
798
+ for (; i < n; i++) {
799
+ if (this.segmentsWanted.has(i)) continue
800
+ this.segmentsWanted.add(i)
801
+
802
+ this.wireWant.send({
803
+ start: i * DEFAULT_SEGMENT_SIZE,
804
+ length: DEFAULT_SEGMENT_SIZE
805
+ })
806
+ }
807
+ }
808
+
786
809
  async _send (req) {
787
810
  const fork = this.core.tree.fork
788
811
 
@@ -1299,6 +1322,28 @@ module.exports = class Replicator {
1299
1322
  else this.updatePeer(peer)
1300
1323
  }
1301
1324
 
1325
+ _onwant (peer, start, length) {
1326
+ length = Math.min(length, this.core.tree.length - start)
1327
+
1328
+ peer.protomux.cork()
1329
+
1330
+ let i = Math.floor(start / this.core.bitfield.pageSize)
1331
+ const n = Math.ceil((start + length) / this.core.bitfield.pageSize)
1332
+
1333
+ for (; i < n; i++) {
1334
+ const p = this.core.bitfield.pages.get(i)
1335
+
1336
+ if (p) {
1337
+ peer.wireBitfield.send({
1338
+ start: i * this.core.bitfield.pageSize,
1339
+ bitfield: p.bitfield
1340
+ })
1341
+ }
1342
+ }
1343
+
1344
+ peer.protomux.uncork()
1345
+ }
1346
+
1302
1347
  async _onreorgdata (peer, req, data) {
1303
1348
  const f = this._addReorg(data.fork, peer)
1304
1349
 
@@ -1525,17 +1570,6 @@ module.exports = class Replicator {
1525
1570
  }
1526
1571
  }
1527
1572
 
1528
- function pages (core) {
1529
- const res = []
1530
-
1531
- for (let i = 0; i < core.tree.length; i += core.bitfield.pageSize) {
1532
- const p = core.bitfield.page(i / core.bitfield.pageSize)
1533
- res.push(p)
1534
- }
1535
-
1536
- return res
1537
- }
1538
-
1539
1573
  function matchingRequest (req, data) {
1540
1574
  if (data.block !== null && (req.block === null || req.block.index !== data.block.index)) return false
1541
1575
  if (data.hash !== null && (req.hash === null || req.hash.index !== data.hash.index)) return false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.0.0-alpha.52",
3
+ "version": "10.0.0-alpha.55",
4
4
  "description": "Hypercore 10",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -35,6 +35,7 @@
35
35
  "@hyperswarm/secret-stream": "^6.0.0",
36
36
  "b4a": "^1.1.0",
37
37
  "big-sparse-array": "^1.0.2",
38
+ "bits-to-bytes": "^1.2.0",
38
39
  "codecs": "^3.0.0",
39
40
  "compact-encoding": "^2.5.0",
40
41
  "crc32-universal": "^1.0.1",
@@ -43,7 +44,7 @@
43
44
  "hypercore-crypto": "^3.2.1",
44
45
  "is-options": "^1.0.1",
45
46
  "protomux": "^3.2.0",
46
- "random-access-file": "^2.1.4",
47
+ "random-access-file": "^3.0.1",
47
48
  "random-array-iterator": "^1.0.0",
48
49
  "safety-catch": "^1.0.1",
49
50
  "sodium-universal": "^3.0.4",
@@ -52,13 +53,10 @@
52
53
  },
53
54
  "devDependencies": {
54
55
  "brittle": "^2.0.0",
55
- "hyperswarm": "next",
56
- "random-access-memory": "^4.1.0",
57
- "random-access-memory-overlay": "^1.0.0",
56
+ "hyperswarm": "^4.1.1",
57
+ "random-access-memory": "^5.0.0",
58
+ "random-access-memory-overlay": "^2.0.0",
58
59
  "standard": "^16.0.3",
59
60
  "tmp-promise": "^3.0.2"
60
- },
61
- "optionalDependencies": {
62
- "fsctl": "^1.0.0"
63
61
  }
64
62
  }