hypercore 10.0.0-alpha.3 → 10.0.0-alpha.32

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/replicator.js CHANGED
@@ -1,812 +1,1578 @@
1
- const Protocol = require('./protocol')
2
- const RemoteBitfield = require('./remote-bitfield')
1
+ const b4a = require('b4a')
2
+ const safetyCatch = require('safety-catch')
3
3
  const RandomIterator = require('random-array-iterator')
4
+ const RemoteBitfield = require('./remote-bitfield')
5
+ const m = require('./messages')
6
+ const caps = require('./caps')
7
+
8
+ const DEFAULT_MAX_INFLIGHT = 32
9
+
10
+ class Attachable {
11
+ constructor () {
12
+ this.resolved = false
13
+ this.refs = []
14
+ }
15
+
16
+ attach (session) {
17
+ const r = {
18
+ context: this,
19
+ session,
20
+ sindex: 0,
21
+ rindex: 0,
22
+ resolve: null,
23
+ reject: null,
24
+ promise: null
25
+ }
4
26
 
5
- const PKG = require('../package.json')
6
- const USER_AGENT = PKG.name + '/' + PKG.version + '@nodejs'
27
+ r.sindex = session.push(r) - 1
28
+ r.rindex = this.refs.push(r) - 1
29
+ r.promise = new Promise((resolve, reject) => {
30
+ r.resolve = resolve
31
+ r.reject = reject
32
+ })
7
33
 
8
- class RemoteState {
9
- constructor (core) {
10
- this.receivedInfo = false
11
- this.inflight = 0
12
- this.maxInflight = 16
13
- this.bitfield = new RemoteBitfield()
14
- this.length = 0
15
- this.fork = 0
34
+ return r
16
35
  }
17
- }
18
36
 
19
- class InvertedPromise {
20
- constructor (resolve, reject, index) {
21
- this.index = index
22
- this.resolve = resolve
23
- this.reject = reject
24
- }
25
- }
37
+ detach (r) {
38
+ if (r.context !== this) return false
26
39
 
27
- class Request {
28
- constructor (index, seek) {
29
- this.peer = null
30
- this.index = index
31
- this.seek = seek
32
- this.value = seek === 0
33
- this.promises = []
34
- }
40
+ this._detach(r)
41
+ this._cancel(r)
42
+ this.gc()
35
43
 
36
- createPromise () {
37
- return new Promise((resolve, reject) => {
38
- this.promises.push(new InvertedPromise(resolve, reject, this.promises.length))
39
- })
44
+ return true
40
45
  }
41
46
 
42
- resolve (val) {
43
- for (let i = 0; i < this.promises.length; i++) {
44
- this.promises[i].resolve(val)
45
- }
46
- }
47
+ _detach (r) {
48
+ const rh = this.refs.pop()
49
+ const sh = r.session.pop()
47
50
 
48
- reject (err) {
49
- for (let i = 0; i < this.promises.length; i++) {
50
- this.promises[i].reject(err)
51
- }
52
- }
53
- }
51
+ if (r.rindex < this.refs.length) this.refs[rh.rindex = r.rindex] = rh
52
+ if (r.sindex < r.session.length) r.session[sh.sindex = r.sindex] = sh
53
+
54
+ r.context = null
54
55
 
55
- class Upgrade {
56
- constructor (minLength) {
57
- this.minLength = minLength
58
- this.promises = []
56
+ return r
59
57
  }
60
58
 
61
- update (peers, fork) {
62
- for (const peer of peers) {
63
- if (peer.state.length >= this.minLength && !paused(peer, fork)) return true
64
- if (!peer.state.receivedInfo) return true
65
- }
59
+ gc () {
60
+ if (this.refs.length === 0) this._unref()
61
+ }
66
62
 
67
- return false
63
+ _cancel (r) {
64
+ r.reject(new Error('Request cancelled'))
68
65
  }
69
66
 
70
- createPromise () {
71
- return new Promise((resolve, reject) => {
72
- this.promises.push(new InvertedPromise(resolve, reject, this.promises.length))
73
- })
67
+ _unref () {
68
+ // overwrite me
74
69
  }
75
70
 
76
71
  resolve (val) {
77
- for (let i = 0; i < this.promises.length; i++) {
78
- this.promises[i].resolve(val)
72
+ this.resolved = true
73
+ while (this.refs.length > 0) {
74
+ this._detach(this.refs[this.refs.length - 1]).resolve(val)
79
75
  }
80
76
  }
81
77
 
82
78
  reject (err) {
83
- for (let i = 0; i < this.promises.length; i++) {
84
- this.promises[i].reject(err)
79
+ this.resolved = true
80
+ while (this.refs.length > 0) {
81
+ this._detach(this.refs[this.refs.length - 1]).reject(err)
85
82
  }
86
83
  }
87
84
  }
88
85
 
89
- class UpgradeLock {
90
- constructor (peer, length) {
91
- this.peer = peer
92
- this.length = length
93
- this.resolve = null
94
- this.promise = new Promise((resolve) => { this.resolve = resolve })
95
- }
96
- }
97
-
98
- class Seek {
99
- constructor (seeker) {
100
- this.request = null
101
- this.seeker = seeker
102
- this.promise = null
103
- this.finished = false
104
- }
86
+ class BlockRequest extends Attachable {
87
+ constructor (tracker, fork, index) {
88
+ super()
105
89
 
106
- async update () {
107
- const res = await this.seeker.update()
108
- if (!res) return false
109
- this.finished = true
110
- this.promise.resolve(res)
111
- return true
90
+ this.fork = fork
91
+ this.index = index
92
+ this.inflight = []
93
+ this.queued = false
94
+ this.tracker = tracker
112
95
  }
113
96
 
114
- createPromise () {
115
- return new Promise((resolve, reject) => {
116
- this.promise = new InvertedPromise(resolve, reject, 0)
117
- })
97
+ _unref () {
98
+ if (this.inflight.length > 0) return
99
+ this.tracker.remove(this.fork, this.index)
118
100
  }
119
101
  }
120
102
 
121
- class Range {
122
- constructor (start, end, linear) {
103
+ class RangeRequest extends Attachable {
104
+ constructor (ranges, fork, start, end, linear, blocks) {
105
+ super()
106
+
107
+ this.fork = fork
123
108
  this.start = start
124
109
  this.end = end
125
- this.linear = !!linear
126
- this.promise = null
127
- this.done = false
110
+ this.linear = linear
111
+ this.blocks = blocks
112
+ this.ranges = ranges
128
113
 
129
- this._inv = null
130
- this._start = start // can be updated
131
- this._ranges = null // set be replicator
132
- this._resolved = false
133
- this._error = null
114
+ // As passed by the user, immut
115
+ this.userStart = start
116
+ this.userEnd = end
134
117
  }
135
118
 
136
- contains (req) {
137
- return this._start <= req.index && req.index < this.end
119
+ _unref () {
120
+ const i = this.ranges.indexOf(this)
121
+ if (i === -1) return
122
+ const h = this.ranges.pop()
123
+ if (i < this.ranges.length - 1) this.ranges[i] = h
138
124
  }
139
125
 
140
- update (bitfield) {
141
- if (this.end === -1) {
142
- while (bitfield.get(this._start)) this._start++
143
- return false
144
- }
126
+ _cancel (r) {
127
+ r.resolve(false)
128
+ }
129
+ }
145
130
 
146
- for (; this._start < this.end; this._start++) {
147
- if (!bitfield.get(this._start)) return false
148
- }
131
+ class UpgradeRequest extends Attachable {
132
+ constructor (replicator, fork, length) {
133
+ super()
149
134
 
150
- return true
135
+ this.fork = fork
136
+ this.length = length
137
+ this.inflight = []
138
+ this.replicator = replicator
151
139
  }
152
140
 
153
- resolve (done) {
154
- this._done(null, done)
141
+ _unref () {
142
+ if (this.replicator.eagerUpgrade === true || this.inflight.length > 0) return
143
+ this.replicator._upgrade = null
155
144
  }
156
145
 
157
- destroy (err) {
158
- this._done(err, false)
146
+ _cancel (r) {
147
+ r.resolve(false)
159
148
  }
149
+ }
160
150
 
161
- downloaded () {
162
- if (this.promise) return this.promise
163
- if (!this.done) return this._makePromise()
164
- if (this._error !== null) return Promise.reject(this._error)
165
- return Promise.resolve(this._resolved)
151
+ class SeekRequest extends Attachable {
152
+ constructor (seeks, fork, seeker) {
153
+ super()
154
+
155
+ this.fork = fork
156
+ this.seeker = seeker
157
+ this.inflight = []
158
+ this.seeks = seeks
166
159
  }
167
160
 
168
- _done (err, done) {
169
- if (this.done) return
170
- this.done = true
161
+ _unref () {
162
+ if (this.inflight.length > 0) return
163
+ const i = this.seeks.indexOf(this)
164
+ if (i === -1) return
165
+ const h = this.seeks.pop()
166
+ if (i < this.seeks.length - 1) this.seeks[i] = h
167
+ }
168
+ }
171
169
 
172
- if (this._ranges) {
173
- const i = this._ranges.indexOf(this)
170
+ class InflightTracker {
171
+ constructor () {
172
+ this._requests = []
173
+ this._free = []
174
+ }
174
175
 
175
- if (i === this._ranges.length - 1) this._ranges.pop()
176
- else if (i > -1) this._ranges[i] = this._ranges.pop()
176
+ * [Symbol.iterator] () {
177
+ for (const req of this._requests) {
178
+ if (req !== null) yield req
177
179
  }
180
+ }
178
181
 
179
- this._ranges = null
180
- this._resolved = done
181
- this._error = err
182
+ add (req) {
183
+ const id = this._free.length ? this._free.pop() : this._requests.push(null)
182
184
 
183
- if (this._inv === null) return
184
- if (err) this._inv.reject(err)
185
- else this._inv.resolve(done)
185
+ req.id = id
186
+ this._requests[id - 1] = req
187
+ return req
186
188
  }
187
189
 
188
- _makePromise () {
189
- this.promise = new Promise((resolve, reject) => {
190
- this._inv = new InvertedPromise(resolve, reject, 0)
191
- })
190
+ get (id) {
191
+ return id <= this._requests.length ? this._requests[id - 1] : null
192
+ }
192
193
 
193
- return this.promise
194
+ remove (id) {
195
+ if (id <= this._requests.length) {
196
+ this._requests[id - 1] = null
197
+ this._free.push(id)
198
+ }
194
199
  }
195
200
  }
196
201
 
197
- class RequestPool {
198
- constructor (replicator, core) {
199
- this.replicator = replicator
200
- this.core = core
201
- this.pending = []
202
- this.seeks = []
203
- this.ranges = []
204
- this.requests = new Map()
205
- this.upgrading = null
206
- this.unforking = null
207
- this.eagerUpgrades = true
208
- this.paused = false
209
- }
210
-
211
- // We could make this faster by using some memory of each peer to store what they are involved in,
212
- // but that might not be worth the cost/complexity.
213
- clear (peer) {
214
- peer.state.inflight = 0
215
-
216
- for (const seek of this.seeks) {
217
- if (seek.request && seek.request.peer === peer) {
218
- // TODO: should prob remove cancel this request all together if no one else wants it
219
- // and nothing is inflight
220
- seek.request = null
221
- }
222
- }
223
- for (const req of this.requests.values()) {
224
- if (req.peer === peer) {
225
- req.peer = null
226
- this.pending.push(req)
227
- }
228
- }
229
- if (this.upgrading && this.upgrading.peer === peer) {
230
- this.upgrading.resolve()
231
- this.upgrading = null
232
- }
202
+ class BlockTracker {
203
+ constructor (core) {
204
+ this._core = core
205
+ this._fork = core.tree.fork
206
+
207
+ this._indexed = new Map()
208
+ this._additional = []
233
209
  }
234
210
 
235
- isRequesting (index) {
236
- return this.requests.has(index)
211
+ * [Symbol.iterator] () {
212
+ yield * this._indexed.values()
213
+ yield * this._additional
237
214
  }
238
215
 
239
- update (peer) {
240
- if (peer.state.inflight >= peer.state.maxInflight) return false
241
- if (this.paused) return false
216
+ isEmpty () {
217
+ return this._indexed.size === 0 && this._additional.length === 0
218
+ }
242
219
 
243
- if (peer.state.fork > this.core.tree.fork) {
244
- // we message them in the info recv
245
- return true
246
- }
220
+ has (fork, index) {
221
+ return this.get(fork, index) !== null
222
+ }
247
223
 
248
- // technically we'd like to run seeks at the same custom prio as reqs
249
- // but this is a lot simpler and they should run asap anyway as they
250
- // are super low cost (hash only request)
251
- for (const seek of this.seeks) {
252
- if (this._updateSeek(peer, seek)) return true
224
+ get (fork, index) {
225
+ if (this._fork === fork) return this._indexed.get(index) || null
226
+ for (const b of this._additional) {
227
+ if (b.index === index && b.fork === fork) return b
253
228
  }
229
+ return null
230
+ }
231
+
232
+ add (fork, index) {
233
+ // TODO: just rely on someone calling .update(fork) instead
234
+ if (this._fork !== this._core.tree.fork) this.update(this._core.tree.fork)
235
+
236
+ let b = this.get(fork, index)
237
+ if (b) return b
254
238
 
255
- if (this.pendingUpgrade) {
256
- if (this._updateUpgrade(peer)) return true
239
+ b = new BlockRequest(this, fork, index)
240
+
241
+ if (fork === this._fork) this._indexed.set(index, b)
242
+ else this._additional.push(b)
243
+
244
+ return b
245
+ }
246
+
247
+ remove (fork, index) {
248
+ if (this._fork === fork) {
249
+ const b = this._indexed.get(index)
250
+ this._indexed.delete(index)
251
+ return b || null
257
252
  }
258
253
 
259
- const pending = new RandomIterator(this.pending) // can be cached
260
- for (const req of pending) {
261
- if (this._updatePeer(peer, req)) {
262
- pending.dequeue()
263
- return true
264
- }
254
+ for (let i = 0; i < this._additional.length; i++) {
255
+ const b = this._additional[i]
256
+ if (b.index !== index || b.fork !== fork) continue
257
+ if (i === this._additional.length - 1) this._additional.pop()
258
+ else this._additional[i] = this._additional.pop()
259
+ return b
265
260
  }
266
261
 
267
- const ranges = new RandomIterator(this.ranges) // can be cached
268
- for (const range of ranges) {
269
- if (this._updateRange(peer, range)) return true
262
+ return null
263
+ }
264
+
265
+ update (fork) {
266
+ if (this._fork === fork) return
267
+
268
+ const additional = this._additional
269
+ this._additional = []
270
+
271
+ for (const b of this._indexed.values()) {
272
+ // TODO: this is only needed cause we hot patch the fork ids below, revert that later
273
+ if (b.fork !== this._fork) additional.push(b)
274
+ else this._additional.push(b)
270
275
  }
276
+ this._indexed.clear()
271
277
 
272
- if (this.eagerUpgrades && !this.upgrading) {
273
- return this._updateUpgrade(peer)
278
+ for (const b of additional) {
279
+ if (b.fork === fork) this._indexed.set(b.index, b)
280
+ else this._additional.push(b)
274
281
  }
275
282
 
276
- return false
283
+ this._fork = fork
277
284
  }
285
+ }
278
286
 
279
- _updateSeek (peer, seek) {
280
- if (seek.request) return false
281
- seek.request = this._requestRange(peer, seek.seeker.start, seek.seeker.end, seek.seeker.bytes)
282
- return seek.request !== null
283
- }
287
+ class Peer {
288
+ constructor (replicator, protomux, channel) {
289
+ this.core = replicator.core
290
+ this.replicator = replicator
291
+ this.stream = protomux.stream
292
+ this.protomux = protomux
293
+
294
+ this.channel = channel
295
+ this.channel.userData = this
296
+
297
+ this.wireSync = this.channel.messages[0]
298
+ this.wireRequest = this.channel.messages[1]
299
+ this.wireCancel = null
300
+ this.wireData = this.channel.messages[3]
301
+ this.wireNoData = this.channel.messages[4]
302
+ this.wireWant = this.channel.messages[5]
303
+ this.wireUnwant = this.channel.messages[6]
304
+ this.wireBitfield = this.channel.messages[7]
305
+ this.wireRange = this.channel.messages[8]
306
+ this.wireExtension = this.channel.messages[9]
284
307
 
285
- _updatePeer (peer, req) {
286
- const remote = peer.state.bitfield
287
- const local = this.core.bitfield
308
+ this.inflight = 0
309
+ this.maxInflight = DEFAULT_MAX_INFLIGHT
288
310
 
289
- if (!remote.get(req.index) || local.get(req.index)) return false
311
+ this.canUpgrade = true
290
312
 
291
- this.send(peer, req)
292
- return true
293
- }
313
+ this.needsSync = false
314
+ this.syncsProcessing = 0
294
315
 
295
- _updateRange (peer, range) {
296
- const end = range.end === -1 ? peer.state.length : range.end
297
- if (end <= range._start) return false
316
+ // TODO: tweak pipelining so that data sent BEFORE remoteOpened is not cap verified!
317
+ // we might wanna tweak that with some crypto, ie use the cap to encrypt it...
318
+ // or just be aware of that, to only push non leaky data
298
319
 
299
- if (range.linear) return !!this._requestRange(peer, range._start, end, 0)
320
+ this.remoteOpened = false
321
+ this.remoteBitfield = new RemoteBitfield()
300
322
 
301
- 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))
303
- }
323
+ this.remoteFork = 0
324
+ this.remoteLength = 0
325
+ this.remoteCanUpgrade = false
326
+ this.remoteUploading = true
327
+ this.remoteDownloading = true
328
+ this.remoteSynced = false
304
329
 
305
- _updateUpgrade (peer) {
306
- const minLength = this.pendingUpgrade
307
- ? this.pendingUpgrade.minLength
308
- : this.core.tree.length + 1
330
+ this.lengthAcked = 0
309
331
 
310
- if (this.upgrading || peer.state.length < minLength) return false
311
- this.upgrading = new UpgradeLock(peer, peer.state.length)
332
+ this.extensions = new Map()
333
+ this.lastExtensionSent = ''
334
+ this.lastExtensionRecv = ''
312
335
 
313
- const data = {
314
- fork: this.core.tree.fork,
315
- seek: null,
316
- block: null,
317
- upgrade: { start: this.core.tree.length, length: peer.state.length - this.core.tree.length }
318
- }
336
+ replicator._ifAvailable++
337
+ }
319
338
 
320
- peer.request(data)
321
- return true
339
+ signalUpgrade () {
340
+ if (this._shouldUpdateCanUpgrade() === true) this._updateCanUpgradeAndSync()
341
+ else this.sendSync()
322
342
  }
323
343
 
324
- checkTimeouts (peers) {
325
- if (!this.pendingUpgrade || this.upgrading) return
326
- if (this.pendingUpgrade.update(peers, this.core.tree.fork)) return
327
- this.pendingUpgrade.resolve(false)
328
- this.pendingUpgrade = null
344
+ broadcastRange (start, length, drop) {
345
+ this.wireRange.send({
346
+ drop,
347
+ start,
348
+ length
349
+ })
329
350
  }
330
351
 
331
- _requestRange (peer, start, end, seek) {
332
- const remote = peer.state.bitfield
333
- const local = this.core.bitfield
352
+ extension (name, message) {
353
+ this.wireExtension.send({ name: name === this.lastExtensionSent ? '' : name, message })
354
+ this.lastExtensionSent = name
355
+ }
334
356
 
335
- // TODO: use 0 instead of -1 as end=0 should never be added!
336
- if (end === -1) end = peer.state.length
357
+ onextension (message) {
358
+ const name = message.name || this.lastExtensionRecv
359
+ this.lastExtensionRecv = name
360
+ const ext = this.extensions.get(name)
361
+ if (ext) ext._onmessage({ start: 0, end: message.byteLength, buffer: message.message }, this)
362
+ }
337
363
 
338
- for (let i = start; i < end; i++) {
339
- if (!remote.get(i) || local.get(i)) continue
340
- // TODO: if this was a NO_VALUE request, retry if no blocks can be found elsewhere
341
- if (this.requests.has(i)) continue
364
+ sendSync () {
365
+ if (this.syncsProcessing !== 0) {
366
+ this.needsSync = true
367
+ return
368
+ }
342
369
 
343
- // 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)
345
- this.requests.set(i, req)
346
- this.send(peer, req)
347
- return req
370
+ if (this.core.tree.fork !== this.remoteFork) {
371
+ this.canUpgrade = false
348
372
  }
349
373
 
350
- return null
374
+ this.needsSync = false
375
+
376
+ this.wireSync.send({
377
+ fork: this.core.tree.fork,
378
+ length: this.core.tree.length,
379
+ remoteLength: this.core.tree.fork === this.remoteFork ? this.remoteLength : 0,
380
+ canUpgrade: this.canUpgrade,
381
+ uploading: true,
382
+ downloading: true
383
+ })
351
384
  }
352
385
 
353
- // send handles it's own errors so we do not need to await/catch it
354
- async send (peer, req) {
355
- req.peer = peer
356
- peer.state.inflight++ // TODO: a non value request should count less than a value one
386
+ onopen ({ capability }) {
387
+ const expected = caps.replicate(this.stream.isInitiator === false, this.replicator.key, this.stream.handshakeHash)
357
388
 
358
- // TODO: also check if remote can even upgrade us lol
359
- let needsUpgrade = peer.state.length > this.core.tree.length || !!(!this.upgrading && this.pendingUpgrade)
360
- const fork = this.core.tree.fork
361
- let upgrading = false
389
+ if (b4a.equals(capability, expected) !== true) { // TODO: change this to a rejection instead, less leakage
390
+ throw new Error('Remote sent an invalid capability')
391
+ }
362
392
 
363
- while (needsUpgrade) {
364
- if (!this.upgrading) {
365
- if (peer.state.length <= this.core.tree.length) {
366
- needsUpgrade = false
367
- break
368
- }
369
- // TODO: if the peer fails, we need to resolve the promise as well woop woop
370
- // so we need some tracking mechanics for upgrades in general.
371
- this.upgrading = new UpgradeLock(peer, peer.state.length)
372
- upgrading = true
373
- break
374
- }
375
- if (req.index < this.core.tree.length) {
376
- needsUpgrade = false
377
- break
378
- }
393
+ if (this.remoteOpened === true) return
394
+ this.remoteOpened = true
379
395
 
380
- await this.upgrading.promise
381
- needsUpgrade = peer.state.length > this.core.tree.length || !!(!this.upgrading && this.pendingUpgrade)
382
- }
396
+ this.protomux.cork()
383
397
 
384
- const data = {
385
- fork,
386
- seek: req.seek ? { bytes: req.seek } : null,
387
- block: { index: req.index, value: req.value, nodes: 0 },
388
- upgrade: needsUpgrade ? { start: this.core.tree.length, length: peer.state.length - this.core.tree.length } : null
389
- }
398
+ this.sendSync()
390
399
 
391
- if (data.block.index < this.core.tree.length || this.core.truncating > 0) {
392
- try {
393
- data.block.nodes = await this.core.tree.nodes(data.block.index * 2)
394
- } catch (err) {
395
- console.error('TODO handle me:', err.stack)
396
- }
400
+ const p = pages(this.core)
401
+
402
+ for (let index = 0; index < p.length; index++) {
403
+ this.wireBitfield.send({
404
+ start: index * this.core.bitfield.pageSize,
405
+ bitfield: p[index]
406
+ })
397
407
  }
398
408
 
399
- if (fork !== this.core.tree.fork || paused(peer, this.core.tree.fork) || this.core.truncating > 0) {
400
- if (peer.state.inflight > 0) peer.state.inflight--
401
- if (req.promises.length) { // someone is eagerly waiting for this request
402
- req.peer = null // resend on some other peer
403
- } else { // otherwise delete the request
404
- this.requests.delete(req.index)
405
- }
406
- if (upgrading) {
407
- this.upgrading.resolve()
408
- this.upgrading = null
409
- }
409
+ this.replicator._ifAvailable--
410
+ this.replicator._addPeer(this)
411
+
412
+ this.protomux.uncork()
413
+ }
414
+
415
+ onclose (isRemote) {
416
+ if (this.remoteOpened === false) {
417
+ this.replicator._ifAvailable--
418
+ this.replicator.updateAll()
410
419
  return
411
420
  }
412
421
 
413
- peer.request(data)
422
+ this.remoteOpened = false
423
+ this.replicator._removePeer(this)
414
424
  }
415
425
 
416
- async _onupgrade (proof, peer) {
417
- if (!this.upgrading || !proof.upgrade) return
418
- if (this.unforking) return
419
-
420
- await this.core.verify(proof, peer)
426
+ async onsync ({ fork, length, remoteLength, canUpgrade, uploading, downloading }) {
427
+ const lengthChanged = length !== this.remoteLength
428
+ const sameFork = (fork === this.core.tree.fork)
421
429
 
422
- // TODO: validate that we actually upgraded our length as well
423
- this.upgrading.resolve()
424
- this.upgrading = null
430
+ this.remoteSynced = true
431
+ this.remoteFork = fork
432
+ this.remoteLength = length
433
+ this.remoteCanUpgrade = canUpgrade
434
+ this.remoteUploading = uploading
435
+ this.remoteDownloading = downloading
425
436
 
426
- if (this.pendingUpgrade) {
427
- this.pendingUpgrade.resolve(true)
428
- this.pendingUpgrade = null
429
- }
437
+ this.lengthAcked = sameFork ? remoteLength : 0
438
+ this.syncsProcessing++
430
439
 
431
- if (this.seeks.length) await this._updateSeeks(null)
440
+ this.replicator._updateFork(this)
432
441
 
433
- this.update(peer)
434
- }
442
+ if (this.remoteLength > this.core.tree.length && this.lengthAcked === this.core.tree.length) {
443
+ if (this.replicator._addUpgradeMaybe() !== null) this._update()
444
+ }
435
445
 
436
- async _onfork (proof, peer) {
437
- // TODO: if proof is from a newer fork than currently unforking, restart
446
+ const upgrade = (lengthChanged === false || sameFork === false)
447
+ ? this.canUpgrade && sameFork
448
+ : await this._canUpgrade(length, fork)
438
449
 
439
- if (this.unforking) {
440
- await this.unforking.update(proof)
441
- } else {
442
- const reorg = await this.core.tree.reorg(proof)
443
- const verified = reorg.signedBy(this.core.header.signer.publicKey)
444
- if (!verified) throw new Error('Remote signature could not be verified')
445
- this.unforking = reorg
446
- }
447
-
448
- if (!this.unforking.finished) {
449
- for (let i = this.unforking.want.start; i < this.unforking.want.end; i++) {
450
- if (peer.state.bitfield.get(i)) {
451
- const data = {
452
- fork: this.unforking.fork,
453
- seek: null,
454
- block: { index: i, value: false, nodes: this.unforking.want.nodes },
455
- upgrade: null
456
- }
457
- peer.request(data)
458
- return
459
- }
460
- }
461
- return
450
+ if (length === this.remoteLength && fork === this.core.tree.fork) {
451
+ this.canUpgrade = upgrade
462
452
  }
463
453
 
464
- const reorg = this.unforking
465
- this.unforking = null
466
- await this.core.reorg(reorg)
454
+ if (--this.syncsProcessing !== 0) return // ie not latest
467
455
 
468
- // reset ranges, also need to reset seeks etc
469
- for (const r of this.ranges) {
470
- r._start = 0
456
+ if (this.needsSync === true || (this.core.tree.fork === this.remoteFork && this.core.tree.length > this.remoteLength)) {
457
+ this.signalUpgrade()
471
458
  }
472
459
 
473
- this.replicator.updateAll()
460
+ this._update()
461
+ }
474
462
 
475
- // TODO: we gotta clear out old requests as well here pointing at the old fork
463
+ _shouldUpdateCanUpgrade () {
464
+ return this.core.tree.fork === this.remoteFork &&
465
+ this.core.tree.length > this.remoteLength &&
466
+ this.canUpgrade === false &&
467
+ this.syncsProcessing === 0
476
468
  }
477
469
 
478
- async ondata (proof, peer) {
479
- // technically if the remote peer pushes a DATA someone else requested inflight can go to zero
480
- if (peer.state.inflight > 0) peer.state.inflight--
470
+ async _updateCanUpgradeAndSync () {
471
+ const len = this.core.tree.length
472
+ const fork = this.core.tree.fork
473
+
474
+ const canUpgrade = await this._canUpgrade(this.remoteLength, this.remoteFork)
481
475
 
482
- // if we get a message from another fork, maybe "unfork".
483
- if (peer.state.fork !== this.core.tree.fork) {
484
- // TODO: user should opt-in to this behaivour
485
- if (peer.state.fork > this.core.tree.fork) return this._onfork(proof, peer)
476
+ if (this.syncsProcessing > 0 || len !== this.core.tree.length || fork !== this.core.tree.fork) {
477
+ return
478
+ }
479
+ if (canUpgrade === this.canUpgrade) {
486
480
  return
487
481
  }
488
482
 
489
- // ignore incoming messages during an unfork.
490
- if (this.unforking) return
491
-
492
- if (!proof.block) return this._onupgrade(proof, peer)
483
+ this.canUpgrade = canUpgrade
484
+ this.sendSync()
485
+ }
493
486
 
494
- const { index, value } = proof.block
495
- const req = this.requests.get(index)
487
+ // Safe to call in the background - never fails
488
+ async _canUpgrade (remoteLength, remoteFork) {
489
+ if (remoteFork !== this.core.tree.fork) return false
496
490
 
497
- // no push allowed, TODO: add flag to allow pushes
498
- if (!req || req.peer !== peer || (value && !req.value) || (proof.upgrade && !this.upgrading)) return
491
+ if (remoteLength === 0) return true
492
+ if (remoteLength >= this.core.tree.length) return false
499
493
 
500
494
  try {
501
- await this.core.verify(proof, peer)
502
- } catch (err) {
503
- this.requests.delete(index)
504
- throw err
505
- }
495
+ // Rely on caching to make sure this is cheap...
496
+ const canUpgrade = await this.core.tree.upgradeable(remoteLength)
506
497
 
507
- // TODO: validate that we actually upgraded our length as well
508
- if (proof.upgrade) {
509
- this.upgrading.resolve()
510
- this.upgrading = null
498
+ if (remoteFork !== this.core.tree.fork) return false
511
499
 
512
- if (this.pendingUpgrade) {
513
- this.pendingUpgrade.resolve(true)
514
- this.pendingUpgrade = null
515
- }
500
+ return canUpgrade
501
+ } catch {
502
+ return false
516
503
  }
504
+ }
517
505
 
518
- await this._resolveRequest(req, index, value)
506
+ async _getProof (msg) {
507
+ const proof = await this.core.tree.proof(msg)
519
508
 
520
- this.update(peer)
509
+ if (proof.block) {
510
+ if (msg.fork !== this.core.tree.fork) return null
511
+ proof.block.value = await this.core.blocks.get(msg.block.index)
512
+ }
513
+
514
+ return proof
521
515
  }
522
516
 
523
- async _resolveRequest (req, index, value) {
524
- // if our request types match, clear inflight, otherwise we upgraded a hash req to a value req
525
- const resolved = req.value === !!value
526
- if (resolved) {
527
- this.requests.delete(index)
528
- req.resolve(value)
517
+ async onrequest (msg) {
518
+ let proof = null
519
+
520
+ // TODO: could still be answerable if (index, fork) is an ancestor of the current fork
521
+ if (msg.fork === this.core.tree.fork) {
522
+ try {
523
+ proof = await this._getProof(msg)
524
+ } catch (err) { // TODO: better error handling here, ie custom errors
525
+ safetyCatch(err)
526
+ }
529
527
  }
530
528
 
531
- if (this.seeks.length) await this._updateSeeks(req)
529
+ if (proof !== null) {
530
+ if (proof.block !== null) {
531
+ this.replicator.onupload(proof.block.index, proof.block.value, this)
532
+ }
532
533
 
533
- // TODO: only do this for active ranges, ie ranges with inflight reqs...
534
- for (let i = 0; i < this.ranges.length; i++) {
535
- const r = this.ranges[i]
536
- if (!r.contains(req)) continue
537
- if (!r.update(this.core.bitfield)) continue
538
- r.resolve(true)
539
- i--
534
+ this.wireData.send({
535
+ request: msg.id,
536
+ fork: msg.fork,
537
+ block: proof.block,
538
+ hash: proof.hash,
539
+ seek: proof.seek,
540
+ upgrade: proof.upgrade
541
+ })
542
+ return
540
543
  }
544
+
545
+ this.wireNoData.send({
546
+ request: msg.id
547
+ })
541
548
  }
542
549
 
543
- async _updateSeeks (req) {
544
- for (let i = 0; i < this.seeks.length; i++) {
545
- await this.seeks[i].update()
550
+ async ondata (data) {
551
+ const req = data.request > 0 ? this.replicator._inflight.get(data.request) : null
552
+ const reorg = data.fork > this.core.tree.fork
553
+
554
+ // no push atm, TODO: check if this satisfies another pending request
555
+ // allow reorg pushes tho as those are not written to storage so we'll take all the help we can get
556
+ if (req === null && reorg === false) return
557
+
558
+ if (req !== null) {
559
+ if (req.peer !== this) return
560
+ this.inflight--
561
+ this.replicator._inflight.remove(req.id)
546
562
  }
547
563
 
548
- for (let i = 0; i < this.seeks.length; i++) {
549
- const seek = this.seeks[i]
564
+ if (reorg === true) return this.replicator._onreorgdata(this, req, data)
550
565
 
551
- if (seek.finished) {
552
- if (this.seeks.length > 1 && i < this.seeks.length - 1) {
553
- this.seeks[i] = this.seeks[this.seeks.length - 1]
554
- i--
555
- }
556
- this.seeks.pop()
566
+ try {
567
+ if (!matchingRequest(req, data) || !(await this.core.verify(data, this))) {
568
+ this.replicator._onnodata(this, req)
569
+ return
557
570
  }
558
- if (req !== null && seek.request === req) seek.request = null
571
+ } catch (err) {
572
+ this.replicator._onnodata(this, req)
573
+ throw err
559
574
  }
560
- }
561
575
 
562
- async resolveBlock (index, value) {
563
- const req = this.requests.get(index)
564
- if (req) await this._resolveRequest(req, index, value)
565
- }
576
+ this.replicator._ondata(this, req, data)
566
577
 
567
- upgrade () {
568
- if (this.pendingUpgrade) return this.pendingUpgrade.createPromise()
569
- this.pendingUpgrade = new Upgrade(this.core.tree.length + 1)
570
- return this.pendingUpgrade.createPromise()
578
+ if (this._shouldUpdateCanUpgrade() === true) {
579
+ this._updateCanUpgradeAndSync()
580
+ }
571
581
  }
572
582
 
573
- range (range) {
574
- if (range.ranges === null || this.ranges.index(range) > -1) return
575
- this.ranges.push(range)
576
- range.ranges = range
583
+ onnodata ({ request }) {
584
+ const req = request > 0 ? this.replicator._inflight.get(request) : null
585
+
586
+ if (req === null || req.peer !== this) return
587
+
588
+ this.inflight--
589
+ this.replicator._inflight.remove(req.id)
590
+ this.replicator._onnodata(this, req)
577
591
  }
578
592
 
579
- seek (seeker) {
580
- const s = new Seek(seeker)
581
- this.seeks.push(s)
582
- return s.createPromise()
593
+ onwant () {
594
+ // TODO
583
595
  }
584
596
 
585
- block (index) {
586
- const e = this.requests.get(index)
597
+ onunwant () {
598
+ // TODO
599
+ }
587
600
 
588
- if (e) {
589
- if (!e.value) {
590
- e.value = true
591
- if (e.peer) this.send(e.peer, e)
592
- }
601
+ onbitfield ({ start, bitfield }) {
602
+ // TODO: tweak this to be more generic
593
603
 
594
- return e.createPromise()
604
+ if (bitfield.length < 1024) {
605
+ const buf = b4a.from(bitfield.buffer, bitfield.byteOffset, bitfield.byteLength)
606
+ const bigger = b4a.concat([buf, b4a.alloc(4096 - buf.length)])
607
+ bitfield = new Uint32Array(bigger.buffer, bigger.byteOffset, 1024)
595
608
  }
596
609
 
597
- const r = new Request(index, 0)
598
-
599
- this.requests.set(index, r)
600
- this.pending.push(r)
610
+ this.remoteBitfield.pages.set(start / this.core.bitfield.pageSize, bitfield)
601
611
 
602
- return r.createPromise()
612
+ this._update()
603
613
  }
604
- }
605
614
 
606
- module.exports = class Replicator {
607
- constructor (core, { onupdate }) {
608
- this.core = core
609
- this.peers = []
610
- this.requests = new RequestPool(this, core)
611
- this.updating = null
612
- this.pendingPeers = new Set()
613
- this.onupdate = onupdate
615
+ onrange ({ drop, start, length }) {
616
+ const has = drop === false
617
+
618
+ for (const end = start + length; start < end; start++) {
619
+ this.remoteBitfield.set(start, has)
620
+ }
621
+
622
+ if (drop === false) this._update()
614
623
  }
615
624
 
616
- static createProtocol (noiseStream) {
617
- return new Protocol(noiseStream, {
618
- protocolVersion: 0,
619
- userAgent: USER_AGENT
620
- })
625
+ onreorghint () {
626
+ // TODO
621
627
  }
622
628
 
623
- joinProtocol (protocol, key, discoveryKey) {
624
- if (protocol.isRegistered(discoveryKey)) return
625
- const peer = protocol.registerPeer(key, discoveryKey, this, new RemoteState(this.core))
626
- this.pendingPeers.add(peer)
629
+ _update () {
630
+ // TODO: if this is in a batch or similar it would be better to defer it
631
+ // we could do that with nextTick/microtick mb? (combined with a property on the session to signal read buffer mb)
632
+ this.replicator.updatePeer(this)
627
633
  }
628
634
 
629
- broadcastBlock (start) {
630
- const msg = { start, length: 1 }
631
- for (const peer of this.peers) peer.have(msg)
635
+ _makeRequest (fork, needsUpgrade) {
636
+ if (needsUpgrade === true && this.replicator._shouldUpgrade(this) === false) {
637
+ return null
638
+ }
632
639
 
633
- // in case of writable peer waiting for the block itself
634
- // we trigger the resolver to see if can't resolve it now
635
- if (this.requests.isRequesting(start)) {
636
- this._resolveBlock(start).then(noop, noop)
640
+ if (needsUpgrade === false && fork === this.core.tree.fork && this.replicator._autoUpgrade(this) === true) {
641
+ needsUpgrade = true
637
642
  }
638
- }
639
643
 
640
- broadcastInfo () {
641
- const msg = { length: this.core.tree.length, fork: this.core.tree.fork }
642
- for (const peer of this.peers) peer.info(msg)
643
- this.updateAll()
644
+ return {
645
+ peer: this,
646
+ id: 0,
647
+ fork,
648
+ block: null,
649
+ hash: null,
650
+ seek: null,
651
+ upgrade: needsUpgrade === false
652
+ ? null
653
+ : { start: this.core.tree.length, length: this.remoteLength - this.core.tree.length }
654
+ }
644
655
  }
645
656
 
646
- requestUpgrade () {
647
- const promise = this.requests.upgrade()
648
- this.updateAll()
649
- return promise
650
- }
657
+ _requestUpgrade (u) {
658
+ const req = this._makeRequest(u.fork, true)
659
+ if (req === null) return false
651
660
 
652
- requestSeek (seeker) {
653
- if (typeof seeker === 'number') seeker = this.core.tree.seek(seeker)
654
- const promise = this.requests.seek(seeker)
655
- this.updateAll()
656
- return promise
657
- }
661
+ this._send(req)
658
662
 
659
- requestBlock (index) {
660
- const promise = this.requests.block(index)
661
- this.updateAll()
662
- return promise
663
+ return true
663
664
  }
664
665
 
665
- static createRange (start, end, linear) {
666
- return new Range(start, end, linear)
667
- }
666
+ _requestSeek (s) {
667
+ if (s.seeker.start >= this.core.tree.length) {
668
+ const req = this._makeRequest(s.fork, true)
668
669
 
669
- addRange (range) {
670
- if (!range.done) {
671
- range._ranges = this.requests.ranges
672
- this.requests.ranges.push(range)
673
- if (range.update(this.core.bitfield)) range.resolve(true)
674
- }
675
- this.updateAll()
676
- }
670
+ // We need an upgrade for the seek, if non can be provided, skip
671
+ if (req === null) return false
677
672
 
678
- async _resolveBlock (index) {
679
- const value = await this.core.blocks.get(index)
680
- this.requests.resolveBlock(index, value)
681
- }
673
+ req.seek = { bytes: s.seeker.bytes }
682
674
 
683
- updateAll () {
684
- const peers = new RandomIterator(this.peers)
685
- for (const peer of peers) {
686
- if (paused(peer, this.core.tree.fork)) continue
687
- if (this.requests.update(peer)) peers.requeue()
675
+ s.inflight.push(req)
676
+ this._send(req)
677
+
678
+ return true
688
679
  }
689
- // TODO: this is a silly way of doing it
690
- if (this.pendingPeers.size === 0) this.requests.checkTimeouts(this.peers)
691
- }
692
680
 
693
- onunregister (peer, err) {
694
- const idx = this.peers.indexOf(peer)
695
- if (idx === -1) return
681
+ const len = s.seeker.end - s.seeker.start
682
+ const off = s.seeker.start + Math.floor(Math.random() * len)
696
683
 
697
- this.pendingPeers.delete(peer)
698
- this.peers.splice(idx, 1)
699
- this.requests.clear(peer)
700
- this.onupdate(false, peer)
684
+ for (let i = 0; i < len; i++) {
685
+ let index = off + i
686
+ if (index > s.seeker.end) index -= len
701
687
 
702
- this.updateAll()
703
- }
688
+ if (this.remoteBitfield.get(index) === false) continue
689
+ if (this.core.bitfield.get(index) === true) continue
704
690
 
705
- oncore (_, peer) {
706
- this.pendingPeers.delete(peer)
707
- this.peers.push(peer)
708
- this.onupdate(true, peer)
691
+ // Check if this block is currently inflight - if so pick another
692
+ const b = this.replicator._blocks.get(s.fork, index)
693
+ if (b !== null && b.inflight.length > 0) continue
709
694
 
710
- peer.info({ length: this.core.tree.length, fork: this.core.tree.fork })
695
+ // Block is not inflight, but we only want the hash, check if that is inflight
696
+ const h = this.replicator._hashes.add(s.fork, index)
697
+ if (h.inflight.length > 0) continue
711
698
 
712
- // YOLO send over all the pages for now
713
- const p = pages(this.core)
714
- for (let index = 0; index < p.length; index++) {
715
- peer.bitfield({ start: index, bitfield: p[index] })
699
+ const req = this._makeRequest(s.fork, false)
700
+
701
+ req.hash = { index: 2 * index, nodes: 0 }
702
+ req.seek = { bytes: s.seeker.bytes }
703
+
704
+ s.inflight.push(req)
705
+ h.inflight.push(req)
706
+ this._send(req)
707
+
708
+ return true
716
709
  }
717
- }
718
710
 
719
- onunknowncore (_, peer) {
720
- this.pendingPeers.delete(peer)
721
- this.updateAll()
722
- // This is a no-op because there isn't any state to dealloc currently
711
+ return false
723
712
  }
724
713
 
725
- oninfo ({ length, fork }, peer) {
726
- const len = peer.state.length
727
- const forked = peer.state.fork !== fork
714
+ // mb turn this into a YES/NO/MAYBE enum, could simplify ifavail logic
715
+ _blockAvailable (b) { // TODO: fork also
716
+ return this.remoteBitfield.get(b.index)
717
+ }
728
718
 
729
- peer.state.length = length
730
- peer.state.fork = fork
719
+ _requestBlock (b) {
720
+ if (this.remoteBitfield.get(b.index) === false) return false
731
721
 
732
- if (forked) {
733
- for (let i = peer.state.length; i < len; i++) {
734
- peer.state.bitfield.set(i, false)
735
- }
722
+ const req = this._makeRequest(b.fork, b.index >= this.core.tree.length)
723
+ if (req === null) return false
736
724
 
737
- if (fork > this.core.tree.fork && length > 0) {
738
- this.requests.clear(peer) // clear all pending requests from this peer as they forked, this can be removed if we add remote cancel
739
- peer.request({ fork, upgrade: { start: 0, length } })
740
- }
741
- }
725
+ req.block = { index: b.index, nodes: 0 }
742
726
 
743
- if (!peer.state.receivedInfo) {
744
- peer.state.receivedInfo = true
745
- }
727
+ b.inflight.push(req)
728
+ this._send(req)
746
729
 
747
- // TODO: do we need to update ALL peers here? prob not
748
- this.updateAll()
730
+ return true
749
731
  }
750
732
 
751
- onbitfield ({ start, bitfield }, peer) {
752
- if (bitfield.length < 1024) {
753
- const buf = Buffer.from(bitfield.buffer, bitfield.byteOffset, bitfield.byteLength)
754
- const bigger = Buffer.concat([buf, Buffer.alloc(4096 - buf.length)])
755
- bitfield = new Uint32Array(bigger.buffer, bigger.byteOffset, 1024)
756
- }
757
- peer.state.bitfield.pages.set(start, bitfield)
733
+ _requestRange (r) {
734
+ const end = Math.min(r.end === -1 ? this.remoteLength : r.end, this.remoteLength)
735
+ if (end < r.start || r.fork !== this.remoteFork) return false
758
736
 
759
- // TODO: do we need to update ALL peers here? prob not
760
- this.updateAll()
761
- }
737
+ const len = end - r.start
738
+ const off = r.start + (r.linear ? 0 : Math.floor(Math.random() * len))
762
739
 
763
- onhave ({ start, length }, peer) {
764
- const end = start + length
765
- for (; start < end; start++) {
766
- peer.state.bitfield.set(start, true)
767
- }
740
+ // TODO: we should weight this to request blocks < .length first
741
+ // as they are "cheaper" and will trigger an auto upgrade if possible
742
+ // If no blocks < .length is avaible then try the "needs upgrade" range
768
743
 
769
- // TODO: do we need to update ALL peers here? prob not
770
- this.updateAll()
771
- }
744
+ for (let i = 0; i < len; i++) {
745
+ let index = off + i
746
+ if (index >= end) index -= len
772
747
 
773
- async ondata (proof, peer) {
774
- try {
775
- await this.requests.ondata(proof, peer)
776
- } catch (err) {
777
- // TODO: the request pool should have this cap, so we can just bubble it up
778
- this.updateAll()
779
- throw err
780
- }
781
- }
748
+ if (r.blocks !== null) index = r.blocks[index]
749
+
750
+ if (this.remoteBitfield.get(index) === false) continue
751
+ if (this.core.bitfield.get(index) === true) continue
782
752
 
783
- async onrequest (req, peer) {
784
- const fork = req.fork || peer.state.fork
785
- if (fork !== this.core.tree.fork) return
753
+ const b = this.replicator._blocks.add(r.fork, index)
754
+ if (b.inflight.length > 0) continue
786
755
 
787
- const proof = await this.core.tree.proof(req)
756
+ const req = this._makeRequest(r.fork, index >= this.core.tree.length)
788
757
 
789
- if (req.block && req.block.value) {
790
- proof.block.value = await this.core.blocks.get(req.block.index)
758
+ // If the request cannot be satisfied, dealloc the block request if no one is subscribed to it
759
+ if (req === null) {
760
+ b.gc()
761
+ return false
762
+ }
763
+
764
+ req.block = { index, nodes: 0 }
765
+
766
+ b.inflight.push(req)
767
+ this._send(req)
768
+
769
+ return true
791
770
  }
792
771
 
793
- peer.data(proof)
772
+ return false
794
773
  }
795
- }
796
774
 
797
- function paused (peer, fork) {
798
- return peer.state.fork !== fork
799
- }
775
+ _requestForkProof (f) {
776
+ const req = this._makeRequest(f.fork, false)
800
777
 
801
- function pages (core) {
802
- const res = []
778
+ req.upgrade = { start: 0, length: this.remoteLength }
803
779
 
804
- for (let i = 0; i < core.tree.length; i += core.bitfield.pageSize) {
805
- const p = core.bitfield.page(i / core.bitfield.pageSize)
806
- res.push(p)
780
+ f.inflight.push(req)
781
+ this._send(req)
807
782
  }
808
783
 
809
- return res
810
- }
784
+ _requestForkRange (f) {
785
+ if (f.fork !== this.remoteFork || f.batch.want === null) return false
811
786
 
812
- function noop () {}
787
+ const end = Math.min(f.batch.want.end, this.remoteLength)
788
+ if (end < f.batch.want.start) return false
789
+
790
+ const len = end - f.batch.want.start
791
+ const off = f.batch.want.start + Math.floor(Math.random() * len)
792
+
793
+ for (let i = 0; i < len; i++) {
794
+ let index = off + i
795
+ if (index >= end) index -= len
796
+
797
+ if (this.remoteBitfield.get(index) === false) continue
798
+
799
+ const req = this._makeRequest(f.fork, false)
800
+
801
+ req.hash = { index: 2 * index, nodes: f.batch.want.nodes }
802
+
803
+ f.inflight.push(req)
804
+ this._send(req)
805
+
806
+ return true
807
+ }
808
+
809
+ return false
810
+ }
811
+
812
+ async _send (req) {
813
+ const fork = this.core.tree.fork
814
+
815
+ this.inflight++
816
+ this.replicator._inflight.add(req)
817
+
818
+ if (req.upgrade !== null && req.fork === fork) {
819
+ const u = this.replicator._addUpgrade()
820
+ u.inflight.push(req)
821
+ }
822
+
823
+ try {
824
+ if (req.block !== null && req.fork === fork) req.block.nodes = await this.core.tree.missingNodes(2 * req.block.index)
825
+ if (req.hash !== null && req.fork === fork) req.hash.nodes = await this.core.tree.missingNodes(req.hash.index)
826
+ } catch (err) {
827
+ this.stream.destroy(err)
828
+ return
829
+ }
830
+
831
+ this.wireRequest.send(req)
832
+ }
833
+ }
834
+
835
+ module.exports = class Replicator {
836
+ constructor (core, key, { eagerUpgrade = true, allowFork = true, onpeerupdate = noop, onupload = noop } = {}) {
837
+ this.key = key
838
+ this.discoveryKey = core.crypto.discoveryKey(key)
839
+ this.core = core
840
+ this.eagerUpgrade = eagerUpgrade
841
+ this.allowFork = allowFork
842
+ this.onpeerupdate = onpeerupdate
843
+ this.onupload = onupload
844
+ this.peers = []
845
+
846
+ this._inflight = new InflightTracker()
847
+ this._blocks = new BlockTracker(core)
848
+ this._hashes = new BlockTracker(core)
849
+
850
+ this._queued = []
851
+
852
+ this._seeks = []
853
+ this._upgrade = null
854
+ this._reorgs = []
855
+ this._ranges = []
856
+
857
+ this._ifAvailable = 0
858
+ this._updatesPending = 0
859
+ this._applyingReorg = false
860
+ }
861
+
862
+ cork () {
863
+ for (const peer of this.peers) peer.protomux.cork()
864
+ }
865
+
866
+ uncork () {
867
+ for (const peer of this.peers) peer.protomux.uncork()
868
+ }
869
+
870
+ broadcastRange (start, length, drop = false) {
871
+ for (const peer of this.peers) peer.broadcastRange(start, length, drop)
872
+ }
873
+
874
+ localUpgrade () {
875
+ for (const peer of this.peers) peer.signalUpgrade()
876
+ if (this._blocks.isEmpty() === false) this._resolveBlocksLocally()
877
+ if (this._upgrade !== null) this._resolveUpgradeRequest(null)
878
+ }
879
+
880
+ addUpgrade (session) {
881
+ if (this._upgrade !== null) {
882
+ const ref = this._upgrade.attach(session)
883
+ this._checkUpgradeIfAvailable()
884
+ return ref
885
+ }
886
+
887
+ const ref = this._addUpgrade().attach(session)
888
+
889
+ for (let i = this._reorgs.length - 1; i >= 0 && this._applyingReorg === false; i--) {
890
+ const f = this._reorgs[i]
891
+ if (f.batch !== null && f.batch.finished) {
892
+ this._applyReorg(f)
893
+ break
894
+ }
895
+ }
896
+
897
+ this.updateAll()
898
+
899
+ return ref
900
+ }
901
+
902
+ addBlock (session, index, fork = this.core.tree.fork) {
903
+ const b = this._blocks.add(fork, index)
904
+ const ref = b.attach(session)
905
+
906
+ this._queueBlock(b)
907
+ this.updateAll()
908
+
909
+ return ref
910
+ }
911
+
912
+ addSeek (session, seeker) {
913
+ const s = new SeekRequest(this._seeks, this.core.tree.fork, seeker)
914
+ const ref = s.attach(session)
915
+
916
+ this._seeks.push(s)
917
+ this.updateAll()
918
+
919
+ return ref
920
+ }
921
+
922
+ addRange (session, { start = 0, end = -1, length = toLength(start, end), blocks = null, linear = false } = {}) {
923
+ if (blocks !== null) {
924
+ if (start >= blocks.length) start = blocks.length
925
+ if (length === -1 || start + length > blocks.length) length = blocks.length - start
926
+ }
927
+
928
+ const r = new RangeRequest(this._ranges, this.core.tree.fork, start, length === -1 ? -1 : start + length, linear, blocks)
929
+ const ref = r.attach(session)
930
+
931
+ this._ranges.push(r)
932
+
933
+ // Trigger this to see if this is already resolved...
934
+ // Also auto compresses the range based on local bitfield
935
+ this._updateNonPrimary()
936
+
937
+ return ref
938
+ }
939
+
940
+ cancel (ref) {
941
+ ref.context.detach(ref)
942
+ }
943
+
944
+ clearRequests (session) {
945
+ while (session.length > 0) {
946
+ const ref = session[session.length - 1]
947
+ ref.context.detach(ref)
948
+ }
949
+
950
+ this.updateAll()
951
+ }
952
+
953
+ _addUpgradeMaybe () {
954
+ return this.eagerUpgrade === true ? this._addUpgrade() : this._upgrade
955
+ }
956
+
957
+ // TODO: this function is OVER called atm, at each updatePeer/updateAll
958
+ // instead its more efficient to only call it when the conditions in here change - ie on sync/add/remove peer
959
+ // Do this when we have more tests.
960
+ _checkUpgradeIfAvailable () {
961
+ if (this._ifAvailable > 0 || this._upgrade === null || this._upgrade.refs.length === 0) return
962
+
963
+ // check if a peer can upgrade us
964
+
965
+ for (let i = 0; i < this.peers.length; i++) {
966
+ const peer = this.peers[i]
967
+
968
+ if (peer.remoteSynced === false) return
969
+
970
+ if (this.core.tree.length === 0 && peer.remoteLength > 0) return
971
+
972
+ if (peer.remoteLength <= this._upgrade.length || peer.remoteFork !== this._upgrade.fork) continue
973
+
974
+ if (peer.syncsProcessing > 0) return
975
+
976
+ if (peer.lengthAcked !== this.core.tree.length && peer.remoteFork === this.core.tree.fork) return
977
+ if (peer.remoteCanUpgrade === true) return
978
+ }
979
+
980
+ // check if reorgs in progress...
981
+
982
+ if (this._applyingReorg === true) return
983
+
984
+ // TODO: we prob should NOT wait for inflight reorgs here, seems better to just resolve the upgrade
985
+ // and then apply the reorg on the next call in case it's slow - needs some testing in practice
986
+
987
+ for (let i = 0; i < this._reorgs.length; i++) {
988
+ const r = this._reorgs[i]
989
+ if (r.inflight.length > 0) return
990
+ }
991
+
992
+ // nothing to do, indicate no update avail
993
+
994
+ const u = this._upgrade
995
+ this._upgrade = null
996
+ u.resolve(false)
997
+ }
998
+
999
+ _addUpgrade () {
1000
+ if (this._upgrade !== null) return this._upgrade
1001
+
1002
+ // TODO: needs a reorg: true/false flag to indicate if the user requested a reorg
1003
+ this._upgrade = new UpgradeRequest(this, this.core.tree.fork, this.core.tree.length)
1004
+
1005
+ return this._upgrade
1006
+ }
1007
+
1008
+ _addReorg (fork, peer) {
1009
+ if (this.allowFork === false) return null
1010
+
1011
+ // TODO: eager gc old reorgs from the same peer
1012
+ // not super important because they'll get gc'ed when the request finishes
1013
+ // but just spam the remote can do ...
1014
+
1015
+ for (const f of this._reorgs) {
1016
+ if (f.fork > fork && f.batch !== null) return null
1017
+ if (f.fork === fork) return f
1018
+ }
1019
+
1020
+ const f = {
1021
+ fork,
1022
+ inflight: [],
1023
+ batch: null
1024
+ }
1025
+
1026
+ this._reorgs.push(f)
1027
+
1028
+ // maintain sorted by fork
1029
+ let i = this._reorgs.length - 1
1030
+ while (i > 0 && this._reorgs[i - 1].fork > fork) {
1031
+ this._reorgs[i] = this._reorgs[i - 1]
1032
+ this._reorgs[--i] = f
1033
+ }
1034
+
1035
+ return f
1036
+ }
1037
+
1038
+ _shouldUpgrade (peer) {
1039
+ if (this._upgrade !== null && this._upgrade.inflight.length > 0) return false
1040
+ return peer.remoteCanUpgrade === true &&
1041
+ peer.remoteLength > this.core.tree.length &&
1042
+ peer.lengthAcked === this.core.tree.length
1043
+ }
1044
+
1045
+ _autoUpgrade (peer) {
1046
+ return this._upgrade !== null && this._shouldUpgrade(peer)
1047
+ }
1048
+
1049
+ _addPeer (peer) {
1050
+ this.peers.push(peer)
1051
+ this.updatePeer(peer)
1052
+ this.onpeerupdate(true, peer)
1053
+ }
1054
+
1055
+ _removePeer (peer) {
1056
+ this.peers.splice(this.peers.indexOf(peer), 1)
1057
+
1058
+ for (const req of this._inflight) {
1059
+ if (req.peer !== peer) continue
1060
+ this._inflight.remove(req.id)
1061
+ this._clearRequest(peer, req)
1062
+ }
1063
+
1064
+ this.onpeerupdate(false, peer)
1065
+ this.updateAll()
1066
+ }
1067
+
1068
+ _queueBlock (b) {
1069
+ if (b.queued === true) return
1070
+ b.queued = true
1071
+ this._queued.push(b)
1072
+ }
1073
+
1074
+ // Runs in the background - not allowed to throw
1075
+ async _resolveBlocksLocally () {
1076
+ // TODO: check if fork compat etc. Requires that we pass down truncation info
1077
+
1078
+ let clear = null
1079
+
1080
+ for (const b of this._blocks) {
1081
+ if (this.core.bitfield.get(b.index) === false) continue
1082
+
1083
+ try {
1084
+ b.resolve(await this.core.blocks.get(b.index))
1085
+ } catch (err) {
1086
+ b.reject(err)
1087
+ }
1088
+
1089
+ if (clear === null) clear = []
1090
+ clear.push(b)
1091
+ }
1092
+
1093
+ if (clear === null) return
1094
+
1095
+ // Currently the block tracker does not support deletes during iteration, so we make
1096
+ // sure to clear them afterwards.
1097
+ for (const b of clear) {
1098
+ this._blocks.remove(b.fork, b.index)
1099
+ }
1100
+ }
1101
+
1102
+ _resolveBlockRequest (tracker, fork, index, value, req) {
1103
+ const b = tracker.remove(fork, index)
1104
+ if (b === null) return false
1105
+
1106
+ removeInflight(b.inflight, req)
1107
+ b.queued = false
1108
+
1109
+ b.resolve(value)
1110
+
1111
+ return true
1112
+ }
1113
+
1114
+ _resolveUpgradeRequest (req) {
1115
+ if (req !== null) removeInflight(this._upgrade.inflight, req)
1116
+
1117
+ if (this.core.tree.length === this._upgrade.length && this.core.tree.fork === this._upgrade.fork) return false
1118
+
1119
+ const u = this._upgrade
1120
+ this._upgrade = null
1121
+ u.resolve(true)
1122
+
1123
+ return true
1124
+ }
1125
+
1126
+ _clearInflightBlock (tracker, req) {
1127
+ const isBlock = tracker === this._blocks
1128
+ const index = isBlock === true ? req.block.index : req.hash.index / 2
1129
+ const b = tracker.get(req.fork, index)
1130
+
1131
+ if (b === null || removeInflight(b.inflight, req) === false) return
1132
+
1133
+ if (b.refs.length > 0 && isBlock === true) {
1134
+ this._queueBlock(b)
1135
+ return
1136
+ }
1137
+
1138
+ b.gc()
1139
+ }
1140
+
1141
+ _clearInflightUpgrade (req) {
1142
+ if (removeInflight(this._upgrade.inflight, req) === false) return
1143
+ this._upgrade.gc()
1144
+ }
1145
+
1146
+ _clearInflightSeeks (req) {
1147
+ for (const s of this._seeks) {
1148
+ if (removeInflight(s.inflight, req) === false) continue
1149
+ s.gc()
1150
+ }
1151
+ }
1152
+
1153
+ _clearInflightReorgs (req) {
1154
+ for (const r of this._reorgs) {
1155
+ removeInflight(r.inflight, req)
1156
+ }
1157
+ }
1158
+
1159
+ _clearOldReorgs (fork) {
1160
+ for (let i = 0; i < this._reorgs.length; i++) {
1161
+ const f = this._reorgs[i]
1162
+ if (f.fork >= fork) continue
1163
+ if (i === this._reorgs.length - 1) this._reorgs.pop()
1164
+ else this._reorgs[i] = this._reorgs.pop()
1165
+ i--
1166
+ }
1167
+ }
1168
+
1169
+ // "slow" updates here - async but not allowed to ever throw
1170
+ async _updateNonPrimary () {
1171
+ // Check if running, if so skip it and the running one will issue another update for us (debounce)
1172
+ while (++this._updatesPending === 1) {
1173
+ for (let i = 0; i < this._ranges.length; i++) {
1174
+ const r = this._ranges[i]
1175
+
1176
+ while (r.start < r.end && this.core.bitfield.get(mapIndex(r.blocks, r.start)) === true) r.start++
1177
+ while (r.start < r.end && this.core.bitfield.get(mapIndex(r.blocks, r.end - 1)) === true) r.end--
1178
+
1179
+ if (r.end === -1 || r.start < r.end) continue
1180
+
1181
+ if (i < this._ranges.length - 1) this._ranges[i] = this._ranges.pop()
1182
+ else this._ranges.pop()
1183
+
1184
+ i--
1185
+
1186
+ r.resolve(true)
1187
+ }
1188
+
1189
+ for (let i = 0; i < this._seeks.length; i++) {
1190
+ const s = this._seeks[i]
1191
+
1192
+ let err = null
1193
+ let res = null
1194
+
1195
+ try {
1196
+ res = await s.seeker.update()
1197
+ } catch (error) {
1198
+ err = error
1199
+ }
1200
+
1201
+ if (!res && !err) continue
1202
+
1203
+ if (i < this._seeks.length - 1) this._seeks[i] = this._seeks.pop()
1204
+ else this._seeks.pop()
1205
+
1206
+ i--
1207
+
1208
+ if (err) s.reject(err)
1209
+ else s.resolve(res)
1210
+ }
1211
+
1212
+ this.updateAll()
1213
+
1214
+ // No additional updates scheduled - return
1215
+ if (--this._updatesPending === 0) return
1216
+ // Debounce the additional updates - continue
1217
+ this._updatesPending = 0
1218
+ }
1219
+ }
1220
+
1221
+ _clearRequest (peer, req) {
1222
+ if (req.block !== null) {
1223
+ this._clearInflightBlock(this._blocks, req)
1224
+ }
1225
+
1226
+ if (req.hash !== null) {
1227
+ this._clearInflightBlock(this._hashes, req)
1228
+ }
1229
+
1230
+ if (req.upgrade !== null && this._upgrade !== null) {
1231
+ this._clearInflightUpgrade(req)
1232
+ }
1233
+
1234
+ if (this._seeks.length > 0) {
1235
+ this._clearInflightSeeks(req)
1236
+ }
1237
+
1238
+ if (this._reorgs.length > 0) {
1239
+ this._clearInflightReorgs(req)
1240
+ }
1241
+ }
1242
+
1243
+ _onnodata (peer, req) {
1244
+ this._clearRequest(peer, req)
1245
+ this.updateAll()
1246
+ }
1247
+
1248
+ _ondata (peer, req, data) {
1249
+ if (data.block !== null) {
1250
+ this._resolveBlockRequest(this._blocks, data.fork, data.block.index, data.block.value, req)
1251
+ }
1252
+
1253
+ if (data.hash !== null && (data.hash.index & 1) === 0) {
1254
+ this._resolveBlockRequest(this._hashes, data.fork, data.hash.index / 2, null, req)
1255
+ }
1256
+
1257
+ if (this._upgrade !== null) {
1258
+ this._resolveUpgradeRequest(req)
1259
+ }
1260
+
1261
+ if (this._seeks.length > 0) {
1262
+ this._clearInflightSeeks(req)
1263
+ }
1264
+
1265
+ if (this._reorgs.length > 0) {
1266
+ this._clearInflightReorgs(req)
1267
+ }
1268
+
1269
+ if (this._seeks.length > 0 || this._ranges.length > 0) this._updateNonPrimary()
1270
+ else this.updatePeer(peer)
1271
+ }
1272
+
1273
+ async _onreorgdata (peer, req, data) {
1274
+ const f = this._addReorg(data.fork, peer)
1275
+
1276
+ if (f === null) {
1277
+ this.updateAll()
1278
+ return
1279
+ }
1280
+
1281
+ removeInflight(f.inflight, req)
1282
+
1283
+ if (f.batch) {
1284
+ await f.batch.update(data)
1285
+ } else {
1286
+ f.batch = await this.core.tree.reorg(data)
1287
+
1288
+ // Remove "older" reorgs in progress as we just verified this one.
1289
+ this._clearOldReorgs(f.fork)
1290
+ }
1291
+
1292
+ if (f.batch.finished) {
1293
+ if (this._addUpgradeMaybe() !== null) {
1294
+ await this._applyReorg(f)
1295
+ }
1296
+ }
1297
+
1298
+ this.updateAll()
1299
+ }
1300
+
1301
+ async _applyReorg (f) {
1302
+ // TODO: more optimal here to check if potentially a better reorg
1303
+ // is available, ie higher fork, and request that one first.
1304
+ // This will request that one after this finishes, which is fine, but we
1305
+ // should investigate the complexity in going the other way
1306
+
1307
+ const u = this._upgrade
1308
+
1309
+ this._applyingReorg = true
1310
+ this._reorgs = [] // clear all as the nodes are against the old tree - easier
1311
+
1312
+ try {
1313
+ await this.core.reorg(f.batch, null) // TODO: null should be the first/last peer?
1314
+ } catch (err) {
1315
+ this._upgrade = null
1316
+ u.reject(err)
1317
+ }
1318
+
1319
+ this._applyingReorg = false
1320
+
1321
+ if (this._upgrade !== null) {
1322
+ this._resolveUpgradeRequest(null)
1323
+ }
1324
+
1325
+ for (const peer of this.peers) this._updateFork(peer)
1326
+
1327
+ // TODO: all the remaining is a tmp workaround until we have a flag/way for ANY_FORK
1328
+ for (const r of this._ranges) {
1329
+ r.fork = this.core.tree.fork
1330
+ r.start = r.userStart
1331
+ r.end = r.userEnd
1332
+ }
1333
+ for (const s of this._seeks) s.fork = this.core.tree.fork
1334
+ for (const b of this._blocks) b.fork = this.core.tree.fork
1335
+ this._blocks.update(this.core.tree.fork)
1336
+ this.updateAll()
1337
+ }
1338
+
1339
+ _maybeUpdate () {
1340
+ return this._upgrade !== null && this._upgrade.inflight.length === 0
1341
+ }
1342
+
1343
+ _updateFork (peer) {
1344
+ if (this._applyingReorg === true || this.allowFork === false || peer.remoteFork <= this.core.tree.fork) {
1345
+ return false
1346
+ }
1347
+
1348
+ const f = this._addReorg(peer.remoteFork, peer)
1349
+
1350
+ // TODO: one per peer is better
1351
+ if (f !== null && f.batch === null && f.inflight.length === 0) {
1352
+ return peer._requestForkProof(f)
1353
+ }
1354
+
1355
+ return false
1356
+ }
1357
+
1358
+ _updatePeer (peer) {
1359
+ if (peer.inflight >= peer.maxInflight) {
1360
+ return false
1361
+ }
1362
+
1363
+ for (const s of this._seeks) {
1364
+ if (s.inflight.length > 0) continue // TODO: one per peer is better
1365
+ if (peer._requestSeek(s) === true) {
1366
+ return true
1367
+ }
1368
+ }
1369
+
1370
+ // Implied that any block in the queue should be requested, no matter how many inflights
1371
+ const blks = new RandomIterator(this._queued)
1372
+
1373
+ for (const b of blks) {
1374
+ if (b.queued === false || peer._requestBlock(b) === true) {
1375
+ b.queued = false
1376
+ blks.dequeue()
1377
+ return true
1378
+ }
1379
+ }
1380
+
1381
+ return false
1382
+ }
1383
+
1384
+ _updatePeerNonPrimary (peer) {
1385
+ if (peer.inflight >= peer.maxInflight) {
1386
+ return false
1387
+ }
1388
+
1389
+ const ranges = new RandomIterator(this._ranges)
1390
+
1391
+ for (const r of ranges) {
1392
+ if (peer._requestRange(r) === true) {
1393
+ return true
1394
+ }
1395
+ }
1396
+
1397
+ // Iterate from newest fork to oldest fork...
1398
+ for (let i = this._reorgs.length - 1; i >= 0; i--) {
1399
+ const f = this._reorgs[i]
1400
+ if (f.batch !== null && f.inflight.length === 0 && peer._requestForkRange(f) === true) {
1401
+ return true
1402
+ }
1403
+ }
1404
+
1405
+ if (this._maybeUpdate() === true && peer._requestUpgrade(this._upgrade) === true) {
1406
+ return true
1407
+ }
1408
+
1409
+ return false
1410
+ }
1411
+
1412
+ updatePeer (peer) {
1413
+ // Quick shortcut to wait for flushing reorgs - not needed but less waisted requests
1414
+ if (this._applyingReorg === true) return
1415
+
1416
+ while (this._updatePeer(peer) === true);
1417
+ while (this._updatePeerNonPrimary(peer) === true);
1418
+
1419
+ this._checkUpgradeIfAvailable()
1420
+ }
1421
+
1422
+ updateAll () {
1423
+ // Quick shortcut to wait for flushing reorgs - not needed but less waisted requests
1424
+ if (this._applyingReorg === true) return
1425
+
1426
+ const peers = new RandomIterator(this.peers)
1427
+
1428
+ for (const peer of peers) {
1429
+ if (this._updatePeer(peer) === true) {
1430
+ peers.requeue()
1431
+ }
1432
+ }
1433
+
1434
+ // Check if we can skip the non primary check fully
1435
+ if (this._maybeUpdate() === false && this._ranges.length === 0 && this._reorgs.length === 0) {
1436
+ this._checkUpgradeIfAvailable()
1437
+ return
1438
+ }
1439
+
1440
+ for (const peer of peers.restart()) {
1441
+ if (this._updatePeerNonPrimary(peer) === true) {
1442
+ peers.requeue()
1443
+ }
1444
+ }
1445
+
1446
+ this._checkUpgradeIfAvailable()
1447
+ }
1448
+
1449
+ attachTo (protomux) {
1450
+ const makePeer = this._makePeer.bind(this, protomux)
1451
+
1452
+ protomux.pair({ protocol: 'hypercore/alpha', id: this.discoveryKey }, makePeer)
1453
+
1454
+ this._ifAvailable++
1455
+ protomux.stream.opened.then((opened) => {
1456
+ this._ifAvailable--
1457
+ if (opened) makePeer()
1458
+ this._checkUpgradeIfAvailable()
1459
+ })
1460
+ }
1461
+
1462
+ _makePeer (protomux) {
1463
+ if (protomux.opened({ protocol: 'hypercore/alpha', id: this.discoveryKey })) return false
1464
+
1465
+ const channel = protomux.createChannel({
1466
+ userData: null,
1467
+ protocol: 'hypercore/alpha',
1468
+ id: this.discoveryKey,
1469
+ handshake: m.wire.handshake,
1470
+ messages: [
1471
+ { encoding: m.wire.sync, onmessage: onwiresync },
1472
+ { encoding: m.wire.request, onmessage: onwirerequest },
1473
+ null, // oncancel
1474
+ { encoding: m.wire.data, onmessage: onwiredata },
1475
+ { encoding: m.wire.noData, onmessage: onwirenodata },
1476
+ { encoding: m.wire.want, onmessage: onwirewant },
1477
+ { encoding: m.wire.unwant, onmessage: onwireunwant },
1478
+ { encoding: m.wire.bitfield, onmessage: onwirebitfield },
1479
+ { encoding: m.wire.range, onmessage: onwirerange },
1480
+ { encoding: m.wire.extension, onmessage: onwireextension }
1481
+ ],
1482
+ onopen: onwireopen,
1483
+ onclose: onwireclose
1484
+ })
1485
+
1486
+ if (channel === null) return false
1487
+
1488
+ const peer = new Peer(this, protomux, channel)
1489
+ const stream = protomux.stream
1490
+
1491
+ peer.channel.open({
1492
+ capability: caps.replicate(stream.isInitiator, this.key, stream.handshakeHash)
1493
+ })
1494
+
1495
+ return true
1496
+ }
1497
+ }
1498
+
1499
+ function pages (core) {
1500
+ const res = []
1501
+
1502
+ for (let i = 0; i < core.tree.length; i += core.bitfield.pageSize) {
1503
+ const p = core.bitfield.page(i / core.bitfield.pageSize)
1504
+ res.push(p)
1505
+ }
1506
+
1507
+ return res
1508
+ }
1509
+
1510
+ function matchingRequest (req, data) {
1511
+ if (data.block !== null && (req.block === null || req.block.index !== data.block.index)) return false
1512
+ if (data.hash !== null && (req.hash === null || req.hash.index !== data.hash.index)) return false
1513
+ if (data.seek !== null && (req.seek === null || req.seek.bytes !== data.seek.bytes)) return false
1514
+ if (data.upgrade !== null && req.upgrade === null) return false
1515
+ return req.fork === data.fork
1516
+ }
1517
+
1518
+ function removeInflight (inf, req) {
1519
+ const i = inf.indexOf(req)
1520
+ if (i === -1) return false
1521
+ if (i < inf.length - 1) inf[i] = inf.pop()
1522
+ else inf.pop()
1523
+ return true
1524
+ }
1525
+
1526
+ function mapIndex (blocks, index) {
1527
+ return blocks === null ? index : blocks[index]
1528
+ }
1529
+
1530
+ function noop () {}
1531
+
1532
+ function toLength (start, end) {
1533
+ return end === -1 ? -1 : (end < start ? 0 : end - start)
1534
+ }
1535
+
1536
+ function onwireopen (m, c) {
1537
+ return c.userData.onopen(m)
1538
+ }
1539
+
1540
+ function onwireclose (isRemote, c) {
1541
+ return c.userData.onclose(isRemote)
1542
+ }
1543
+
1544
+ function onwiresync (m, c) {
1545
+ return c.userData.onsync(m)
1546
+ }
1547
+
1548
+ function onwirerequest (m, c) {
1549
+ return c.userData.onrequest(m)
1550
+ }
1551
+
1552
+ function onwiredata (m, c) {
1553
+ return c.userData.ondata(m)
1554
+ }
1555
+
1556
+ function onwirenodata (m, c) {
1557
+ return c.userData.onnodata(m)
1558
+ }
1559
+
1560
+ function onwirewant (m, c) {
1561
+ return c.userData.onwant(m)
1562
+ }
1563
+
1564
+ function onwireunwant (m, c) {
1565
+ return c.userData.onunwant(m)
1566
+ }
1567
+
1568
+ function onwirebitfield (m, c) {
1569
+ return c.userData.onbitfield(m)
1570
+ }
1571
+
1572
+ function onwirerange (m, c) {
1573
+ return c.userData.onrange(m)
1574
+ }
1575
+
1576
+ function onwireextension (m, c) {
1577
+ return c.userData.onextension(m)
1578
+ }