hypercore 10.0.0-alpha.5 → 10.0.0-alpha.52
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 +201 -118
- 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 +19 -10
- 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/test/oplog.js
DELETED
|
@@ -1,399 +0,0 @@
|
|
|
1
|
-
const p = require('path')
|
|
2
|
-
const fs = require('fs')
|
|
3
|
-
const test = require('brittle')
|
|
4
|
-
const fsctl = require('fsctl')
|
|
5
|
-
const raf = require('random-access-file')
|
|
6
|
-
const c = require('compact-encoding')
|
|
7
|
-
|
|
8
|
-
const Oplog = require('../lib/oplog')
|
|
9
|
-
|
|
10
|
-
const STORAGE_FILE_NAME = 'oplog-test-storage'
|
|
11
|
-
const STORAGE_FILE_PATH = p.join(__dirname, STORAGE_FILE_NAME)
|
|
12
|
-
const SHOULD_ERROR = Symbol('hypercore-oplog-should-error')
|
|
13
|
-
|
|
14
|
-
test.configure({ serial: true })
|
|
15
|
-
|
|
16
|
-
test('oplog - reset storage', async function (t) {
|
|
17
|
-
// just to make sure to cleanup storage if it failed half way through before
|
|
18
|
-
if (fs.existsSync(STORAGE_FILE_PATH)) await fs.promises.unlink(STORAGE_FILE_PATH)
|
|
19
|
-
t.pass('data is reset')
|
|
20
|
-
t.end()
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
test('oplog - basic append', async function (t) {
|
|
24
|
-
const storage = testStorage()
|
|
25
|
-
|
|
26
|
-
const logWr = new Oplog(storage)
|
|
27
|
-
|
|
28
|
-
await logWr.open()
|
|
29
|
-
await logWr.flush(Buffer.from('h'))
|
|
30
|
-
await logWr.append(Buffer.from('a'))
|
|
31
|
-
await logWr.append(Buffer.from('b'))
|
|
32
|
-
|
|
33
|
-
const logRd = new Oplog(storage)
|
|
34
|
-
|
|
35
|
-
{
|
|
36
|
-
const { header, entries } = await logRd.open()
|
|
37
|
-
|
|
38
|
-
t.alike(header, Buffer.from('h'))
|
|
39
|
-
t.is(entries.length, 2)
|
|
40
|
-
t.alike(entries[0], Buffer.from('a'))
|
|
41
|
-
t.alike(entries[1], Buffer.from('b'))
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
await logWr.flush(Buffer.from('i'))
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
const { header, entries } = await logRd.open()
|
|
48
|
-
|
|
49
|
-
t.alike(header, Buffer.from('i'))
|
|
50
|
-
t.is(entries.length, 0)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
await logWr.append(Buffer.from('c'))
|
|
54
|
-
|
|
55
|
-
{
|
|
56
|
-
const { header, entries } = await logRd.open()
|
|
57
|
-
|
|
58
|
-
t.alike(header, Buffer.from('i'))
|
|
59
|
-
t.is(entries.length, 1)
|
|
60
|
-
t.alike(entries[0], Buffer.from('c'))
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
await cleanup(storage)
|
|
64
|
-
t.end()
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test('oplog - custom encoding', async function (t) {
|
|
68
|
-
const storage = testStorage()
|
|
69
|
-
|
|
70
|
-
const log = new Oplog(storage, {
|
|
71
|
-
headerEncoding: c.string,
|
|
72
|
-
entryEncoding: c.uint
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
await log.open()
|
|
76
|
-
await log.flush('one header')
|
|
77
|
-
await log.append(42)
|
|
78
|
-
await log.append(43)
|
|
79
|
-
|
|
80
|
-
const { header, entries } = await log.open()
|
|
81
|
-
|
|
82
|
-
t.is(header, 'one header')
|
|
83
|
-
t.is(entries.length, 2)
|
|
84
|
-
t.is(entries[0], 42)
|
|
85
|
-
t.is(entries[1], 43)
|
|
86
|
-
|
|
87
|
-
await cleanup(storage)
|
|
88
|
-
t.end()
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
test('oplog - alternating header writes', async function (t) {
|
|
92
|
-
const storage = testStorage()
|
|
93
|
-
|
|
94
|
-
const log = new Oplog(storage)
|
|
95
|
-
|
|
96
|
-
await log.open()
|
|
97
|
-
await log.flush(Buffer.from('1'))
|
|
98
|
-
await log.flush(Buffer.from('2'))
|
|
99
|
-
|
|
100
|
-
{
|
|
101
|
-
const { header } = await log.open()
|
|
102
|
-
t.alike(header, Buffer.from('2'))
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
await log.flush(Buffer.from('1')) // Should overwrite first header
|
|
106
|
-
|
|
107
|
-
{
|
|
108
|
-
const { header } = await log.open()
|
|
109
|
-
t.alike(header, Buffer.from('1'))
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
await log.flush(Buffer.from('2')) // Should overwrite second header
|
|
113
|
-
|
|
114
|
-
{
|
|
115
|
-
const { header } = await log.open()
|
|
116
|
-
t.alike(header, Buffer.from('2'))
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
await cleanup(storage)
|
|
120
|
-
t.end()
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
test('oplog - one fully-corrupted header', async function (t) {
|
|
124
|
-
const storage = testStorage()
|
|
125
|
-
|
|
126
|
-
const log = new Oplog(storage)
|
|
127
|
-
|
|
128
|
-
await log.open()
|
|
129
|
-
await log.flush(Buffer.from('header 1'))
|
|
130
|
-
|
|
131
|
-
{
|
|
132
|
-
const { header } = await log.open()
|
|
133
|
-
t.alike(header, Buffer.from('header 1'))
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
await log.flush(Buffer.from('header 2'))
|
|
137
|
-
|
|
138
|
-
{
|
|
139
|
-
const { header } = await log.open()
|
|
140
|
-
t.alike(header, Buffer.from('header 2'))
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
await log.flush(Buffer.from('header 3')) // should overwrite first header
|
|
144
|
-
|
|
145
|
-
{
|
|
146
|
-
const { header } = await log.open()
|
|
147
|
-
t.alike(header, Buffer.from('header 3'))
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Corrupt the first header -- second header should win now
|
|
151
|
-
await new Promise((resolve, reject) => {
|
|
152
|
-
storage.write(0, Buffer.from('hello world'), err => {
|
|
153
|
-
if (err) return reject(err)
|
|
154
|
-
return resolve()
|
|
155
|
-
})
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
{
|
|
159
|
-
const { header } = await log.open()
|
|
160
|
-
t.alike(header, Buffer.from('header 2'), 'one is corrupted or partially written')
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
await cleanup(storage)
|
|
164
|
-
t.end()
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
test('oplog - header invalid checksum', async function (t) {
|
|
168
|
-
const storage = testStorage()
|
|
169
|
-
|
|
170
|
-
const log = new Oplog(storage)
|
|
171
|
-
|
|
172
|
-
await log.open()
|
|
173
|
-
await log.flush(Buffer.from('a'))
|
|
174
|
-
await log.flush(Buffer.from('b'))
|
|
175
|
-
|
|
176
|
-
{
|
|
177
|
-
const { header } = await log.open()
|
|
178
|
-
t.alike(header, Buffer.from('b'))
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Invalidate the first header's checksum -- second header should win now
|
|
182
|
-
await new Promise((resolve, reject) => {
|
|
183
|
-
storage.write(4096 + 8, Buffer.from('a'), err => {
|
|
184
|
-
if (err) return reject(err)
|
|
185
|
-
return resolve()
|
|
186
|
-
})
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
{
|
|
190
|
-
const { header } = await log.open()
|
|
191
|
-
t.alike(header, Buffer.from('a'))
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Invalidate the second header's checksum -- the hypercore is now corrupted
|
|
195
|
-
await new Promise((resolve, reject) => {
|
|
196
|
-
storage.write(8, Buffer.from('b'), err => {
|
|
197
|
-
if (err) return reject(err)
|
|
198
|
-
return resolve()
|
|
199
|
-
})
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
try {
|
|
203
|
-
await log.open()
|
|
204
|
-
t.fail('corruption should have been detected')
|
|
205
|
-
} catch {
|
|
206
|
-
t.pass('corruption was correctly detected')
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
await cleanup(storage)
|
|
210
|
-
t.end()
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
test('oplog - malformed log entry gets overwritten', async function (t) {
|
|
214
|
-
let storage = testStorage()
|
|
215
|
-
let log = new Oplog(storage)
|
|
216
|
-
|
|
217
|
-
await log.flush(Buffer.from('header'))
|
|
218
|
-
await log.append(Buffer.from('a'))
|
|
219
|
-
await log.append(Buffer.from('b'))
|
|
220
|
-
await log.close()
|
|
221
|
-
|
|
222
|
-
const offset = log.byteLength
|
|
223
|
-
|
|
224
|
-
storage = testStorage()
|
|
225
|
-
log = new Oplog(storage)
|
|
226
|
-
|
|
227
|
-
// Write a bad oplog message at the end (simulates a failed append)
|
|
228
|
-
await new Promise((resolve, reject) => {
|
|
229
|
-
storage.write(offset + 4096 * 2, Buffer.from([0, 0, 0, 0, 4, 0, 0, 0]), err => {
|
|
230
|
-
if (err) return reject(err)
|
|
231
|
-
return resolve()
|
|
232
|
-
})
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
{
|
|
236
|
-
const { entries } = await log.open()
|
|
237
|
-
|
|
238
|
-
t.is(entries.length, 2) // The partial entry should not be present
|
|
239
|
-
t.alike(entries[0], Buffer.from('a'))
|
|
240
|
-
t.alike(entries[1], Buffer.from('b'))
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Write a valid oplog message now
|
|
244
|
-
await log.append(Buffer.from('c'))
|
|
245
|
-
|
|
246
|
-
{
|
|
247
|
-
const { entries } = await log.open()
|
|
248
|
-
|
|
249
|
-
t.is(entries.length, 3) // The partial entry should not be present
|
|
250
|
-
t.alike(entries[0], Buffer.from('a'))
|
|
251
|
-
t.alike(entries[1], Buffer.from('b'))
|
|
252
|
-
t.alike(entries[2], Buffer.from('c'))
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
await cleanup(storage)
|
|
256
|
-
t.end()
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
test('oplog - log not truncated when header write fails', async function (t) {
|
|
260
|
-
const storage = failingOffsetStorage(4096 * 2)
|
|
261
|
-
|
|
262
|
-
const log = new Oplog(storage)
|
|
263
|
-
|
|
264
|
-
await log.flush(Buffer.from('header'))
|
|
265
|
-
await log.append(Buffer.from('a'))
|
|
266
|
-
await log.append(Buffer.from('b'))
|
|
267
|
-
|
|
268
|
-
// Make subsequent header writes fail
|
|
269
|
-
storage[SHOULD_ERROR](true)
|
|
270
|
-
|
|
271
|
-
// The flush should fail because the header can't be updated -- log should still have entries after this
|
|
272
|
-
try {
|
|
273
|
-
await log.flush(Buffer.from('header two'))
|
|
274
|
-
} catch (err) {
|
|
275
|
-
t.ok(err.synthetic)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
{
|
|
279
|
-
const { header, entries } = await log.open()
|
|
280
|
-
|
|
281
|
-
t.alike(header, Buffer.from('header'))
|
|
282
|
-
t.is(entries.length, 2)
|
|
283
|
-
t.alike(entries[0], Buffer.from('a'))
|
|
284
|
-
t.alike(entries[1], Buffer.from('b'))
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Re-enable header writes
|
|
288
|
-
storage[SHOULD_ERROR](false)
|
|
289
|
-
await log.flush(Buffer.from('header two')) // Should correctly truncate the oplog now
|
|
290
|
-
|
|
291
|
-
{
|
|
292
|
-
const { header, entries } = await log.open()
|
|
293
|
-
|
|
294
|
-
t.alike(header, Buffer.from('header two'))
|
|
295
|
-
t.is(entries.length, 0)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
await cleanup(storage)
|
|
299
|
-
t.end()
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
test('oplog - multi append', async function (t) {
|
|
303
|
-
const storage = testStorage()
|
|
304
|
-
|
|
305
|
-
const log = new Oplog(storage)
|
|
306
|
-
|
|
307
|
-
await log.open()
|
|
308
|
-
await log.flush(Buffer.from('a'))
|
|
309
|
-
|
|
310
|
-
await log.append([
|
|
311
|
-
Buffer.from('1'),
|
|
312
|
-
Buffer.from('22'),
|
|
313
|
-
Buffer.from('333'),
|
|
314
|
-
Buffer.from('4')
|
|
315
|
-
])
|
|
316
|
-
|
|
317
|
-
t.is(log.length, 4)
|
|
318
|
-
t.is(log.byteLength, 32 + 1 + 2 + 3 + 1)
|
|
319
|
-
|
|
320
|
-
const { header, entries } = await log.open()
|
|
321
|
-
|
|
322
|
-
t.alike(header, Buffer.from('a'))
|
|
323
|
-
t.alike(entries, [
|
|
324
|
-
Buffer.from('1'),
|
|
325
|
-
Buffer.from('22'),
|
|
326
|
-
Buffer.from('333'),
|
|
327
|
-
Buffer.from('4')
|
|
328
|
-
])
|
|
329
|
-
|
|
330
|
-
await cleanup(storage)
|
|
331
|
-
t.end()
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
test('oplog - multi append is atomic', async function (t) {
|
|
335
|
-
const storage = testStorage()
|
|
336
|
-
|
|
337
|
-
const log = new Oplog(storage)
|
|
338
|
-
|
|
339
|
-
await log.open()
|
|
340
|
-
await log.flush(Buffer.from('a'))
|
|
341
|
-
|
|
342
|
-
await log.append(Buffer.from('0'))
|
|
343
|
-
await log.append([
|
|
344
|
-
Buffer.from('1'),
|
|
345
|
-
Buffer.from('22'),
|
|
346
|
-
Buffer.from('333'),
|
|
347
|
-
Buffer.from('4')
|
|
348
|
-
])
|
|
349
|
-
|
|
350
|
-
t.is(log.length, 5)
|
|
351
|
-
t.is(log.byteLength, 40 + 1 + 1 + 2 + 3 + 1)
|
|
352
|
-
|
|
353
|
-
// Corrupt the last write, should revert the full batch
|
|
354
|
-
await new Promise((resolve, reject) => {
|
|
355
|
-
storage.write(8192 + log.byteLength - 1, Buffer.from('x'), err => {
|
|
356
|
-
if (err) return reject(err)
|
|
357
|
-
return resolve()
|
|
358
|
-
})
|
|
359
|
-
})
|
|
360
|
-
|
|
361
|
-
const { entries } = await log.open()
|
|
362
|
-
|
|
363
|
-
t.is(log.length, 1)
|
|
364
|
-
t.alike(entries, [
|
|
365
|
-
Buffer.from('0')
|
|
366
|
-
])
|
|
367
|
-
|
|
368
|
-
await cleanup(storage)
|
|
369
|
-
t.end()
|
|
370
|
-
})
|
|
371
|
-
|
|
372
|
-
function testStorage () {
|
|
373
|
-
return raf(STORAGE_FILE_NAME, { directory: __dirname, lock: fsctl.lock })
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function failingOffsetStorage (offset) {
|
|
377
|
-
let shouldError = false
|
|
378
|
-
const storage = raf(STORAGE_FILE_NAME, { directory: __dirname, lock: fsctl.lock })
|
|
379
|
-
const write = storage.write.bind(storage)
|
|
380
|
-
|
|
381
|
-
storage.write = (off, data, cb) => {
|
|
382
|
-
if (off < offset && shouldError) {
|
|
383
|
-
const err = new Error('Synthetic write failure')
|
|
384
|
-
err.synthetic = true
|
|
385
|
-
return cb(err)
|
|
386
|
-
}
|
|
387
|
-
return write(off, data, cb)
|
|
388
|
-
}
|
|
389
|
-
storage[SHOULD_ERROR] = s => {
|
|
390
|
-
shouldError = s
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return storage
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
async function cleanup (storage) {
|
|
397
|
-
await new Promise((resolve) => storage.close(() => resolve()))
|
|
398
|
-
await fs.promises.unlink(STORAGE_FILE_PATH)
|
|
399
|
-
}
|
package/test/preload.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
const crypto = require('hypercore-crypto')
|
|
2
|
-
const test = require('brittle')
|
|
3
|
-
const ram = require('random-access-memory')
|
|
4
|
-
const Hypercore = require('../')
|
|
5
|
-
|
|
6
|
-
test('preload - storage', async function (t) {
|
|
7
|
-
const core = new Hypercore(null, {
|
|
8
|
-
preload: () => {
|
|
9
|
-
return { storage: ram }
|
|
10
|
-
}
|
|
11
|
-
})
|
|
12
|
-
await core.ready()
|
|
13
|
-
|
|
14
|
-
await core.append('hello world')
|
|
15
|
-
t.is(core.length, 1)
|
|
16
|
-
t.alike(await core.get(0), Buffer.from('hello world'))
|
|
17
|
-
|
|
18
|
-
t.end()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
test('preload - from another core', async function (t) {
|
|
22
|
-
t.plan(2)
|
|
23
|
-
|
|
24
|
-
const first = new Hypercore(ram)
|
|
25
|
-
await first.ready()
|
|
26
|
-
|
|
27
|
-
const second = new Hypercore(null, {
|
|
28
|
-
preload: () => {
|
|
29
|
-
return { from: first }
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
await second.ready()
|
|
33
|
-
|
|
34
|
-
t.is(first.key, second.key)
|
|
35
|
-
t.is(first.sessions, second.sessions)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
test('preload - custom keypair', async function (t) {
|
|
39
|
-
const keyPair = crypto.keyPair()
|
|
40
|
-
const core = new Hypercore(ram, keyPair.publicKey, {
|
|
41
|
-
preload: () => {
|
|
42
|
-
return { keyPair }
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
await core.ready()
|
|
46
|
-
|
|
47
|
-
t.ok(core.writable)
|
|
48
|
-
t.is(core.key, keyPair.publicKey)
|
|
49
|
-
|
|
50
|
-
t.end()
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test('preload - sign/storage', async function (t) {
|
|
54
|
-
const keyPair = crypto.keyPair()
|
|
55
|
-
const core = new Hypercore(null, keyPair.publicKey, {
|
|
56
|
-
valueEncoding: 'utf-8',
|
|
57
|
-
preload: () => {
|
|
58
|
-
return {
|
|
59
|
-
storage: ram,
|
|
60
|
-
sign: signable => crypto.sign(signable, keyPair.secretKey)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
await core.ready()
|
|
65
|
-
|
|
66
|
-
t.ok(core.writable)
|
|
67
|
-
await core.append('hello world')
|
|
68
|
-
t.is(core.length, 1)
|
|
69
|
-
t.is(await core.get(0), 'hello world')
|
|
70
|
-
|
|
71
|
-
t.end()
|
|
72
|
-
})
|