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