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