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