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