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