hypercore 10.0.0-alpha.2 → 10.0.0-alpha.20
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 +266 -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 +31 -7
- package/lib/replicator.js +60 -15
- 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)
|
|
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,126 @@ 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
|
+
keyPair,
|
|
235
|
+
crypto: this.crypto,
|
|
236
|
+
onupdate: this._oncoreupdate.bind(this)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
if (opts.userData) {
|
|
240
|
+
for (const [key, value] of Object.entries(opts.userData)) {
|
|
241
|
+
await this.core.userData(key, value)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this.replicator = new Replicator(this.core, {
|
|
246
|
+
onupdate: this._onpeerupdate.bind(this)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
|
|
250
|
+
this.key = this.core.header.signer.publicKey
|
|
251
|
+
|
|
252
|
+
if (!this.encryption && opts.encryptionKey) {
|
|
253
|
+
this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.extensions.attach(this.replicator)
|
|
257
|
+
}
|
|
258
|
+
|
|
140
259
|
close () {
|
|
141
260
|
if (this.closing) return this.closing
|
|
142
261
|
this.closing = this._close()
|
|
@@ -168,33 +287,17 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
168
287
|
}
|
|
169
288
|
|
|
170
289
|
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
|
-
|
|
290
|
+
const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
|
|
291
|
+
const noiseStream = protocolStream.noiseStream
|
|
190
292
|
const protocol = noiseStream.userData
|
|
293
|
+
|
|
191
294
|
if (this.opened) {
|
|
192
295
|
this.replicator.joinProtocol(protocol, this.key, this.discoveryKey)
|
|
193
296
|
} else {
|
|
194
297
|
this.opening.then(() => this.replicator.joinProtocol(protocol, this.key, this.discoveryKey), protocol.destroy.bind(protocol))
|
|
195
298
|
}
|
|
196
299
|
|
|
197
|
-
return
|
|
300
|
+
return protocolStream
|
|
198
301
|
}
|
|
199
302
|
|
|
200
303
|
get length () {
|
|
@@ -202,7 +305,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
202
305
|
}
|
|
203
306
|
|
|
204
307
|
get byteLength () {
|
|
205
|
-
return this.core === null ? 0 : this.core.tree.byteLength
|
|
308
|
+
return this.core === null ? 0 : this.core.tree.byteLength - (this.core.tree.length * this.padding)
|
|
206
309
|
}
|
|
207
310
|
|
|
208
311
|
get fork () {
|
|
@@ -213,76 +316,28 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
213
316
|
return this.replicator === null ? [] : this.replicator.peers
|
|
214
317
|
}
|
|
215
318
|
|
|
216
|
-
|
|
217
|
-
return this.
|
|
319
|
+
get encryptionKey () {
|
|
320
|
+
return this.encryption && this.encryption.key
|
|
218
321
|
}
|
|
219
322
|
|
|
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)
|
|
323
|
+
get padding () {
|
|
324
|
+
return this.encryption === null ? 0 : this.encryption.padding
|
|
325
|
+
}
|
|
273
326
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (s !== this) s._initSession(this)
|
|
277
|
-
s.emit('ready')
|
|
278
|
-
}
|
|
327
|
+
ready () {
|
|
328
|
+
return this.opening
|
|
279
329
|
}
|
|
280
330
|
|
|
281
331
|
_oncoreupdate (status, bitfield, value, from) {
|
|
282
332
|
if (status !== 0) {
|
|
283
333
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
284
|
-
if ((status & 0b10) !== 0)
|
|
285
|
-
|
|
334
|
+
if ((status & 0b10) !== 0) {
|
|
335
|
+
if (this.cache) this.cache.clear()
|
|
336
|
+
this.sessions[i].emit('truncate', this.core.tree.fork)
|
|
337
|
+
}
|
|
338
|
+
if ((status & 0b01) !== 0) {
|
|
339
|
+
this.sessions[i].emit('append')
|
|
340
|
+
}
|
|
286
341
|
}
|
|
287
342
|
|
|
288
343
|
this.replicator.broadcastInfo()
|
|
@@ -295,8 +350,10 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
295
350
|
}
|
|
296
351
|
|
|
297
352
|
if (value) {
|
|
353
|
+
const byteLength = value.byteLength - this.padding
|
|
354
|
+
|
|
298
355
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
299
|
-
this.sessions[i].emit('download', bitfield.start,
|
|
356
|
+
this.sessions[i].emit('download', bitfield.start, byteLength, from)
|
|
300
357
|
}
|
|
301
358
|
}
|
|
302
359
|
}
|
|
@@ -333,7 +390,7 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
333
390
|
async seek (bytes) {
|
|
334
391
|
if (this.opened === false) await this.opening
|
|
335
392
|
|
|
336
|
-
const s = this.core.tree.seek(bytes)
|
|
393
|
+
const s = this.core.tree.seek(bytes, this.padding)
|
|
337
394
|
|
|
338
395
|
return (await s.update()) || this.replicator.requestSeek(s)
|
|
339
396
|
}
|
|
@@ -346,22 +403,61 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
346
403
|
|
|
347
404
|
async get (index, opts) {
|
|
348
405
|
if (this.opened === false) await this.opening
|
|
406
|
+
const c = this.cache && this.cache.get(index)
|
|
407
|
+
if (c) return c
|
|
408
|
+
const fork = this.core.tree.fork
|
|
409
|
+
const b = await this._get(index, opts)
|
|
410
|
+
if (this.cache && fork === this.core.tree.fork && b) this.cache.set(index, b)
|
|
411
|
+
return b
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async _get (index, opts) {
|
|
349
415
|
const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
|
|
350
416
|
|
|
351
|
-
|
|
352
|
-
if (opts && opts.onwait) opts.onwait(index)
|
|
417
|
+
let block
|
|
353
418
|
|
|
354
|
-
|
|
419
|
+
if (this.core.bitfield.get(index)) {
|
|
420
|
+
block = await this.core.blocks.get(index)
|
|
421
|
+
} else {
|
|
422
|
+
if (opts && opts.wait === false) return null
|
|
423
|
+
if (opts && opts.onwait) opts.onwait(index)
|
|
424
|
+
block = await this.replicator.requestBlock(index)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (this.encryption) this.encryption.decrypt(index, block)
|
|
428
|
+
return this._decode(encoding, block)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
createReadStream (opts) {
|
|
432
|
+
return new ReadStream(this, opts)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
createWriteStream (opts) {
|
|
436
|
+
return new WriteStream(this, opts)
|
|
355
437
|
}
|
|
356
438
|
|
|
357
439
|
download (range) {
|
|
358
|
-
const start = (range && range.start) || 0
|
|
359
|
-
const end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
|
|
360
440
|
const linear = !!(range && range.linear)
|
|
361
441
|
|
|
362
|
-
|
|
442
|
+
let start
|
|
443
|
+
let end
|
|
444
|
+
let filter
|
|
445
|
+
|
|
446
|
+
if (range && range.blocks) {
|
|
447
|
+
const blocks = range.blocks instanceof Set
|
|
448
|
+
? range.blocks
|
|
449
|
+
: new Set(range.blocks)
|
|
450
|
+
|
|
451
|
+
start = range.start || (blocks.size ? min(range.blocks) : 0)
|
|
452
|
+
end = range.end || (blocks.size ? max(range.blocks) + 1 : 0)
|
|
453
|
+
|
|
454
|
+
filter = (i) => blocks.has(i)
|
|
455
|
+
} else {
|
|
456
|
+
start = (range && range.start) || 0
|
|
457
|
+
end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
|
|
458
|
+
}
|
|
363
459
|
|
|
364
|
-
const r = Replicator.createRange(start, end, linear)
|
|
460
|
+
const r = Replicator.createRange(start, end, filter, linear)
|
|
365
461
|
|
|
366
462
|
if (this.opened) this.replicator.addRange(r)
|
|
367
463
|
else this.opening.then(() => this.replicator.addRange(r), noop)
|
|
@@ -394,22 +490,19 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
394
490
|
if (this.opened === false) await this.opening
|
|
395
491
|
if (this.writable === false) throw new Error('Core is not writable')
|
|
396
492
|
|
|
397
|
-
|
|
398
|
-
const buffers = new Array(blks.length)
|
|
493
|
+
blocks = Array.isArray(blocks) ? blocks : [blocks]
|
|
399
494
|
|
|
400
|
-
|
|
401
|
-
const blk = blks[i]
|
|
495
|
+
const preappend = this.encryption && this._preappend
|
|
402
496
|
|
|
403
|
-
|
|
404
|
-
? blk
|
|
405
|
-
: this.valueEncoding
|
|
406
|
-
? c.encode(this.valueEncoding, blk)
|
|
407
|
-
: Buffer.from(blk)
|
|
497
|
+
const buffers = this.encodeBatch !== null ? this.encodeBatch(blocks) : new Array(blocks.length)
|
|
408
498
|
|
|
409
|
-
|
|
499
|
+
if (this.encodeBatch === null) {
|
|
500
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
501
|
+
buffers[i] = this._encode(this.valueEncoding, blocks[i])
|
|
502
|
+
}
|
|
410
503
|
}
|
|
411
504
|
|
|
412
|
-
return await this.core.append(buffers, this.sign)
|
|
505
|
+
return await this.core.append(buffers, this.sign, { preappend })
|
|
413
506
|
}
|
|
414
507
|
|
|
415
508
|
registerExtension (name, handlers) {
|
|
@@ -420,14 +513,38 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
420
513
|
onextensionupdate () {
|
|
421
514
|
if (this.replicator !== null) this.replicator.broadcastOptions()
|
|
422
515
|
}
|
|
423
|
-
}
|
|
424
516
|
|
|
425
|
-
|
|
517
|
+
_encode (enc, val) {
|
|
518
|
+
const state = { start: this.padding, end: this.padding, buffer: null }
|
|
519
|
+
|
|
520
|
+
if (b4a.isBuffer(val)) {
|
|
521
|
+
if (state.start === 0) return val
|
|
522
|
+
state.end += val.byteLength
|
|
523
|
+
} else if (enc) {
|
|
524
|
+
enc.preencode(state, val)
|
|
525
|
+
} else {
|
|
526
|
+
val = b4a.from(val)
|
|
527
|
+
if (state.start === 0) return val
|
|
528
|
+
state.end += val.byteLength
|
|
529
|
+
}
|
|
426
530
|
|
|
427
|
-
|
|
428
|
-
|
|
531
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
532
|
+
|
|
533
|
+
if (enc) enc.encode(state, val)
|
|
534
|
+
else state.buffer.set(val, state.start)
|
|
535
|
+
|
|
536
|
+
return state.buffer
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
_decode (enc, block) {
|
|
540
|
+
block = block.subarray(this.padding)
|
|
541
|
+
if (enc) return c.decode(enc, block)
|
|
542
|
+
return block
|
|
543
|
+
}
|
|
429
544
|
}
|
|
430
545
|
|
|
546
|
+
function noop () {}
|
|
547
|
+
|
|
431
548
|
function isStream (s) {
|
|
432
549
|
return typeof s === 'object' && s && typeof s.pipe === 'function'
|
|
433
550
|
}
|
|
@@ -441,5 +558,27 @@ function requireMaybe (name) {
|
|
|
441
558
|
}
|
|
442
559
|
|
|
443
560
|
function toHex (buf) {
|
|
444
|
-
return buf &&
|
|
561
|
+
return buf && b4a.toString(buf, 'hex')
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function reduce (iter, fn, acc) {
|
|
565
|
+
for (const item of iter) acc = fn(acc, item)
|
|
566
|
+
return acc
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function min (arr) {
|
|
570
|
+
return reduce(arr, (a, b) => Math.min(a, b), Infinity)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function max (arr) {
|
|
574
|
+
return reduce(arr, (a, b) => Math.max(a, b), -Infinity)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function preappend (blocks) {
|
|
578
|
+
const offset = this.core.tree.length
|
|
579
|
+
const fork = this.core.tree.fork
|
|
580
|
+
|
|
581
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
582
|
+
this.encryption.encrypt(offset + i, blocks[i], fork)
|
|
583
|
+
}
|
|
445
584
|
}
|
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) {
|