hypercore 10.18.5 → 10.19.1

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/index.js CHANGED
@@ -658,6 +658,10 @@ module.exports = class Hypercore extends EventEmitter {
658
658
  return null
659
659
  }
660
660
 
661
+ createTreeBatch () {
662
+ return this.core.tree.batch()
663
+ }
664
+
661
665
  findingPeers () {
662
666
  this._findingPeers++
663
667
  if (this.replicator !== null && !this.closing) this.replicator.findingPeers++
@@ -709,9 +713,9 @@ module.exports = class Hypercore extends EventEmitter {
709
713
  return true
710
714
  }
711
715
 
712
- batch () {
716
+ batch ({ autoClose = true, session = true } = {}) {
713
717
  if (this._batch !== null) throw BATCH_ALREADY_EXISTS()
714
- const batch = new Batch(this)
718
+ const batch = new Batch(session ? this.session() : this, autoClose)
715
719
  for (const session of this.sessions) session._batch = batch
716
720
  return batch
717
721
  }
@@ -878,7 +882,8 @@ module.exports = class Hypercore extends EventEmitter {
878
882
  }
879
883
 
880
884
  async truncate (newLength = 0, fork = -1) {
881
- if (this._batch && !this._batch.flushed) throw BATCH_UNFLUSHED()
885
+ if (this._batch) throw BATCH_UNFLUSHED()
886
+
882
887
  if (this.opened === false) await this.opening
883
888
  if (this.writable === false) throw SESSION_NOT_WRITABLE()
884
889
 
@@ -889,8 +894,9 @@ module.exports = class Hypercore extends EventEmitter {
889
894
  this.replicator.updateAll()
890
895
  }
891
896
 
892
- async append (blocks) {
893
- if (this._batch && !this._batch.flushed) throw BATCH_UNFLUSHED()
897
+ async append (blocks, opts) {
898
+ if (this._batch && this !== this._batch.session) throw BATCH_UNFLUSHED()
899
+
894
900
  if (this.opened === false) await this.opening
895
901
  if (this.writable === false) throw SESSION_NOT_WRITABLE()
896
902
 
@@ -906,7 +912,7 @@ module.exports = class Hypercore extends EventEmitter {
906
912
  }
907
913
  }
908
914
 
909
- return this.core.append(buffers, this.auth, { preappend })
915
+ return this.core.append(buffers, (opts && opts.auth) || this.auth, { preappend })
910
916
  }
911
917
 
912
918
  async treeHash (length) {
package/lib/batch.js CHANGED
@@ -1,66 +1,196 @@
1
- const { SESSION_NOT_WRITABLE, BATCH_ALREADY_FLUSHED } = require('hypercore-errors')
1
+ const { SESSION_NOT_WRITABLE, BLOCK_NOT_AVAILABLE, SESSION_CLOSED } = require('hypercore-errors')
2
+ const EventEmitter = require('events')
3
+ const c = require('compact-encoding')
4
+
5
+ module.exports = class HypercoreBatch extends EventEmitter {
6
+ constructor (session, autoClose) {
7
+ super()
2
8
 
3
- module.exports = class Batch {
4
- constructor (session) {
5
9
  this.session = session
6
- this.flushed = false
10
+ this.opened = false
11
+ this.closed = false
12
+ this.opening = null
13
+ this.closing = null
14
+ this.autoClose = autoClose
7
15
 
8
16
  this._appends = []
9
17
  this._byteLength = 0
18
+ this._fork = 0
19
+ this._sessionLength = 0
20
+ this._sessionByteLength = 0
21
+ this._flushing = null
22
+
23
+ this.opening = this.ready().catch(noop)
24
+ }
25
+
26
+ get id () {
27
+ return this.session.id
28
+ }
29
+
30
+ get key () {
31
+ return this.session.key
32
+ }
33
+
34
+ get discoveryKey () {
35
+ return this.session.discoveryKey
36
+ }
37
+
38
+ get indexedLength () {
39
+ return this._sessionLength
40
+ }
41
+
42
+ get indexedByteLength () {
43
+ return this._sessionByteLength
10
44
  }
11
45
 
12
46
  get length () {
13
- return this.session.length + this._appends.length
47
+ return this._sessionLength + this._appends.length
48
+ }
49
+
50
+ get byteLength () {
51
+ return this._sessionByteLength + this._byteLength
52
+ }
53
+
54
+ get writable () {
55
+ return this.session.writable
56
+ }
57
+
58
+ get core () {
59
+ return this.session.core
60
+ }
61
+
62
+ async ready () {
63
+ await this.session.ready()
64
+ if (this.opened) return
65
+ this._sessionLength = this.session.length
66
+ this._sessionByteLength = this.session.byteLength
67
+ this.opened = true
68
+ this.emit('ready')
69
+ }
70
+
71
+ async update (opts) {
72
+ if (this.opened === false) await this.ready()
73
+ await this.session.update(opts)
14
74
  }
15
75
 
16
- ready () {
17
- return this.session.ready()
76
+ setUserData (key, value, opts) {
77
+ return this.session.setUserData(key, value, opts)
78
+ }
79
+
80
+ getUserData (key, opts) {
81
+ return this.session.getUserData(key, opts)
18
82
  }
19
83
 
20
84
  async info (opts) {
21
85
  const session = this.session
22
86
  const info = await session.info(opts)
23
87
 
24
- if (info.contiguousLength === info.length) {
88
+ info.length = this._sessionLength
89
+
90
+ if (info.contiguousLength >= info.length) {
25
91
  info.contiguousLength = info.length += this._appends.length
26
92
  } else {
27
93
  info.length += this._appends.length
28
94
  }
29
95
 
30
- info.byteLength += this._byteLength
96
+ info.byteLength = this._sessionByteLength + this._byteLength
31
97
 
32
98
  return info
33
99
  }
34
100
 
101
+ async seek (bytes, opts) {
102
+ if (this.opened === false) await this.opening
103
+ if (this.closing) throw SESSION_CLOSED()
104
+
105
+ if (bytes < this._sessionByteLength) return await this.session.seek(bytes, opts)
106
+
107
+ bytes -= this._sessionByteLength
108
+
109
+ let i = 0
110
+
111
+ for (const blk of this._appends) {
112
+ if (bytes < blk.byteLength) return [this._sessionLength + i, bytes]
113
+ i++
114
+ bytes -= blk.byteLength
115
+ }
116
+
117
+ if (bytes === 0) return [this._sessionLength + i, 0]
118
+
119
+ throw BLOCK_NOT_AVAILABLE()
120
+ }
121
+
35
122
  async get (index, opts) {
36
- const session = this.session
37
- if (session.opened === false) await session.opening
123
+ if (this.opened === false) await this.opening
124
+ if (this.closing) throw SESSION_CLOSED()
38
125
 
39
- const length = this.session.length
126
+ const length = this._sessionLength
40
127
  if (index < length) return this.session.get(index, opts)
41
128
 
42
- return this._appends[index - length] || null
129
+ const buffer = this._appends[index - length] || null
130
+ if (!buffer) throw BLOCK_NOT_AVAILABLE()
131
+
132
+ const encoding = (opts && opts.valueEncoding && c.from(opts.valueEncoding)) || this.session.valueEncoding
133
+ if (!encoding) return buffer
134
+
135
+ return c.decode(encoding, buffer)
136
+ }
137
+
138
+ async _waitForFlush () {
139
+ // wait for any pending flush...
140
+ while (this._flushing) {
141
+ await this._flushing
142
+ await Promise.resolve() // yield in case a new flush is queued
143
+ }
144
+ }
145
+
146
+ createTreeBatch (length, blocks = []) {
147
+ if (!length && length !== 0) length = this.length + blocks.length
148
+
149
+ const maxLength = this.length + blocks.length
150
+ const b = this.session.createTreeBatch()
151
+ const len = Math.min(length, this.length)
152
+
153
+ if (len < this._sessionLength || length > maxLength) return null
154
+
155
+ for (let i = 0; i < len - this._sessionLength; i++) {
156
+ b.append(this._appends[i])
157
+ }
158
+
159
+ if (len < this.length) return b
160
+
161
+ for (let i = 0; i < length - len; i++) {
162
+ b.append(blocks[i])
163
+ }
164
+
165
+ return b
43
166
  }
44
167
 
45
168
  async truncate (newLength) {
46
- if (this.flushed) throw BATCH_ALREADY_FLUSHED()
169
+ if (this.opened === false) await this.opening
170
+ if (this.writable === false) throw SESSION_NOT_WRITABLE()
171
+ if (this.closing) throw SESSION_CLOSED()
47
172
 
48
- const session = this.session
49
- if (session.opened === false) await session.opening
50
- if (session.writable === false) throw SESSION_NOT_WRITABLE()
173
+ // wait for any pending flush... (prop needs a lock)
174
+ await this._waitForFlush()
51
175
 
52
- const length = session.length
176
+ const length = this._sessionLength
53
177
  if (newLength < length) throw new Error('Cannot truncate committed blocks')
54
178
 
55
179
  this._appends.length = newLength - length
180
+ this._fork++
181
+
182
+ this.emit('truncate', newLength, this.fork)
56
183
  }
57
184
 
58
185
  async append (blocks) {
59
- if (this.flushed) throw BATCH_ALREADY_FLUSHED()
60
-
61
186
  const session = this.session
62
- if (session.opened === false) await session.opening
63
- if (session.writable === false) throw SESSION_NOT_WRITABLE()
187
+
188
+ if (this.opened === false) await this.opening
189
+ if (this.writable === false) throw SESSION_NOT_WRITABLE()
190
+ if (this.closing) throw SESSION_CLOSED()
191
+
192
+ // wait for any pending flush... (prop needs a lock)
193
+ await this._waitForFlush()
64
194
 
65
195
  blocks = Array.isArray(blocks) ? blocks : [blocks]
66
196
 
@@ -78,37 +208,67 @@ module.exports = class Batch {
78
208
 
79
209
  this._appends.push(...buffers)
80
210
 
81
- const byteLength = session.byteLength + this._byteLength
211
+ const info = { length: this.length, byteLength: this.byteLength }
212
+ this.emit('append')
82
213
 
83
- return { length: this.length, byteLength }
214
+ return info
84
215
  }
85
216
 
86
- async flush () {
87
- if (this.flushed) throw BATCH_ALREADY_FLUSHED()
88
- this.flushed = true
217
+ async flush (length = this._appends.length, auth) {
218
+ if (this.opened === false) await this.opening
219
+ if (this.closing) throw SESSION_CLOSED()
220
+
221
+ while (this._flushing) await this._flushing
222
+ this._flushing = this._flush(length, auth)
89
223
 
90
224
  try {
91
- if (this._appends.length) await this.session.append(this._appends)
225
+ await this._flushing
92
226
  } finally {
93
- this._clearBatch()
94
- this._clearAppends()
227
+ this._flushing = null
95
228
  }
229
+
230
+ if (this.autoClose) await this.close()
231
+ }
232
+
233
+ async _flush (length, auth) { // TODO: make this safe to interact with a parallel truncate...
234
+ if (this._appends.length === 0) return
235
+
236
+ const flushingLength = Math.min(length, this._appends.length)
237
+ const info = await this.session.append(flushingLength < this._appends.length ? this._appends.slice(0, flushingLength) : this._appends, { auth })
238
+ const delta = info.byteLength - this._sessionByteLength
239
+
240
+ this._sessionLength = info.length
241
+ this._sessionByteLength = info.byteLength
242
+ this._appends = this._appends.slice(flushingLength)
243
+ this._byteLength -= delta
244
+
245
+ this.emit('flush')
96
246
  }
97
247
 
98
- async close () {
99
- if (this.flushed) throw BATCH_ALREADY_FLUSHED()
100
- this.flushed = true
248
+ close () {
249
+ if (!this.closing) this.closing = this._close()
250
+ return this.closing
251
+ }
101
252
 
253
+ async _close () {
102
254
  this._clearBatch()
103
255
  this._clearAppends()
256
+
257
+ await this.session.close()
258
+
259
+ this.closed = true
260
+ this.emit('close')
104
261
  }
105
262
 
106
263
  _clearAppends () {
107
264
  this._appends = []
108
265
  this._byteLength = 0
266
+ this._fork = 0
109
267
  }
110
268
 
111
269
  _clearBatch () {
112
270
  for (const session of this.session.sessions) session._batch = null
113
271
  }
114
272
  }
273
+
274
+ function noop () {}
package/lib/core.js CHANGED
@@ -13,6 +13,7 @@ module.exports = class Core {
13
13
  constructor (header, crypto, oplog, tree, blocks, bitfield, auth, legacy, onupdate, onconflict) {
14
14
  this.onupdate = onupdate
15
15
  this.onconflict = onconflict
16
+ this.preupdate = null
16
17
  this.header = header
17
18
  this.crypto = crypto
18
19
  this.oplog = oplog
@@ -232,7 +233,7 @@ module.exports = class Core {
232
233
 
233
234
  try {
234
235
  const batch = await this.tree.truncate(length, fork)
235
- batch.signature = await auth.sign(batch.signable())
236
+ batch.signature = await auth.sign(batch.signable(), batch)
236
237
  await this._truncate(batch, null)
237
238
  } finally {
238
239
  this.truncating--
@@ -322,7 +323,7 @@ module.exports = class Core {
322
323
  for (const val of values) batch.append(val)
323
324
 
324
325
  const hash = batch.hash()
325
- batch.signature = await auth.sign(this._legacy ? batch.signableLegacy(hash) : batch.signable(hash))
326
+ batch.signature = await auth.sign(this._legacy ? batch.signableLegacy(hash) : batch.signable(hash), batch)
326
327
 
327
328
  const entry = {
328
329
  userData: null,
@@ -359,7 +360,7 @@ module.exports = class Core {
359
360
 
360
361
  _signed (batch, hash, auth = this.defaultAuth) {
361
362
  const signable = this._legacy ? batch.signableLegacy(hash) : batch.signable(hash)
362
- return auth.verify(signable, batch.signature)
363
+ return auth.verify(signable, batch.signature, batch)
363
364
  }
364
365
 
365
366
  async _verifyExclusive ({ batch, bitfield, value, from }) {
@@ -381,6 +382,7 @@ module.exports = class Core {
381
382
  bitfield
382
383
  }
383
384
 
385
+ if (this.preupdate !== null) await this.preupdate(batch, this.header.signer.publicKey)
384
386
  if (bitfield) await this._writeBlock(batch, bitfield.start, value)
385
387
 
386
388
  await this.oplog.append([entry], false)
@@ -85,6 +85,34 @@ class MerkleTreeBatch {
85
85
  return caps.treeSignableLegacy(hash, this.length, this.fork)
86
86
  }
87
87
 
88
+ get (index) {
89
+ if (index >= this.length * 2) {
90
+ return null
91
+ }
92
+
93
+ if (index < this.treeLength * 2) {
94
+ return this.tree.get(index)
95
+ }
96
+
97
+ for (const n of this.nodes) {
98
+ if (n.index === index) return n
99
+ }
100
+
101
+ return null
102
+ }
103
+
104
+ proof ({ block, hash, seek, upgrade }) {
105
+ return generateProof(this, block, hash, seek, upgrade)
106
+ }
107
+
108
+ verifyUpgrade (proof) {
109
+ const unverified = verifyTree(proof, this.tree.crypto, this.nodes)
110
+
111
+ if (!proof.upgrade) throw INVALID_OPERATION('Expected upgrade proof')
112
+
113
+ return verifyUpgrade(proof, unverified, this)
114
+ }
115
+
88
116
  append (buf) {
89
117
  const head = this.length * 2
90
118
  const ite = flat.iterator(head)
@@ -640,80 +668,8 @@ module.exports = class MerkleTree {
640
668
  return batch
641
669
  }
642
670
 
643
- async proof ({ block, hash, seek, upgrade }) {
644
- // Important that this does not throw inbetween making the promise arrays
645
- // and finalise being called, otherwise there will be lingering promises in the background
646
-
647
- const fork = this.fork
648
- const signature = this.signature
649
- const head = 2 * this.length
650
- const from = upgrade ? upgrade.start * 2 : 0
651
- const to = upgrade ? from + upgrade.length * 2 : head
652
- const node = normalizeIndexed(block, hash)
653
-
654
- if (from >= to || to > head) {
655
- throw INVALID_OPERATION('Invalid upgrade')
656
- }
657
- if (seek && upgrade && node !== null && node.index >= from) {
658
- throw INVALID_OPERATION('Cannot both do a seek and block/hash request when upgrading')
659
- }
660
-
661
- let subTree = head
662
-
663
- const p = {
664
- node: null,
665
- seek: null,
666
- upgrade: null,
667
- additionalUpgrade: null
668
- }
669
-
670
- if (node !== null && (!upgrade || node.lastIndex < upgrade.start)) {
671
- subTree = nodesToRoot(node.index, node.nodes, to)
672
- const seekRoot = seek ? await seekUntrustedTree(this, subTree, seek.bytes) : head
673
- blockAndSeekProof(this, node, seek, seekRoot, subTree, p)
674
- } else if ((node || seek) && upgrade) {
675
- subTree = seek ? await seekFromHead(this, to, seek.bytes) : node.index
676
- }
677
-
678
- if (upgrade) {
679
- upgradeProof(this, node, seek, from, to, subTree, p)
680
- if (head > to) additionalUpgradeProof(this, to, head, p)
681
- }
682
-
683
- const [pNode, pSeek, pUpgrade, pAdditional] = await settleProof(p)
684
- const result = { fork, block: null, hash: null, seek: null, upgrade: null }
685
-
686
- if (block) {
687
- result.block = {
688
- index: block.index,
689
- value: null, // populated upstream, alloc it here for simplicity
690
- nodes: pNode
691
- }
692
- } else if (hash) {
693
- result.hash = {
694
- index: hash.index,
695
- nodes: pNode
696
- }
697
- }
698
-
699
- if (seek && pSeek !== null) {
700
- result.seek = {
701
- bytes: seek.bytes,
702
- nodes: pSeek
703
- }
704
- }
705
-
706
- if (upgrade) {
707
- result.upgrade = {
708
- start: upgrade.start,
709
- length: upgrade.length,
710
- nodes: pUpgrade,
711
- additionalNodes: pAdditional || [],
712
- signature
713
- }
714
- }
715
-
716
- return result
671
+ proof ({ block, hash, seek, upgrade }) {
672
+ return generateProof(this, block, hash, seek, upgrade)
717
673
  }
718
674
 
719
675
  // Successor to .nodes()
@@ -1216,3 +1172,79 @@ async function settleProof (p) {
1216
1172
  throw err
1217
1173
  }
1218
1174
  }
1175
+
1176
+ // tree can be either the merkle tree or a merkle tree batch
1177
+ async function generateProof (tree, block, hash, seek, upgrade) {
1178
+ // Important that this does not throw inbetween making the promise arrays
1179
+ // and finalise being called, otherwise there will be lingering promises in the background
1180
+
1181
+ const fork = tree.fork
1182
+ const signature = tree.signature
1183
+ const head = 2 * tree.length
1184
+ const from = upgrade ? upgrade.start * 2 : 0
1185
+ const to = upgrade ? from + upgrade.length * 2 : head
1186
+ const node = normalizeIndexed(block, hash)
1187
+ if (from >= to || to > head) {
1188
+ throw INVALID_OPERATION('Invalid upgrade')
1189
+ }
1190
+ if (seek && upgrade && node !== null && node.index >= from) {
1191
+ throw INVALID_OPERATION('Cannot both do a seek and block/hash request when upgrading')
1192
+ }
1193
+
1194
+ let subTree = head
1195
+
1196
+ const p = {
1197
+ node: null,
1198
+ seek: null,
1199
+ upgrade: null,
1200
+ additionalUpgrade: null
1201
+ }
1202
+
1203
+ if (node !== null && (!upgrade || node.lastIndex < upgrade.start)) {
1204
+ subTree = nodesToRoot(node.index, node.nodes, to)
1205
+ const seekRoot = seek ? await seekUntrustedTree(tree, subTree, seek.bytes) : head
1206
+ blockAndSeekProof(tree, node, seek, seekRoot, subTree, p)
1207
+ } else if ((node || seek) && upgrade) {
1208
+ subTree = seek ? await seekFromHead(tree, to, seek.bytes) : node.index
1209
+ }
1210
+
1211
+ if (upgrade) {
1212
+ upgradeProof(tree, node, seek, from, to, subTree, p)
1213
+ if (head > to) additionalUpgradeProof(tree, to, head, p)
1214
+ }
1215
+
1216
+ const [pNode, pSeek, pUpgrade, pAdditional] = await settleProof(p)
1217
+ const result = { fork, block: null, hash: null, seek: null, upgrade: null }
1218
+
1219
+ if (block) {
1220
+ result.block = {
1221
+ index: block.index,
1222
+ value: null, // populated upstream, alloc it here for simplicity
1223
+ nodes: pNode
1224
+ }
1225
+ } else if (hash) {
1226
+ result.hash = {
1227
+ index: hash.index,
1228
+ nodes: pNode
1229
+ }
1230
+ }
1231
+
1232
+ if (seek && pSeek !== null) {
1233
+ result.seek = {
1234
+ bytes: seek.bytes,
1235
+ nodes: pSeek
1236
+ }
1237
+ }
1238
+
1239
+ if (upgrade) {
1240
+ result.upgrade = {
1241
+ start: upgrade.start,
1242
+ length: upgrade.length,
1243
+ nodes: pUpgrade,
1244
+ additionalNodes: pAdditional || [],
1245
+ signature
1246
+ }
1247
+ }
1248
+
1249
+ return result
1250
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.18.5",
3
+ "version": "10.19.1",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {