hypercore 10.0.0-alpha.2 → 10.0.0-alpha.23
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 +34 -2
- package/index.js +268 -127
- package/lib/bitfield.js +3 -2
- package/lib/block-encryption.js +68 -0
- package/lib/block-store.js +3 -1
- package/lib/core.js +6 -3
- package/lib/merkle-tree.js +62 -35
- package/lib/oplog.js +4 -3
- package/lib/protocol.js +69 -8
- package/lib/replicator.js +62 -16
- package/lib/streams.js +56 -0
- package/package.json +14 -7
- package/.github/workflows/test-node.yml +0 -24
- package/UPGRADE.md +0 -9
- package/examples/announce.js +0 -19
- package/examples/basic.js +0 -10
- package/examples/http.js +0 -123
- package/examples/lookup.js +0 -20
- package/test/basic.js +0 -78
- package/test/bitfield.js +0 -71
- package/test/core.js +0 -290
- package/test/encodings.js +0 -18
- package/test/extension.js +0 -71
- package/test/helpers/index.js +0 -23
- package/test/merkle-tree.js +0 -518
- package/test/mutex.js +0 -137
- package/test/oplog.js +0 -399
- package/test/preload.js +0 -72
- package/test/replicate.js +0 -296
- package/test/sessions.js +0 -173
- package/test/user-data.js +0 -47
package/README.md
CHANGED
|
@@ -63,12 +63,16 @@ Note that `tree`, `data`, and `bitfield` are normally heavily sparse files.
|
|
|
63
63
|
createIfMissing: true, // create a new Hypercore key pair if none was present in storage
|
|
64
64
|
overwrite: false, // overwrite any old Hypercore that might already exist
|
|
65
65
|
valueEncoding: 'json' | 'utf-8' | 'binary', // defaults to binary
|
|
66
|
-
|
|
66
|
+
encodeBatch: batch => { ... }, // optionally apply an encoding to complete batches
|
|
67
|
+
keyPair: kp, // optionally pass the public key and secret key as a key pair
|
|
68
|
+
encryptionKey: k // optionally pass an encryption key to enable block encryption
|
|
67
69
|
}
|
|
68
70
|
```
|
|
69
71
|
|
|
70
72
|
You can also set valueEncoding to any [abstract-encoding](https://github.com/mafintosh/abstract-encoding) or [compact-encoding](https://github.com/compact-encoding) instance.
|
|
71
73
|
|
|
74
|
+
valueEncodings will be applied to individually blocks, even if you append batches. If you want to control encoding at the batch-level, you can use the `encodeBatch` option, which is a function that takes a batch and returns a binary-encoded batch. If you provide a custom valueEncoding, it will not be applied prior to `encodeBatch`.
|
|
75
|
+
|
|
72
76
|
#### `const seq = await core.append(block)`
|
|
73
77
|
|
|
74
78
|
Append a block of data (or an array of blocks) to the core.
|
|
@@ -83,7 +87,7 @@ Options include
|
|
|
83
87
|
|
|
84
88
|
``` js
|
|
85
89
|
{
|
|
86
|
-
wait: true, // wait for
|
|
90
|
+
wait: true, // wait for block to be downloaded
|
|
87
91
|
onwait: () => {}, // hook that is called if the get is waiting for download
|
|
88
92
|
timeout: 0, // wait at max some milliseconds (0 means no timeout)
|
|
89
93
|
valueEncoding: 'json' | 'utf-8' | 'binary' // defaults to the core's valueEncoding
|
|
@@ -97,6 +101,19 @@ Truncate the core to a smaller length.
|
|
|
97
101
|
Per default this will update the fork id of the core to `+ 1`, but you can set the fork id you prefer with the option.
|
|
98
102
|
Note that the fork id should be monotonely incrementing.
|
|
99
103
|
|
|
104
|
+
#### `const stream = core.createReadStream([options])`
|
|
105
|
+
|
|
106
|
+
Make a read stream. Options include:
|
|
107
|
+
|
|
108
|
+
``` js
|
|
109
|
+
{
|
|
110
|
+
start: 0,
|
|
111
|
+
end: core.length,
|
|
112
|
+
live: false,
|
|
113
|
+
snapshot: true // auto set end to core.length on open or update it on every read
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
100
117
|
#### `const range = core.download([range])`
|
|
101
118
|
|
|
102
119
|
Download a range of data.
|
|
@@ -113,6 +130,7 @@ A range can have the following properties:
|
|
|
113
130
|
{
|
|
114
131
|
start: startIndex,
|
|
115
132
|
end: nonInclusiveEndIndex,
|
|
133
|
+
blocks: [index1, index2, ...],
|
|
116
134
|
linear: false // download range linearly and not randomly
|
|
117
135
|
}
|
|
118
136
|
```
|
|
@@ -125,6 +143,12 @@ To download the full core continously (often referred to as non sparse mode) do
|
|
|
125
143
|
core.download({ start: 0, end: -1 })
|
|
126
144
|
```
|
|
127
145
|
|
|
146
|
+
To downloaded a discrete range of blocks pass a list of indices.
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
core.download({ blocks: [4, 9, 7] });
|
|
150
|
+
```
|
|
151
|
+
|
|
128
152
|
To cancel downloading a range simply destroy the range instance.
|
|
129
153
|
|
|
130
154
|
``` js
|
|
@@ -195,6 +219,10 @@ In contrast to `core.key` this key does not allow you to verify the data but can
|
|
|
195
219
|
|
|
196
220
|
Populated after `ready` has been emitted. Will be `null` before the event.
|
|
197
221
|
|
|
222
|
+
#### `core.encryptionKey`
|
|
223
|
+
|
|
224
|
+
Buffer containing the optional block encryption key of this core. Will be `null` unless block encryption is enabled.
|
|
225
|
+
|
|
198
226
|
#### `core.length`
|
|
199
227
|
|
|
200
228
|
How many blocks of data are available on this core?
|
|
@@ -213,6 +241,10 @@ What is the current fork id of this core?
|
|
|
213
241
|
|
|
214
242
|
Populated after `ready` has been emitted. Will be `0` before the event.
|
|
215
243
|
|
|
244
|
+
#### `core.padding`
|
|
245
|
+
|
|
246
|
+
How much padding is applied to each block of this core? Will be `0` unless block encryption is enabled.
|
|
247
|
+
|
|
216
248
|
#### `const stream = core.replicate(isInitiatorOrReplicationStream)`
|
|
217
249
|
|
|
218
250
|
Create a replication stream. You should pipe this to another Hypercore instance.
|
package/index.js
CHANGED
|
@@ -3,6 +3,8 @@ const raf = require('random-access-file')
|
|
|
3
3
|
const isOptions = require('is-options')
|
|
4
4
|
const hypercoreCrypto = require('hypercore-crypto')
|
|
5
5
|
const c = require('compact-encoding')
|
|
6
|
+
const b4a = require('b4a')
|
|
7
|
+
const Xache = require('xache')
|
|
6
8
|
const NoiseSecretStream = require('@hyperswarm/secret-stream')
|
|
7
9
|
const codecs = require('codecs')
|
|
8
10
|
|
|
@@ -11,6 +13,8 @@ const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
|
|
|
11
13
|
const Replicator = require('./lib/replicator')
|
|
12
14
|
const Extensions = require('./lib/extensions')
|
|
13
15
|
const Core = require('./lib/core')
|
|
16
|
+
const BlockEncryption = require('./lib/block-encryption')
|
|
17
|
+
const { ReadStream, WriteStream } = require('./lib/streams')
|
|
14
18
|
|
|
15
19
|
const promises = Symbol.for('hypercore.promises')
|
|
16
20
|
const inspect = Symbol.for('nodejs.util.inspect.custom')
|
|
@@ -27,14 +31,17 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
27
31
|
opts = key
|
|
28
32
|
key = null
|
|
29
33
|
}
|
|
34
|
+
|
|
30
35
|
if (key && typeof key === 'string') {
|
|
31
|
-
key =
|
|
36
|
+
key = b4a.from(key, 'hex')
|
|
32
37
|
}
|
|
33
|
-
|
|
38
|
+
|
|
39
|
+
if (!opts) opts = {}
|
|
40
|
+
|
|
41
|
+
if (!opts.crypto && key && key.byteLength !== 32) {
|
|
34
42
|
throw new Error('Hypercore key should be 32 bytes')
|
|
35
43
|
}
|
|
36
44
|
|
|
37
|
-
if (!opts) opts = {}
|
|
38
45
|
if (!storage) storage = opts.storage
|
|
39
46
|
|
|
40
47
|
this[promises] = true
|
|
@@ -43,9 +50,13 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
43
50
|
this.crypto = opts.crypto || hypercoreCrypto
|
|
44
51
|
this.core = null
|
|
45
52
|
this.replicator = null
|
|
53
|
+
this.encryption = null
|
|
46
54
|
this.extensions = opts.extensions || new Extensions()
|
|
55
|
+
this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
|
|
47
56
|
|
|
48
57
|
this.valueEncoding = null
|
|
58
|
+
this.encodeBatch = null
|
|
59
|
+
|
|
49
60
|
this.key = key || null
|
|
50
61
|
this.discoveryKey = null
|
|
51
62
|
this.readable = true
|
|
@@ -57,8 +68,10 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
57
68
|
this.autoClose = !!opts.autoClose
|
|
58
69
|
|
|
59
70
|
this.closing = null
|
|
60
|
-
this.opening =
|
|
71
|
+
this.opening = this._openSession(key, storage, opts)
|
|
61
72
|
this.opening.catch(noop)
|
|
73
|
+
|
|
74
|
+
this._preappend = preappend.bind(this)
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
[inspect] (depth, opts) {
|
|
@@ -79,9 +92,28 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
79
92
|
indent + ')'
|
|
80
93
|
}
|
|
81
94
|
|
|
82
|
-
static createProtocolStream (isInitiator, opts) {
|
|
83
|
-
|
|
84
|
-
|
|
95
|
+
static createProtocolStream (isInitiator, opts = {}) {
|
|
96
|
+
let outerStream = isStream(isInitiator)
|
|
97
|
+
? isInitiator
|
|
98
|
+
: opts.stream
|
|
99
|
+
let noiseStream = null
|
|
100
|
+
|
|
101
|
+
if (outerStream) {
|
|
102
|
+
noiseStream = outerStream.noiseStream
|
|
103
|
+
} else {
|
|
104
|
+
noiseStream = new NoiseSecretStream(isInitiator, null, opts)
|
|
105
|
+
outerStream = noiseStream.rawStream
|
|
106
|
+
}
|
|
107
|
+
if (!noiseStream) throw new Error('Invalid stream')
|
|
108
|
+
|
|
109
|
+
if (!noiseStream.userData) {
|
|
110
|
+
const protocol = Replicator.createProtocol(noiseStream, opts)
|
|
111
|
+
if (opts.keepAlive !== false) protocol.setKeepAlive(true)
|
|
112
|
+
noiseStream.userData = protocol
|
|
113
|
+
noiseStream.on('error', noop) // All noise errors already propagate through outerStream
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return outerStream
|
|
85
117
|
}
|
|
86
118
|
|
|
87
119
|
static defaultStorage (storage, opts = {}) {
|
|
@@ -91,7 +123,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
91
123
|
return function createFile (name) {
|
|
92
124
|
const locked = name === toLock || name.endsWith('/' + toLock)
|
|
93
125
|
const lock = locked ? fsctl.lock : null
|
|
94
|
-
const sparse = locked ? null : fsctl.sparse
|
|
126
|
+
const sparse = locked ? null : null // fsctl.sparse, disable sparse on windows - seems to fail for some people. TODO: investigate
|
|
95
127
|
return raf(name, { directory, lock, sparse })
|
|
96
128
|
}
|
|
97
129
|
}
|
|
@@ -104,39 +136,128 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
104
136
|
}
|
|
105
137
|
|
|
106
138
|
const Clz = opts.class || Hypercore
|
|
107
|
-
const keyPair = opts.keyPair && opts.keyPair.secretKey && { ...opts.keyPair }
|
|
108
|
-
|
|
109
|
-
// This only works if the hypercore was fully loaded,
|
|
110
|
-
// but we only do this to validate the keypair to help catch bugs so yolo
|
|
111
|
-
if (this.key && keyPair) keyPair.publicKey = this.key
|
|
112
|
-
|
|
113
139
|
const s = new Clz(this.storage, this.key, {
|
|
114
140
|
...opts,
|
|
115
|
-
sign: opts.sign || (keyPair && keyPair.secretKey && Core.createSigner(this.crypto, keyPair)) || this.sign,
|
|
116
|
-
valueEncoding: this.valueEncoding,
|
|
117
141
|
extensions: this.extensions,
|
|
118
142
|
_opening: this.opening,
|
|
119
143
|
_sessions: this.sessions
|
|
120
144
|
})
|
|
121
145
|
|
|
122
|
-
s.
|
|
146
|
+
s._passCapabilities(this)
|
|
123
147
|
this.sessions.push(s)
|
|
124
148
|
|
|
125
149
|
return s
|
|
126
150
|
}
|
|
127
151
|
|
|
128
|
-
|
|
152
|
+
_passCapabilities (o) {
|
|
129
153
|
if (!this.sign) this.sign = o.sign
|
|
130
154
|
this.crypto = o.crypto
|
|
131
|
-
this.opened = o.opened
|
|
132
155
|
this.key = o.key
|
|
133
156
|
this.discoveryKey = o.discoveryKey
|
|
134
157
|
this.core = o.core
|
|
135
158
|
this.replicator = o.replicator
|
|
159
|
+
this.encryption = o.encryption
|
|
136
160
|
this.writable = !!this.sign
|
|
137
161
|
this.autoClose = o.autoClose
|
|
138
162
|
}
|
|
139
163
|
|
|
164
|
+
async _openFromExisting (from, opts) {
|
|
165
|
+
await from.opening
|
|
166
|
+
|
|
167
|
+
for (const [name, ext] of this.extensions) {
|
|
168
|
+
from.extensions.register(name, null, ext)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this._passCapabilities(from)
|
|
172
|
+
this.extensions = from.extensions
|
|
173
|
+
this.sessions = from.sessions
|
|
174
|
+
this.storage = from.storage
|
|
175
|
+
|
|
176
|
+
this.sessions.push(this)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async _openSession (key, storage, opts) {
|
|
180
|
+
const isFirst = !opts._opening
|
|
181
|
+
|
|
182
|
+
if (!isFirst) await opts._opening
|
|
183
|
+
if (opts.preload) opts = { ...opts, ...(await opts.preload()) }
|
|
184
|
+
|
|
185
|
+
const keyPair = (key && opts.keyPair)
|
|
186
|
+
? { ...opts.keyPair, publicKey: key }
|
|
187
|
+
: key
|
|
188
|
+
? { publicKey: key, secretKey: null }
|
|
189
|
+
: opts.keyPair
|
|
190
|
+
|
|
191
|
+
// This only works if the hypercore was fully loaded,
|
|
192
|
+
// but we only do this to validate the keypair to help catch bugs so yolo
|
|
193
|
+
if (this.key && keyPair) keyPair.publicKey = this.key
|
|
194
|
+
|
|
195
|
+
if (opts.sign) {
|
|
196
|
+
this.sign = opts.sign
|
|
197
|
+
} else if (keyPair && keyPair.secretKey) {
|
|
198
|
+
this.sign = Core.createSigner(this.crypto, keyPair)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (isFirst) {
|
|
202
|
+
await this._openCapabilities(keyPair, storage, opts)
|
|
203
|
+
// Only the root session should pass capabilities to other sessions.
|
|
204
|
+
for (let i = 0; i < this.sessions.length; i++) {
|
|
205
|
+
const s = this.sessions[i]
|
|
206
|
+
if (s !== this) s._passCapabilities(this)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!this.sign) this.sign = this.core.defaultSign
|
|
211
|
+
this.writable = !!this.sign
|
|
212
|
+
|
|
213
|
+
if (opts.valueEncoding) {
|
|
214
|
+
this.valueEncoding = c.from(codecs(opts.valueEncoding))
|
|
215
|
+
}
|
|
216
|
+
if (opts.encodeBatch) {
|
|
217
|
+
this.encodeBatch = opts.encodeBatch
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// This is a hidden option that's only used by Corestore.
|
|
221
|
+
// It's required so that corestore can load a name from userData before 'ready' is emitted.
|
|
222
|
+
if (opts._preready) await opts._preready(this)
|
|
223
|
+
|
|
224
|
+
this.opened = true
|
|
225
|
+
this.emit('ready')
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async _openCapabilities (keyPair, storage, opts) {
|
|
229
|
+
if (opts.from) return this._openFromExisting(opts.from, opts)
|
|
230
|
+
|
|
231
|
+
this.storage = Hypercore.defaultStorage(opts.storage || storage)
|
|
232
|
+
|
|
233
|
+
this.core = await Core.open(this.storage, {
|
|
234
|
+
createIfMissing: opts.createIfMissing,
|
|
235
|
+
overwrite: opts.overwrite,
|
|
236
|
+
keyPair,
|
|
237
|
+
crypto: this.crypto,
|
|
238
|
+
onupdate: this._oncoreupdate.bind(this)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
if (opts.userData) {
|
|
242
|
+
for (const [key, value] of Object.entries(opts.userData)) {
|
|
243
|
+
await this.core.userData(key, value)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
this.replicator = new Replicator(this.core, {
|
|
248
|
+
onupdate: this._onpeerupdate.bind(this)
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
|
|
252
|
+
this.key = this.core.header.signer.publicKey
|
|
253
|
+
|
|
254
|
+
if (!this.encryption && opts.encryptionKey) {
|
|
255
|
+
this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
this.extensions.attach(this.replicator)
|
|
259
|
+
}
|
|
260
|
+
|
|
140
261
|
close () {
|
|
141
262
|
if (this.closing) return this.closing
|
|
142
263
|
this.closing = this._close()
|
|
@@ -168,33 +289,17 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
168
289
|
}
|
|
169
290
|
|
|
170
291
|
replicate (isInitiator, opts = {}) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
: opts.stream
|
|
174
|
-
let noiseStream = null
|
|
175
|
-
|
|
176
|
-
if (outerStream) {
|
|
177
|
-
noiseStream = outerStream.noiseStream
|
|
178
|
-
} else {
|
|
179
|
-
outerStream = Hypercore.createProtocolStream(isInitiator, opts)
|
|
180
|
-
noiseStream = outerStream.noiseStream
|
|
181
|
-
}
|
|
182
|
-
if (!noiseStream) throw new Error('Invalid stream passed to replicate')
|
|
183
|
-
|
|
184
|
-
if (!noiseStream.userData) {
|
|
185
|
-
const protocol = Replicator.createProtocol(noiseStream)
|
|
186
|
-
noiseStream.userData = protocol
|
|
187
|
-
noiseStream.on('error', noop) // All noise errors already propagate through outerStream
|
|
188
|
-
}
|
|
189
|
-
|
|
292
|
+
const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
|
|
293
|
+
const noiseStream = protocolStream.noiseStream
|
|
190
294
|
const protocol = noiseStream.userData
|
|
295
|
+
|
|
191
296
|
if (this.opened) {
|
|
192
297
|
this.replicator.joinProtocol(protocol, this.key, this.discoveryKey)
|
|
193
298
|
} else {
|
|
194
299
|
this.opening.then(() => this.replicator.joinProtocol(protocol, this.key, this.discoveryKey), protocol.destroy.bind(protocol))
|
|
195
300
|
}
|
|
196
301
|
|
|
197
|
-
return
|
|
302
|
+
return protocolStream
|
|
198
303
|
}
|
|
199
304
|
|
|
200
305
|
get length () {
|
|
@@ -202,7 +307,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
202
307
|
}
|
|
203
308
|
|
|
204
309
|
get byteLength () {
|
|
205
|
-
return this.core === null ? 0 : this.core.tree.byteLength
|
|
310
|
+
return this.core === null ? 0 : this.core.tree.byteLength - (this.core.tree.length * this.padding)
|
|
206
311
|
}
|
|
207
312
|
|
|
208
313
|
get fork () {
|
|
@@ -213,76 +318,28 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
213
318
|
return this.replicator === null ? [] : this.replicator.peers
|
|
214
319
|
}
|
|
215
320
|
|
|
216
|
-
|
|
217
|
-
return this.
|
|
321
|
+
get encryptionKey () {
|
|
322
|
+
return this.encryption && this.encryption.key
|
|
218
323
|
}
|
|
219
324
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
this.valueEncoding = opts.valueEncoding ? c.from(codecs(opts.valueEncoding)) : null
|
|
224
|
-
|
|
225
|
-
const keyPair = (key && opts.keyPair)
|
|
226
|
-
? { ...opts.keyPair, publicKey: key }
|
|
227
|
-
: key
|
|
228
|
-
? { publicKey: key, secretKey: null }
|
|
229
|
-
: opts.keyPair
|
|
230
|
-
|
|
231
|
-
if (opts.from) {
|
|
232
|
-
const from = opts.from
|
|
233
|
-
await from.opening
|
|
234
|
-
for (const [name, ext] of this.extensions) from.extensions.register(name, null, ext)
|
|
235
|
-
this._initSession(from)
|
|
236
|
-
this.extensions = from.extensions
|
|
237
|
-
this.sessions = from.sessions
|
|
238
|
-
this.storage = from.storage
|
|
239
|
-
if (!this.sign) this.sign = opts.sign || ((keyPair && keyPair.secretKey) ? Core.createSigner(this.crypto, keyPair) : null)
|
|
240
|
-
this.writable = !!this.sign
|
|
241
|
-
this.sessions.push(this)
|
|
242
|
-
return
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (!this.storage) this.storage = Hypercore.defaultStorage(opts.storage || storage)
|
|
246
|
-
|
|
247
|
-
this.core = await Core.open(this.storage, {
|
|
248
|
-
keyPair,
|
|
249
|
-
crypto: this.crypto,
|
|
250
|
-
onupdate: this._oncoreupdate.bind(this)
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
if (opts.userData) {
|
|
254
|
-
for (const [key, value] of Object.entries(opts.userData)) {
|
|
255
|
-
await this.core.userData(key, value)
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
this.replicator = new Replicator(this.core, {
|
|
260
|
-
onupdate: this._onpeerupdate.bind(this)
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
if (!this.sign) this.sign = opts.sign || this.core.defaultSign
|
|
264
|
-
|
|
265
|
-
this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
|
|
266
|
-
this.key = this.core.header.signer.publicKey
|
|
267
|
-
this.writable = !!this.sign
|
|
268
|
-
|
|
269
|
-
this.extensions.attach(this.replicator)
|
|
270
|
-
this.opened = true
|
|
271
|
-
|
|
272
|
-
if (opts.postload) await opts.postload(this)
|
|
325
|
+
get padding () {
|
|
326
|
+
return this.encryption === null ? 0 : this.encryption.padding
|
|
327
|
+
}
|
|
273
328
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (s !== this) s._initSession(this)
|
|
277
|
-
s.emit('ready')
|
|
278
|
-
}
|
|
329
|
+
ready () {
|
|
330
|
+
return this.opening
|
|
279
331
|
}
|
|
280
332
|
|
|
281
333
|
_oncoreupdate (status, bitfield, value, from) {
|
|
282
334
|
if (status !== 0) {
|
|
283
335
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
284
|
-
if ((status & 0b10) !== 0)
|
|
285
|
-
|
|
336
|
+
if ((status & 0b10) !== 0) {
|
|
337
|
+
if (this.cache) this.cache.clear()
|
|
338
|
+
this.sessions[i].emit('truncate', this.core.tree.fork)
|
|
339
|
+
}
|
|
340
|
+
if ((status & 0b01) !== 0) {
|
|
341
|
+
this.sessions[i].emit('append')
|
|
342
|
+
}
|
|
286
343
|
}
|
|
287
344
|
|
|
288
345
|
this.replicator.broadcastInfo()
|
|
@@ -295,8 +352,10 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
295
352
|
}
|
|
296
353
|
|
|
297
354
|
if (value) {
|
|
355
|
+
const byteLength = value.byteLength - this.padding
|
|
356
|
+
|
|
298
357
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
299
|
-
this.sessions[i].emit('download', bitfield.start,
|
|
358
|
+
this.sessions[i].emit('download', bitfield.start, byteLength, from)
|
|
300
359
|
}
|
|
301
360
|
}
|
|
302
361
|
}
|
|
@@ -333,7 +392,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
333
392
|
async seek (bytes) {
|
|
334
393
|
if (this.opened === false) await this.opening
|
|
335
394
|
|
|
336
|
-
const s = this.core.tree.seek(bytes)
|
|
395
|
+
const s = this.core.tree.seek(bytes, this.padding)
|
|
337
396
|
|
|
338
397
|
return (await s.update()) || this.replicator.requestSeek(s)
|
|
339
398
|
}
|
|
@@ -346,22 +405,61 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
346
405
|
|
|
347
406
|
async get (index, opts) {
|
|
348
407
|
if (this.opened === false) await this.opening
|
|
408
|
+
const c = this.cache && this.cache.get(index)
|
|
409
|
+
if (c) return c
|
|
410
|
+
const fork = this.core.tree.fork
|
|
411
|
+
const b = await this._get(index, opts)
|
|
412
|
+
if (this.cache && fork === this.core.tree.fork && b) this.cache.set(index, b)
|
|
413
|
+
return b
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async _get (index, opts) {
|
|
349
417
|
const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
|
|
350
418
|
|
|
351
|
-
|
|
352
|
-
if (opts && opts.onwait) opts.onwait(index)
|
|
419
|
+
let block
|
|
353
420
|
|
|
354
|
-
|
|
421
|
+
if (this.core.bitfield.get(index)) {
|
|
422
|
+
block = await this.core.blocks.get(index)
|
|
423
|
+
} else {
|
|
424
|
+
if (opts && opts.wait === false) return null
|
|
425
|
+
if (opts && opts.onwait) opts.onwait(index)
|
|
426
|
+
block = await this.replicator.requestBlock(index)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (this.encryption) this.encryption.decrypt(index, block)
|
|
430
|
+
return this._decode(encoding, block)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
createReadStream (opts) {
|
|
434
|
+
return new ReadStream(this, opts)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
createWriteStream (opts) {
|
|
438
|
+
return new WriteStream(this, opts)
|
|
355
439
|
}
|
|
356
440
|
|
|
357
441
|
download (range) {
|
|
358
|
-
const start = (range && range.start) || 0
|
|
359
|
-
const end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
|
|
360
442
|
const linear = !!(range && range.linear)
|
|
361
443
|
|
|
362
|
-
|
|
444
|
+
let start
|
|
445
|
+
let end
|
|
446
|
+
let filter
|
|
447
|
+
|
|
448
|
+
if (range && range.blocks) {
|
|
449
|
+
const blocks = range.blocks instanceof Set
|
|
450
|
+
? range.blocks
|
|
451
|
+
: new Set(range.blocks)
|
|
452
|
+
|
|
453
|
+
start = range.start || (blocks.size ? min(range.blocks) : 0)
|
|
454
|
+
end = range.end || (blocks.size ? max(range.blocks) + 1 : 0)
|
|
455
|
+
|
|
456
|
+
filter = (i) => blocks.has(i)
|
|
457
|
+
} else {
|
|
458
|
+
start = (range && range.start) || 0
|
|
459
|
+
end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
|
|
460
|
+
}
|
|
363
461
|
|
|
364
|
-
const r = Replicator.createRange(start, end, linear)
|
|
462
|
+
const r = Replicator.createRange(start, end, filter, linear)
|
|
365
463
|
|
|
366
464
|
if (this.opened) this.replicator.addRange(r)
|
|
367
465
|
else this.opening.then(() => this.replicator.addRange(r), noop)
|
|
@@ -394,22 +492,19 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
394
492
|
if (this.opened === false) await this.opening
|
|
395
493
|
if (this.writable === false) throw new Error('Core is not writable')
|
|
396
494
|
|
|
397
|
-
|
|
398
|
-
const buffers = new Array(blks.length)
|
|
495
|
+
blocks = Array.isArray(blocks) ? blocks : [blocks]
|
|
399
496
|
|
|
400
|
-
|
|
401
|
-
const blk = blks[i]
|
|
497
|
+
const preappend = this.encryption && this._preappend
|
|
402
498
|
|
|
403
|
-
|
|
404
|
-
? blk
|
|
405
|
-
: this.valueEncoding
|
|
406
|
-
? c.encode(this.valueEncoding, blk)
|
|
407
|
-
: Buffer.from(blk)
|
|
499
|
+
const buffers = this.encodeBatch !== null ? this.encodeBatch(blocks) : new Array(blocks.length)
|
|
408
500
|
|
|
409
|
-
|
|
501
|
+
if (this.encodeBatch === null) {
|
|
502
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
503
|
+
buffers[i] = this._encode(this.valueEncoding, blocks[i])
|
|
504
|
+
}
|
|
410
505
|
}
|
|
411
506
|
|
|
412
|
-
return await this.core.append(buffers, this.sign)
|
|
507
|
+
return await this.core.append(buffers, this.sign, { preappend })
|
|
413
508
|
}
|
|
414
509
|
|
|
415
510
|
registerExtension (name, handlers) {
|
|
@@ -420,14 +515,38 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
420
515
|
onextensionupdate () {
|
|
421
516
|
if (this.replicator !== null) this.replicator.broadcastOptions()
|
|
422
517
|
}
|
|
423
|
-
}
|
|
424
518
|
|
|
425
|
-
|
|
519
|
+
_encode (enc, val) {
|
|
520
|
+
const state = { start: this.padding, end: this.padding, buffer: null }
|
|
521
|
+
|
|
522
|
+
if (b4a.isBuffer(val)) {
|
|
523
|
+
if (state.start === 0) return val
|
|
524
|
+
state.end += val.byteLength
|
|
525
|
+
} else if (enc) {
|
|
526
|
+
enc.preencode(state, val)
|
|
527
|
+
} else {
|
|
528
|
+
val = b4a.from(val)
|
|
529
|
+
if (state.start === 0) return val
|
|
530
|
+
state.end += val.byteLength
|
|
531
|
+
}
|
|
426
532
|
|
|
427
|
-
|
|
428
|
-
|
|
533
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
534
|
+
|
|
535
|
+
if (enc) enc.encode(state, val)
|
|
536
|
+
else state.buffer.set(val, state.start)
|
|
537
|
+
|
|
538
|
+
return state.buffer
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
_decode (enc, block) {
|
|
542
|
+
block = block.subarray(this.padding)
|
|
543
|
+
if (enc) return c.decode(enc, block)
|
|
544
|
+
return block
|
|
545
|
+
}
|
|
429
546
|
}
|
|
430
547
|
|
|
548
|
+
function noop () {}
|
|
549
|
+
|
|
431
550
|
function isStream (s) {
|
|
432
551
|
return typeof s === 'object' && s && typeof s.pipe === 'function'
|
|
433
552
|
}
|
|
@@ -441,5 +560,27 @@ function requireMaybe (name) {
|
|
|
441
560
|
}
|
|
442
561
|
|
|
443
562
|
function toHex (buf) {
|
|
444
|
-
return buf &&
|
|
563
|
+
return buf && b4a.toString(buf, 'hex')
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function reduce (iter, fn, acc) {
|
|
567
|
+
for (const item of iter) acc = fn(acc, item)
|
|
568
|
+
return acc
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function min (arr) {
|
|
572
|
+
return reduce(arr, (a, b) => Math.min(a, b), Infinity)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function max (arr) {
|
|
576
|
+
return reduce(arr, (a, b) => Math.max(a, b), -Infinity)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function preappend (blocks) {
|
|
580
|
+
const offset = this.core.tree.length
|
|
581
|
+
const fork = this.core.tree.fork
|
|
582
|
+
|
|
583
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
584
|
+
this.encryption.encrypt(offset + i, blocks[i], fork)
|
|
585
|
+
}
|
|
445
586
|
}
|
package/lib/bitfield.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// TODO: needs massive improvements obvs
|
|
2
2
|
|
|
3
3
|
const BigSparseArray = require('big-sparse-array')
|
|
4
|
+
const b4a = require('b4a')
|
|
4
5
|
|
|
5
6
|
class FixedBitfield {
|
|
6
7
|
constructor (index, bitfield) {
|
|
@@ -116,9 +117,9 @@ module.exports = class Bitfield {
|
|
|
116
117
|
let error = null
|
|
117
118
|
|
|
118
119
|
for (const page of this.unflushed) {
|
|
119
|
-
const
|
|
120
|
+
const buf = b4a.from(page.bitfield.buffer, page.bitfield.byteOffset, page.bitfield.byteLength)
|
|
120
121
|
page.dirty = false
|
|
121
|
-
this.storage.write(page.index * 4096,
|
|
122
|
+
this.storage.write(page.index * 4096, buf, done)
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
function done (err) {
|