hypercore 9.12.0 → 10.0.0-alpha.11
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/.github/workflows/test-node.yml +3 -4
- package/README.md +131 -404
- package/__snapshots__/test/storage.js.snapshot.cjs +15 -0
- package/examples/announce.js +19 -0
- package/examples/basic.js +10 -0
- package/examples/http.js +123 -0
- package/examples/lookup.js +20 -0
- package/index.js +365 -1600
- package/lib/bitfield.js +113 -285
- package/lib/block-encryption.js +68 -0
- package/lib/block-store.js +58 -0
- package/lib/core.js +468 -0
- package/lib/extensions.js +76 -0
- package/lib/merkle-tree.js +1110 -0
- package/lib/messages.js +571 -0
- package/lib/mutex.js +39 -0
- package/lib/oplog.js +224 -0
- package/lib/protocol.js +525 -0
- package/lib/random-iterator.js +46 -0
- package/lib/remote-bitfield.js +24 -0
- package/lib/replicator.js +857 -0
- package/lib/streams.js +39 -0
- package/package.json +44 -45
- package/test/basic.js +59 -471
- package/test/bitfield.js +48 -133
- package/test/core.js +290 -0
- package/test/encodings.js +18 -0
- package/test/encryption.js +123 -0
- package/test/extension.js +71 -0
- package/test/helpers/index.js +23 -0
- package/test/merkle-tree.js +518 -0
- package/test/mutex.js +137 -0
- package/test/oplog.js +399 -0
- package/test/preload.js +72 -0
- package/test/replicate.js +227 -824
- package/test/sessions.js +173 -0
- package/test/storage.js +31 -0
- package/test/streams.js +39 -146
- package/test/user-data.js +47 -0
- package/bench/all.sh +0 -65
- package/bench/copy-64kb-blocks.js +0 -51
- package/bench/helpers/read-throttled.js +0 -27
- package/bench/helpers/read.js +0 -47
- package/bench/helpers/write.js +0 -29
- package/bench/read-16kb-blocks-proof-throttled.js +0 -1
- package/bench/read-16kb-blocks-proof.js +0 -1
- package/bench/read-16kb-blocks-throttled.js +0 -1
- package/bench/read-16kb-blocks.js +0 -1
- package/bench/read-512b-blocks.js +0 -1
- package/bench/read-64kb-blocks-linear-batch.js +0 -18
- package/bench/read-64kb-blocks-linear.js +0 -18
- package/bench/read-64kb-blocks-proof.js +0 -1
- package/bench/read-64kb-blocks.js +0 -1
- package/bench/replicate-16kb-blocks.js +0 -19
- package/bench/replicate-64kb-blocks.js +0 -19
- package/bench/write-16kb-blocks.js +0 -1
- package/bench/write-512b-blocks.js +0 -1
- package/bench/write-64kb-blocks-static.js +0 -1
- package/bench/write-64kb-blocks.js +0 -1
- package/example.js +0 -23
- package/lib/cache.js +0 -26
- package/lib/crypto.js +0 -5
- package/lib/replicate.js +0 -829
- package/lib/safe-buffer-equals.js +0 -6
- package/lib/storage.js +0 -421
- package/lib/tree-index.js +0 -183
- package/test/ack.js +0 -306
- package/test/audit.js +0 -36
- package/test/cache.js +0 -93
- package/test/compat.js +0 -209
- package/test/copy.js +0 -377
- package/test/default-storage.js +0 -51
- package/test/extensions.js +0 -137
- package/test/get.js +0 -64
- package/test/head.js +0 -65
- package/test/helpers/create-tracking-ram.js +0 -27
- package/test/helpers/create.js +0 -6
- package/test/helpers/replicate.js +0 -4
- package/test/seek.js +0 -234
- package/test/selections.js +0 -95
- package/test/set-uploading-downloading.js +0 -91
- package/test/stats.js +0 -77
- package/test/timeouts.js +0 -22
- package/test/tree-index.js +0 -841
- package/test/update.js +0 -156
- package/test/value-encoding.js +0 -52
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
const Protocol = require('./protocol')
|
|
2
|
+
const RemoteBitfield = require('./remote-bitfield')
|
|
3
|
+
const RandomIterator = require('random-array-iterator')
|
|
4
|
+
const b4a = require('b4a')
|
|
5
|
+
|
|
6
|
+
const PKG = require('../package.json')
|
|
7
|
+
const USER_AGENT = PKG.name + '/' + PKG.version + '@nodejs'
|
|
8
|
+
|
|
9
|
+
class RemoteState {
|
|
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
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class InvertedPromise {
|
|
21
|
+
constructor (resolve, reject, index) {
|
|
22
|
+
this.index = index
|
|
23
|
+
this.resolve = resolve
|
|
24
|
+
this.reject = reject
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class Request {
|
|
29
|
+
constructor (index, seek, nodes) {
|
|
30
|
+
this.peer = null
|
|
31
|
+
this.index = index
|
|
32
|
+
this.seek = seek
|
|
33
|
+
this.value = seek === 0
|
|
34
|
+
this.nodes = nodes
|
|
35
|
+
this.promises = []
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
createPromise () {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
this.promises.push(new InvertedPromise(resolve, reject, this.promises.length))
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
resolve (val) {
|
|
45
|
+
for (let i = 0; i < this.promises.length; i++) {
|
|
46
|
+
this.promises[i].resolve(val)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
reject (err) {
|
|
51
|
+
for (let i = 0; i < this.promises.length; i++) {
|
|
52
|
+
this.promises[i].reject(err)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class Upgrade {
|
|
58
|
+
constructor (minLength) {
|
|
59
|
+
this.minLength = minLength
|
|
60
|
+
this.promises = []
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
update (peers, fork) {
|
|
64
|
+
for (const peer of peers) {
|
|
65
|
+
if (peer.state.length >= this.minLength && !paused(peer, fork)) return true
|
|
66
|
+
if (!peer.state.receivedInfo) return true
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
createPromise () {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
this.promises.push(new InvertedPromise(resolve, reject, this.promises.length))
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
resolve (val) {
|
|
79
|
+
for (let i = 0; i < this.promises.length; i++) {
|
|
80
|
+
this.promises[i].resolve(val)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
reject (err) {
|
|
85
|
+
for (let i = 0; i < this.promises.length; i++) {
|
|
86
|
+
this.promises[i].reject(err)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class UpgradeLock {
|
|
92
|
+
constructor (peer, length) {
|
|
93
|
+
this.peer = peer
|
|
94
|
+
this.length = length
|
|
95
|
+
this.resolve = null
|
|
96
|
+
this.promise = new Promise((resolve) => { this.resolve = resolve })
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class Seek {
|
|
101
|
+
constructor (seeker) {
|
|
102
|
+
this.request = null
|
|
103
|
+
this.seeker = seeker
|
|
104
|
+
this.promise = null
|
|
105
|
+
this.finished = false
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async update () {
|
|
109
|
+
const res = await this.seeker.update()
|
|
110
|
+
if (!res) return false
|
|
111
|
+
this.finished = true
|
|
112
|
+
this.promise.resolve(res)
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
createPromise () {
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
this.promise = new InvertedPromise(resolve, reject, 0)
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
class Range {
|
|
124
|
+
constructor (start, end, filter, linear) {
|
|
125
|
+
this.start = start
|
|
126
|
+
this.end = end
|
|
127
|
+
this.filter = filter
|
|
128
|
+
this.linear = !!linear
|
|
129
|
+
this.promise = null
|
|
130
|
+
this.done = false
|
|
131
|
+
|
|
132
|
+
this._inv = null
|
|
133
|
+
this._start = start // can be updated
|
|
134
|
+
this._ranges = null // set be replicator
|
|
135
|
+
this._resolved = false
|
|
136
|
+
this._error = null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
contains (req) {
|
|
140
|
+
return this._start <= req.index && req.index < this.end
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
update (bitfield) {
|
|
144
|
+
if (this.end === -1) {
|
|
145
|
+
while (bitfield.get(this._start)) this._start++
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (; this._start < this.end; this._start++) {
|
|
150
|
+
if (this.filter(this._start) && !bitfield.get(this._start)) return false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return true
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
resolve (done) {
|
|
157
|
+
this._done(null, done)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
destroy (err) {
|
|
161
|
+
this._done(err, false)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
downloaded () {
|
|
165
|
+
if (this.promise) return this.promise
|
|
166
|
+
if (!this.done) return this._makePromise()
|
|
167
|
+
if (this._error !== null) return Promise.reject(this._error)
|
|
168
|
+
return Promise.resolve(this._resolved)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
_done (err, done) {
|
|
172
|
+
if (this.done) return
|
|
173
|
+
this.done = true
|
|
174
|
+
|
|
175
|
+
if (this._ranges) {
|
|
176
|
+
const i = this._ranges.indexOf(this)
|
|
177
|
+
|
|
178
|
+
if (i === this._ranges.length - 1) this._ranges.pop()
|
|
179
|
+
else if (i > -1) this._ranges[i] = this._ranges.pop()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this._ranges = null
|
|
183
|
+
this._resolved = done
|
|
184
|
+
this._error = err
|
|
185
|
+
|
|
186
|
+
if (this._inv === null) return
|
|
187
|
+
if (err) this._inv.reject(err)
|
|
188
|
+
else this._inv.resolve(done)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
_makePromise () {
|
|
192
|
+
this.promise = new Promise((resolve, reject) => {
|
|
193
|
+
this._inv = new InvertedPromise(resolve, reject, 0)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
return this.promise
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
class RequestPool {
|
|
201
|
+
constructor (replicator, core) {
|
|
202
|
+
this.replicator = replicator
|
|
203
|
+
this.core = core
|
|
204
|
+
this.pending = []
|
|
205
|
+
this.seeks = []
|
|
206
|
+
this.ranges = []
|
|
207
|
+
this.requests = new Map()
|
|
208
|
+
this.upgrading = null
|
|
209
|
+
this.unforking = null
|
|
210
|
+
this.eagerUpgrades = true
|
|
211
|
+
this.paused = false
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// We could make this faster by using some memory of each peer to store what they are involved in,
|
|
215
|
+
// but that might not be worth the cost/complexity.
|
|
216
|
+
clear (peer) {
|
|
217
|
+
peer.state.inflight = 0
|
|
218
|
+
|
|
219
|
+
for (const seek of this.seeks) {
|
|
220
|
+
if (seek.request && seek.request.peer === peer) {
|
|
221
|
+
// TODO: should prob remove cancel this request all together if no one else wants it
|
|
222
|
+
// and nothing is inflight
|
|
223
|
+
seek.request = null
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
for (const req of this.requests.values()) {
|
|
227
|
+
if (req.peer === peer) {
|
|
228
|
+
req.peer = null
|
|
229
|
+
this.pending.push(req)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (this.upgrading && this.upgrading.peer === peer) {
|
|
233
|
+
this.upgrading.resolve()
|
|
234
|
+
this.upgrading = null
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
isRequesting (index) {
|
|
239
|
+
return this.requests.has(index)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
update (peer) {
|
|
243
|
+
if (peer.state.inflight >= peer.state.maxInflight) return false
|
|
244
|
+
if (this.paused) return false
|
|
245
|
+
|
|
246
|
+
if (peer.state.fork > this.core.tree.fork) {
|
|
247
|
+
// we message them in the info recv
|
|
248
|
+
return true
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// technically we'd like to run seeks at the same custom prio as reqs
|
|
252
|
+
// but this is a lot simpler and they should run asap anyway as they
|
|
253
|
+
// are super low cost (hash only request)
|
|
254
|
+
for (const seek of this.seeks) {
|
|
255
|
+
if (this._updateSeek(peer, seek)) return true
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (this.pendingUpgrade) {
|
|
259
|
+
if (this._updateUpgrade(peer)) return true
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const pending = new RandomIterator(this.pending) // can be cached
|
|
263
|
+
for (const req of pending) {
|
|
264
|
+
if (this._updatePeer(peer, req)) {
|
|
265
|
+
pending.dequeue()
|
|
266
|
+
return true
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const ranges = new RandomIterator(this.ranges) // can be cached
|
|
271
|
+
for (const range of ranges) {
|
|
272
|
+
if (this._updateRange(peer, range)) return true
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (this.eagerUpgrades && !this.upgrading) {
|
|
276
|
+
return this._updateUpgrade(peer)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return false
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
_updateSeek (peer, seek) {
|
|
283
|
+
if (seek.request) return false
|
|
284
|
+
// We have to snapshot the nodes here now, due to the caching of the request...
|
|
285
|
+
const nodes = log2(seek.seeker.end - seek.seeker.start)
|
|
286
|
+
seek.request = this._requestRange(peer, seek.seeker.start, seek.seeker.end, seek.seeker.bytes, nodes)
|
|
287
|
+
return seek.request !== null
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
_updatePeer (peer, req) {
|
|
291
|
+
const remote = peer.state.bitfield
|
|
292
|
+
const local = this.core.bitfield
|
|
293
|
+
|
|
294
|
+
if (!remote.get(req.index) || local.get(req.index)) return false
|
|
295
|
+
|
|
296
|
+
this.send(peer, req)
|
|
297
|
+
return true
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
_updateRange (peer, range) {
|
|
301
|
+
const end = range.end === -1 ? peer.state.length : range.end
|
|
302
|
+
if (end <= range._start) return false
|
|
303
|
+
|
|
304
|
+
if (range.linear) return !!this._requestRange(peer, range._start, end, 0, 0, range.filter)
|
|
305
|
+
|
|
306
|
+
const r = range._start + Math.floor(Math.random() * (end - range._start))
|
|
307
|
+
return !!(
|
|
308
|
+
this._requestRange(peer, r, end, 0, 0, range.filter) ||
|
|
309
|
+
this._requestRange(peer, range._start, r, 0, 0, range.filter)
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
_updateUpgrade (peer) {
|
|
314
|
+
const minLength = this.pendingUpgrade
|
|
315
|
+
? this.pendingUpgrade.minLength
|
|
316
|
+
: this.core.tree.length + 1
|
|
317
|
+
|
|
318
|
+
if (this.upgrading || peer.state.length < minLength) return false
|
|
319
|
+
this.upgrading = new UpgradeLock(peer, peer.state.length)
|
|
320
|
+
|
|
321
|
+
const data = {
|
|
322
|
+
fork: this.core.tree.fork,
|
|
323
|
+
seek: null,
|
|
324
|
+
block: null,
|
|
325
|
+
upgrade: { start: this.core.tree.length, length: peer.state.length - this.core.tree.length }
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
peer.request(data)
|
|
329
|
+
return true
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
checkTimeouts (peers) {
|
|
333
|
+
if (!this.pendingUpgrade || this.upgrading) return
|
|
334
|
+
if (this.pendingUpgrade.update(peers, this.core.tree.fork)) return
|
|
335
|
+
this.pendingUpgrade.resolve(false)
|
|
336
|
+
this.pendingUpgrade = null
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
_requestRange (peer, start, end, seek, nodes, filter = tautology) {
|
|
340
|
+
const remote = peer.state.bitfield
|
|
341
|
+
const local = this.core.bitfield
|
|
342
|
+
|
|
343
|
+
// TODO: use 0 instead of -1 as end=0 should never be added!
|
|
344
|
+
if (end === -1) end = peer.state.length
|
|
345
|
+
|
|
346
|
+
for (let i = start; i < end; i++) {
|
|
347
|
+
if (!filter(i) || !remote.get(i) || local.get(i)) continue
|
|
348
|
+
// TODO: if this was a NO_VALUE request, retry if no blocks can be found elsewhere
|
|
349
|
+
if (this.requests.has(i)) continue
|
|
350
|
+
|
|
351
|
+
// TODO: if seeking and i >= core.length, let that takes precendance in the upgrade req
|
|
352
|
+
const req = new Request(i, i < this.core.tree.length ? seek : 0, nodes)
|
|
353
|
+
this.requests.set(i, req)
|
|
354
|
+
this.send(peer, req)
|
|
355
|
+
return req
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return null
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// send handles it's own errors so we do not need to await/catch it
|
|
362
|
+
async send (peer, req) {
|
|
363
|
+
req.peer = peer
|
|
364
|
+
peer.state.inflight++ // TODO: a non value request should count less than a value one
|
|
365
|
+
|
|
366
|
+
// TODO: also check if remote can even upgrade us lol
|
|
367
|
+
let needsUpgrade = peer.state.length > this.core.tree.length || !!(!this.upgrading && this.pendingUpgrade)
|
|
368
|
+
const fork = this.core.tree.fork
|
|
369
|
+
let upgrading = false
|
|
370
|
+
|
|
371
|
+
while (needsUpgrade) {
|
|
372
|
+
if (!this.upgrading) {
|
|
373
|
+
if (peer.state.length <= this.core.tree.length) {
|
|
374
|
+
needsUpgrade = false
|
|
375
|
+
break
|
|
376
|
+
}
|
|
377
|
+
// TODO: if the peer fails, we need to resolve the promise as well woop woop
|
|
378
|
+
// so we need some tracking mechanics for upgrades in general.
|
|
379
|
+
this.upgrading = new UpgradeLock(peer, peer.state.length)
|
|
380
|
+
upgrading = true
|
|
381
|
+
break
|
|
382
|
+
}
|
|
383
|
+
if (req.index < this.core.tree.length) {
|
|
384
|
+
needsUpgrade = false
|
|
385
|
+
break
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
await this.upgrading.promise
|
|
389
|
+
needsUpgrade = peer.state.length > this.core.tree.length || !!(!this.upgrading && this.pendingUpgrade)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const data = {
|
|
393
|
+
fork,
|
|
394
|
+
seek: req.seek ? { bytes: req.seek } : null,
|
|
395
|
+
block: { index: req.index, value: req.value, nodes: 0 },
|
|
396
|
+
upgrade: needsUpgrade ? { start: this.core.tree.length, length: peer.state.length - this.core.tree.length } : null
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (data.block.index < this.core.tree.length || this.core.truncating > 0) {
|
|
400
|
+
try {
|
|
401
|
+
data.block.nodes = Math.max(req.nodes, await this.core.tree.nodes(data.block.index * 2))
|
|
402
|
+
} catch (err) {
|
|
403
|
+
console.error('TODO handle me:', err.stack)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (peer.destroyed) {
|
|
408
|
+
req.peer = null
|
|
409
|
+
this.pending.push(req)
|
|
410
|
+
if (upgrading) {
|
|
411
|
+
this.upgrading.resolve()
|
|
412
|
+
this.upgrading = null
|
|
413
|
+
}
|
|
414
|
+
this.replicator.updateAll()
|
|
415
|
+
return
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (fork !== this.core.tree.fork || paused(peer, this.core.tree.fork) || this.core.truncating > 0) {
|
|
419
|
+
if (peer.state.inflight > 0) peer.state.inflight--
|
|
420
|
+
if (req.promises.length) { // someone is eagerly waiting for this request
|
|
421
|
+
req.peer = null // resend on some other peer
|
|
422
|
+
this.pending.push(req)
|
|
423
|
+
} else { // otherwise delete the request
|
|
424
|
+
this.requests.delete(req.index)
|
|
425
|
+
}
|
|
426
|
+
if (upgrading) {
|
|
427
|
+
this.upgrading.resolve()
|
|
428
|
+
this.upgrading = null
|
|
429
|
+
}
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
peer.request(data)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async _onupgrade (proof, peer) {
|
|
437
|
+
if (!this.upgrading || !proof.upgrade) return
|
|
438
|
+
if (this.unforking) return
|
|
439
|
+
|
|
440
|
+
await this.core.verify(proof, peer)
|
|
441
|
+
|
|
442
|
+
// TODO: validate that we actually upgraded our length as well
|
|
443
|
+
this.upgrading.resolve()
|
|
444
|
+
this.upgrading = null
|
|
445
|
+
|
|
446
|
+
if (this.pendingUpgrade) {
|
|
447
|
+
this.pendingUpgrade.resolve(true)
|
|
448
|
+
this.pendingUpgrade = null
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (this.seeks.length) await this._updateSeeks(null)
|
|
452
|
+
|
|
453
|
+
this.update(peer)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async _onfork (proof, peer) {
|
|
457
|
+
// TODO: if proof is from a newer fork than currently unforking, restart
|
|
458
|
+
|
|
459
|
+
if (this.unforking) {
|
|
460
|
+
await this.unforking.update(proof)
|
|
461
|
+
} else {
|
|
462
|
+
const reorg = await this.core.tree.reorg(proof)
|
|
463
|
+
const verified = reorg.signedBy(this.core.header.signer.publicKey)
|
|
464
|
+
if (!verified) throw new Error('Remote signature could not be verified')
|
|
465
|
+
this.unforking = reorg
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (!this.unforking.finished) {
|
|
469
|
+
for (let i = this.unforking.want.start; i < this.unforking.want.end; i++) {
|
|
470
|
+
if (peer.state.bitfield.get(i)) {
|
|
471
|
+
const data = {
|
|
472
|
+
fork: this.unforking.fork,
|
|
473
|
+
seek: null,
|
|
474
|
+
block: { index: i, value: false, nodes: this.unforking.want.nodes },
|
|
475
|
+
upgrade: null
|
|
476
|
+
}
|
|
477
|
+
peer.request(data)
|
|
478
|
+
return
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const reorg = this.unforking
|
|
485
|
+
this.unforking = null
|
|
486
|
+
await this.core.reorg(reorg)
|
|
487
|
+
|
|
488
|
+
// reset ranges, also need to reset seeks etc
|
|
489
|
+
for (const r of this.ranges) {
|
|
490
|
+
r._start = 0
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
this.replicator.updateAll()
|
|
494
|
+
|
|
495
|
+
// TODO: we gotta clear out old requests as well here pointing at the old fork
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async ondata (proof, peer) {
|
|
499
|
+
// technically if the remote peer pushes a DATA someone else requested inflight can go to zero
|
|
500
|
+
if (peer.state.inflight > 0) peer.state.inflight--
|
|
501
|
+
|
|
502
|
+
// if we get a message from another fork, maybe "unfork".
|
|
503
|
+
if (peer.state.fork !== this.core.tree.fork) {
|
|
504
|
+
// TODO: user should opt-in to this behaivour
|
|
505
|
+
if (peer.state.fork > this.core.tree.fork) return this._onfork(proof, peer)
|
|
506
|
+
return
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// ignore incoming messages during an unfork.
|
|
510
|
+
if (this.unforking) return
|
|
511
|
+
|
|
512
|
+
if (!proof.block) return this._onupgrade(proof, peer)
|
|
513
|
+
|
|
514
|
+
const { index, value } = proof.block
|
|
515
|
+
const req = this.requests.get(index)
|
|
516
|
+
|
|
517
|
+
// no push allowed, TODO: add flag to allow pushes
|
|
518
|
+
if (!req || req.peer !== peer || (value && !req.value) || (proof.upgrade && !this.upgrading)) return
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
await this.core.verify(proof, peer)
|
|
522
|
+
} catch (err) {
|
|
523
|
+
this.requests.delete(index)
|
|
524
|
+
throw err
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// TODO: validate that we actually upgraded our length as well
|
|
528
|
+
if (proof.upgrade) {
|
|
529
|
+
this.upgrading.resolve()
|
|
530
|
+
this.upgrading = null
|
|
531
|
+
|
|
532
|
+
if (this.pendingUpgrade) {
|
|
533
|
+
this.pendingUpgrade.resolve(true)
|
|
534
|
+
this.pendingUpgrade = null
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
await this._resolveRequest(req, index, value)
|
|
539
|
+
|
|
540
|
+
this.update(peer)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
async _resolveRequest (req, index, value) {
|
|
544
|
+
// if our request types match, clear inflight, otherwise we upgraded a hash req to a value req
|
|
545
|
+
const resolved = req.value === !!value
|
|
546
|
+
if (resolved) {
|
|
547
|
+
this.requests.delete(index)
|
|
548
|
+
req.resolve(value)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (this.seeks.length) await this._updateSeeks(req)
|
|
552
|
+
|
|
553
|
+
// TODO: only do this for active ranges, ie ranges with inflight reqs...
|
|
554
|
+
for (let i = 0; i < this.ranges.length; i++) {
|
|
555
|
+
const r = this.ranges[i]
|
|
556
|
+
if (!r.contains(req)) continue
|
|
557
|
+
if (!r.update(this.core.bitfield)) continue
|
|
558
|
+
r.resolve(true)
|
|
559
|
+
i--
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
async _updateSeeks (req) {
|
|
564
|
+
for (let i = 0; i < this.seeks.length; i++) {
|
|
565
|
+
await this.seeks[i].update()
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
for (let i = 0; i < this.seeks.length; i++) {
|
|
569
|
+
const seek = this.seeks[i]
|
|
570
|
+
|
|
571
|
+
if (seek.finished) {
|
|
572
|
+
if (this.seeks.length > 1 && i < this.seeks.length - 1) {
|
|
573
|
+
this.seeks[i] = this.seeks[this.seeks.length - 1]
|
|
574
|
+
i--
|
|
575
|
+
}
|
|
576
|
+
this.seeks.pop()
|
|
577
|
+
}
|
|
578
|
+
if (req !== null && seek.request === req) seek.request = null
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async resolveBlock (index, value) {
|
|
583
|
+
const req = this.requests.get(index)
|
|
584
|
+
if (req) await this._resolveRequest(req, index, value)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
upgrade () {
|
|
588
|
+
if (this.pendingUpgrade) return this.pendingUpgrade.createPromise()
|
|
589
|
+
this.pendingUpgrade = new Upgrade(this.core.tree.length + 1)
|
|
590
|
+
return this.pendingUpgrade.createPromise()
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
range (range) {
|
|
594
|
+
if (range.ranges === null || this.ranges.index(range) > -1) return
|
|
595
|
+
this.ranges.push(range)
|
|
596
|
+
range.ranges = range
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
seek (seeker) {
|
|
600
|
+
const s = new Seek(seeker)
|
|
601
|
+
this.seeks.push(s)
|
|
602
|
+
return s.createPromise()
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
block (index) {
|
|
606
|
+
const e = this.requests.get(index)
|
|
607
|
+
|
|
608
|
+
if (e) {
|
|
609
|
+
if (!e.value) {
|
|
610
|
+
e.value = true
|
|
611
|
+
if (e.peer) this.send(e.peer, e)
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return e.createPromise()
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const r = new Request(index, 0, 0)
|
|
618
|
+
|
|
619
|
+
this.requests.set(index, r)
|
|
620
|
+
this.pending.push(r)
|
|
621
|
+
|
|
622
|
+
return r.createPromise()
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
module.exports = class Replicator {
|
|
627
|
+
constructor (core, { onupdate }) {
|
|
628
|
+
this.core = core
|
|
629
|
+
this.peers = []
|
|
630
|
+
this.requests = new RequestPool(this, core)
|
|
631
|
+
this.updating = null
|
|
632
|
+
this.pendingPeers = new Set()
|
|
633
|
+
this.onupdate = onupdate
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
static createProtocol (noiseStream) {
|
|
637
|
+
return new Protocol(noiseStream, {
|
|
638
|
+
protocolVersion: 0,
|
|
639
|
+
userAgent: USER_AGENT
|
|
640
|
+
})
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
joinProtocol (protocol, key, discoveryKey) {
|
|
644
|
+
if (protocol.isRegistered(discoveryKey)) return
|
|
645
|
+
const peer = protocol.registerPeer(key, discoveryKey, this, new RemoteState(this.core))
|
|
646
|
+
this.pendingPeers.add(peer)
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
broadcastBlock (start) {
|
|
650
|
+
const msg = { start, length: 1 }
|
|
651
|
+
for (const peer of this.peers) peer.have(msg)
|
|
652
|
+
|
|
653
|
+
// in case of writable peer waiting for the block itself
|
|
654
|
+
// we trigger the resolver to see if can't resolve it now
|
|
655
|
+
if (this.requests.isRequesting(start)) {
|
|
656
|
+
this._resolveBlock(start).then(noop, noop)
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
broadcastInfo () {
|
|
661
|
+
const msg = { length: this.core.tree.length, fork: this.core.tree.fork }
|
|
662
|
+
for (const peer of this.peers) peer.info(msg)
|
|
663
|
+
this.updateAll()
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
requestUpgrade () {
|
|
667
|
+
const promise = this.requests.upgrade()
|
|
668
|
+
this.updateAll()
|
|
669
|
+
return promise
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
requestSeek (seeker) {
|
|
673
|
+
if (typeof seeker === 'number') seeker = this.core.tree.seek(seeker)
|
|
674
|
+
const promise = this.requests.seek(seeker)
|
|
675
|
+
this.updateAll()
|
|
676
|
+
return promise
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
requestBlock (index) {
|
|
680
|
+
const promise = this.requests.block(index)
|
|
681
|
+
this.updateAll()
|
|
682
|
+
return promise
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
static createRange (start, end, filter, linear) {
|
|
686
|
+
// createRange(start, end)
|
|
687
|
+
if (filter === undefined) {
|
|
688
|
+
filter = tautology
|
|
689
|
+
|
|
690
|
+
// createRange(start, end, linear)
|
|
691
|
+
} else if (typeof filter === 'boolean') {
|
|
692
|
+
linear = filter
|
|
693
|
+
filter = tautology
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return new Range(start, end, filter, linear)
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
addRange (range) {
|
|
700
|
+
if (!range.done) {
|
|
701
|
+
range._ranges = this.requests.ranges
|
|
702
|
+
this.requests.ranges.push(range)
|
|
703
|
+
if (range.update(this.core.bitfield)) range.resolve(true)
|
|
704
|
+
}
|
|
705
|
+
this.updateAll()
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
async _resolveBlock (index) {
|
|
709
|
+
const value = await this.core.blocks.get(index)
|
|
710
|
+
this.requests.resolveBlock(index, value)
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
updateAll () {
|
|
714
|
+
const peers = new RandomIterator(this.peers)
|
|
715
|
+
for (const peer of peers) {
|
|
716
|
+
if (paused(peer, this.core.tree.fork)) continue
|
|
717
|
+
if (this.requests.update(peer)) peers.requeue()
|
|
718
|
+
}
|
|
719
|
+
// TODO: this is a silly way of doing it
|
|
720
|
+
if (this.pendingPeers.size === 0) this.requests.checkTimeouts(this.peers)
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
onunregister (peer, err) {
|
|
724
|
+
const idx = this.peers.indexOf(peer)
|
|
725
|
+
if (idx === -1) return
|
|
726
|
+
|
|
727
|
+
this.pendingPeers.delete(peer)
|
|
728
|
+
this.peers.splice(idx, 1)
|
|
729
|
+
this.requests.clear(peer)
|
|
730
|
+
this.onupdate(false, peer)
|
|
731
|
+
|
|
732
|
+
this.updateAll()
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
oncore (_, peer) {
|
|
736
|
+
this.pendingPeers.delete(peer)
|
|
737
|
+
this.peers.push(peer)
|
|
738
|
+
this.onupdate(true, peer)
|
|
739
|
+
|
|
740
|
+
peer.info({ length: this.core.tree.length, fork: this.core.tree.fork })
|
|
741
|
+
|
|
742
|
+
// YOLO send over all the pages for now
|
|
743
|
+
const p = pages(this.core)
|
|
744
|
+
for (let index = 0; index < p.length; index++) {
|
|
745
|
+
peer.bitfield({ start: index, bitfield: p[index] })
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
onunknowncore (_, peer) {
|
|
750
|
+
this.pendingPeers.delete(peer)
|
|
751
|
+
this.updateAll()
|
|
752
|
+
// This is a no-op because there isn't any state to dealloc currently
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
oninfo ({ length, fork }, peer) {
|
|
756
|
+
const len = peer.state.length
|
|
757
|
+
const forked = peer.state.fork !== fork
|
|
758
|
+
|
|
759
|
+
peer.state.length = length
|
|
760
|
+
peer.state.fork = fork
|
|
761
|
+
|
|
762
|
+
if (forked) {
|
|
763
|
+
for (let i = peer.state.length; i < len; i++) {
|
|
764
|
+
peer.state.bitfield.set(i, false)
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (fork > this.core.tree.fork && length > 0) {
|
|
768
|
+
this.requests.clear(peer) // clear all pending requests from this peer as they forked, this can be removed if we add remote cancel
|
|
769
|
+
peer.request({ fork, upgrade: { start: 0, length } })
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (!peer.state.receivedInfo) {
|
|
774
|
+
peer.state.receivedInfo = true
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// TODO: do we need to update ALL peers here? prob not
|
|
778
|
+
this.updateAll()
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
onbitfield ({ start, bitfield }, peer) {
|
|
782
|
+
if (bitfield.length < 1024) {
|
|
783
|
+
const buf = b4a.from(bitfield.buffer, bitfield.byteOffset, bitfield.byteLength)
|
|
784
|
+
const bigger = b4a.concat([buf, b4a.alloc(4096 - buf.length)])
|
|
785
|
+
bitfield = new Uint32Array(bigger.buffer, bigger.byteOffset, 1024)
|
|
786
|
+
}
|
|
787
|
+
peer.state.bitfield.pages.set(start, bitfield)
|
|
788
|
+
|
|
789
|
+
// TODO: do we need to update ALL peers here? prob not
|
|
790
|
+
this.updateAll()
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
onhave ({ start, length }, peer) {
|
|
794
|
+
const end = start + length
|
|
795
|
+
for (; start < end; start++) {
|
|
796
|
+
peer.state.bitfield.set(start, true)
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// TODO: do we need to update ALL peers here? prob not
|
|
800
|
+
this.updateAll()
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
async ondata (proof, peer) {
|
|
804
|
+
try {
|
|
805
|
+
await this.requests.ondata(proof, peer)
|
|
806
|
+
} catch (err) {
|
|
807
|
+
// TODO: the request pool should have this cap, so we can just bubble it up
|
|
808
|
+
this.updateAll()
|
|
809
|
+
throw err
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
async onrequest (req, peer) {
|
|
814
|
+
const fork = req.fork || peer.state.fork
|
|
815
|
+
if (fork !== this.core.tree.fork) return
|
|
816
|
+
|
|
817
|
+
const proof = await this.core.tree.proof(req)
|
|
818
|
+
|
|
819
|
+
if (req.block && req.block.value) {
|
|
820
|
+
proof.block.value = await this.core.blocks.get(req.block.index)
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
peer.data(proof)
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function paused (peer, fork) {
|
|
828
|
+
return peer.state.fork !== fork
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function pages (core) {
|
|
832
|
+
const res = []
|
|
833
|
+
|
|
834
|
+
for (let i = 0; i < core.tree.length; i += core.bitfield.pageSize) {
|
|
835
|
+
const p = core.bitfield.page(i / core.bitfield.pageSize)
|
|
836
|
+
res.push(p)
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return res
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function noop () {}
|
|
843
|
+
|
|
844
|
+
function tautology () {
|
|
845
|
+
return true
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function log2 (n) {
|
|
849
|
+
let res = 1
|
|
850
|
+
|
|
851
|
+
while (n > 2) {
|
|
852
|
+
n /= 2
|
|
853
|
+
res++
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
return res
|
|
857
|
+
}
|