hypercore 10.0.0-alpha.7 → 10.0.0

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/lib/bitfield.js CHANGED
@@ -1,6 +1,6 @@
1
- // TODO: needs massive improvements obvs
2
-
3
1
  const BigSparseArray = require('big-sparse-array')
2
+ const b4a = require('b4a')
3
+ const bits = require('bits-to-bytes')
4
4
 
5
5
  class FixedBitfield {
6
6
  constructor (index, bitfield) {
@@ -10,27 +10,26 @@ class FixedBitfield {
10
10
  }
11
11
 
12
12
  get (index) {
13
- const j = index & 31
14
- const i = (index - j) / 32
15
-
16
- return i < this.bitfield.length && (this.bitfield[i] & (1 << j)) !== 0
13
+ return bits.get(this.bitfield, index)
17
14
  }
18
15
 
19
16
  set (index, val) {
20
- const j = index & 31
21
- const i = (index - j) / 32
22
- const v = this.bitfield[i]
23
-
24
- if (val === ((v & (1 << j)) !== 0)) return false
17
+ return bits.set(this.bitfield, index, val)
18
+ }
25
19
 
26
- const u = val
27
- ? v | (1 << j)
28
- : v ^ (1 << j)
20
+ setRange (start, length, val) {
21
+ // Using fill instead of setRange is ~2 orders of magnitude faster, but does
22
+ // have the downside of not being able to tell if any bits actually changed.
23
+ bits.fill(this.bitfield, val, start, start + length)
24
+ return true
25
+ }
29
26
 
30
- if (u === v) return false
27
+ firstSet (position) {
28
+ return bits.indexOf(this.bitfield, true, position)
29
+ }
31
30
 
32
- this.bitfield[i] = u
33
- return true
31
+ lastSet (position) {
32
+ return bits.lastIndexOf(this.bitfield, true, position)
34
33
  }
35
34
  }
36
35
 
@@ -40,9 +39,14 @@ module.exports = class Bitfield {
40
39
  this.pages = new BigSparseArray()
41
40
  this.unflushed = []
42
41
  this.storage = storage
43
-
44
- const all = (buf && buf.byteLength >= 4)
45
- ? new Uint32Array(buf.buffer, buf.byteOffset, Math.floor(buf.byteLength / 4))
42
+ this.resumed = !!(buf && buf.byteLength >= 4)
43
+
44
+ const all = this.resumed
45
+ ? new Uint32Array(
46
+ buf.buffer,
47
+ buf.byteOffset,
48
+ Math.floor(buf.byteLength / 4)
49
+ )
46
50
  : new Uint32Array(1024)
47
51
 
48
52
  for (let i = 0; i < all.length; i += 1024) {
@@ -53,47 +57,106 @@ module.exports = class Bitfield {
53
57
  }
54
58
 
55
59
  get (index) {
56
- const j = index & 32767
57
- const i = (index - j) / 32768
60
+ const j = index & (this.pageSize - 1)
61
+ const i = (index - j) / this.pageSize
62
+
58
63
  const p = this.pages.get(i)
59
64
 
60
65
  return p ? p.get(j) : false
61
66
  }
62
67
 
63
68
  set (index, val) {
64
- const j = index & 32767
65
- const i = (index - j) / 32768
69
+ const j = index & (this.pageSize - 1)
70
+ const i = (index - j) / this.pageSize
66
71
 
67
72
  let p = this.pages.get(i)
68
73
 
69
- if (!p) {
70
- if (!val) return
74
+ if (!p && val) {
71
75
  p = this.pages.set(i, new FixedBitfield(i, new Uint32Array(1024)))
72
76
  }
73
77
 
74
- if (!p.set(j, val) || p.dirty) return
75
-
76
- p.dirty = true
77
- this.unflushed.push(p)
78
+ if (p && p.set(j, val) && !p.dirty) {
79
+ p.dirty = true
80
+ this.unflushed.push(p)
81
+ }
78
82
  }
79
83
 
80
84
  setRange (start, length, val) {
81
- for (let i = 0; i < length; i++) {
82
- this.set(start + i, val)
85
+ let j = start & (this.pageSize - 1)
86
+ let i = (start - j) / this.pageSize
87
+
88
+ while (length > 0) {
89
+ let p = this.pages.get(i)
90
+
91
+ if (!p && val) {
92
+ p = this.pages.set(i, new FixedBitfield(i, new Uint32Array(1024)))
93
+ }
94
+
95
+ const end = Math.min(j + length, this.pageSize)
96
+ const range = end - j
97
+
98
+ if (p && p.setRange(j, range, val) && !p.dirty) {
99
+ p.dirty = true
100
+ this.unflushed.push(p)
101
+ }
102
+
103
+ j = 0
104
+ i++
105
+ length -= range
83
106
  }
84
107
  }
85
108
 
86
- // Should prob be removed, when/if we re-add compression
87
- page (i) {
88
- const p = this.pages.get(i)
89
- return p ? p.bitfield : new Uint32Array(1024)
109
+ firstSet (position) {
110
+ let j = position & (this.pageSize - 1)
111
+ let i = (position - j) / this.pageSize
112
+
113
+ while (i < this.pages.factor) {
114
+ const p = this.pages.get(i)
115
+
116
+ if (p) {
117
+ const index = p.firstSet(j)
118
+
119
+ if (index !== -1) {
120
+ return i * this.pageSize + index
121
+ }
122
+ }
123
+
124
+ j = 0
125
+ i++
126
+ }
127
+
128
+ return -1
129
+ }
130
+
131
+ lastSet (position) {
132
+ let j = position & (this.pageSize - 1)
133
+ let i = (position - j) / this.pageSize
134
+
135
+ while (i >= 0) {
136
+ const p = this.pages.get(i)
137
+
138
+ if (p) {
139
+ const index = p.lastSet(j)
140
+
141
+ if (index !== -1) {
142
+ return i * this.pageSize + index
143
+ }
144
+ }
145
+
146
+ j = this.pageSize - 1
147
+ i--
148
+ }
149
+
150
+ return -1
90
151
  }
91
152
 
92
153
  clear () {
93
154
  return new Promise((resolve, reject) => {
94
155
  this.storage.del(0, Infinity, (err) => {
95
- if (err) reject(err)
96
- else resolve()
156
+ if (err) return reject(err)
157
+ this.pages = new BigSparseArray()
158
+ this.unflushed = []
159
+ resolve()
97
160
  })
98
161
  })
99
162
  }
@@ -116,9 +179,14 @@ module.exports = class Bitfield {
116
179
  let error = null
117
180
 
118
181
  for (const page of this.unflushed) {
119
- const b = Buffer.from(page.bitfield.buffer, page.bitfield.byteOffset, page.bitfield.byteLength)
182
+ const buf = b4a.from(
183
+ page.bitfield.buffer,
184
+ page.bitfield.byteOffset,
185
+ page.bitfield.byteLength
186
+ )
187
+
120
188
  page.dirty = false
121
- this.storage.write(page.index * 4096, b, done)
189
+ this.storage.write(page.index * 4096, buf, done)
122
190
  }
123
191
 
124
192
  function done (err) {
@@ -147,7 +215,7 @@ module.exports = class Bitfield {
147
215
  }
148
216
 
149
217
  function ensureSize (uint32, size) {
150
- if (uint32.length === size) return uint32
218
+ if (uint32.byteLength === size) return uint32
151
219
  const a = new Uint32Array(1024)
152
220
  a.set(uint32, 0)
153
221
  return a
@@ -1,11 +1,12 @@
1
1
  const sodium = require('sodium-universal')
2
2
  const c = require('compact-encoding')
3
+ const b4a = require('b4a')
3
4
 
4
- const nonce = Buffer.alloc(sodium.crypto_stream_NONCEBYTES)
5
+ const nonce = b4a.alloc(sodium.crypto_stream_NONCEBYTES)
5
6
 
6
7
  module.exports = class BlockEncryption {
7
8
  constructor (encryptionKey, hypercoreKey) {
8
- const subKeys = Buffer.alloc(2 * sodium.crypto_stream_KEYBYTES)
9
+ const subKeys = b4a.alloc(2 * sodium.crypto_stream_KEYBYTES)
9
10
 
10
11
  this.key = encryptionKey
11
12
  this.blockKey = subKeys.subarray(0, sodium.crypto_stream_KEYBYTES)
@@ -1,3 +1,5 @@
1
+ const b4a = require('b4a')
2
+
1
3
  module.exports = class BlockStore {
2
4
  constructor (storage, tree) {
3
5
  this.storage = storage
@@ -15,12 +17,12 @@ module.exports = class BlockStore {
15
17
 
16
18
  putBatch (i, batch, offset) {
17
19
  if (batch.length === 0) return Promise.resolve()
18
- return this.put(i, batch.length === 1 ? batch[0] : Buffer.concat(batch), offset)
20
+ return this.put(i, batch.length === 1 ? batch[0] : b4a.concat(batch), offset)
19
21
  }
20
22
 
21
- clear () {
23
+ clear (offset = 0, length = Infinity) {
22
24
  return new Promise((resolve, reject) => {
23
- this.storage.del(0, Infinity, (err) => {
25
+ this.storage.del(offset, length, (err) => {
24
26
  if (err) reject(err)
25
27
  else resolve()
26
28
  })
@@ -49,7 +51,7 @@ module.exports = class BlockStore {
49
51
  return new Promise((resolve, reject) => {
50
52
  this.storage.write(offset, data, (err) => {
51
53
  if (err) reject(err)
52
- else resolve()
54
+ else resolve(offset + data.byteLength)
53
55
  })
54
56
  })
55
57
  }
package/lib/caps.js ADDED
@@ -0,0 +1,32 @@
1
+ const crypto = require('hypercore-crypto')
2
+ const sodium = require('sodium-universal')
3
+ const b4a = require('b4a')
4
+ const c = require('compact-encoding')
5
+
6
+ // TODO: rename this to "crypto" and move everything hashing related etc in here
7
+ // Also lets move the tree stuff from hypercore-crypto here
8
+
9
+ const [TREE, REPLICATE_INITIATOR, REPLICATE_RESPONDER] = crypto.namespace('hypercore', 3)
10
+
11
+ exports.replicate = function (isInitiator, key, handshakeHash) {
12
+ const out = b4a.allocUnsafe(32)
13
+ sodium.crypto_generichash_batch(out, [isInitiator ? REPLICATE_INITIATOR : REPLICATE_RESPONDER, key], handshakeHash)
14
+ return out
15
+ }
16
+
17
+ exports.treeSignable = function (hash, length, fork) {
18
+ const state = { start: 0, end: 80, buffer: b4a.allocUnsafe(80) }
19
+ c.raw.encode(state, TREE)
20
+ c.raw.encode(state, hash)
21
+ c.uint64.encode(state, length)
22
+ c.uint64.encode(state, fork)
23
+ return state.buffer
24
+ }
25
+
26
+ exports.treeSignableLegacy = function (hash, length, fork) {
27
+ const state = { start: 0, end: 48, buffer: b4a.allocUnsafe(48) }
28
+ c.raw.encode(state, hash)
29
+ c.uint64.encode(state, length)
30
+ c.uint64.encode(state, fork)
31
+ return state.buffer
32
+ }
package/lib/core.js CHANGED
@@ -1,13 +1,15 @@
1
1
  const hypercoreCrypto = require('hypercore-crypto')
2
+ const b4a = require('b4a')
2
3
  const Oplog = require('./oplog')
3
4
  const Mutex = require('./mutex')
4
5
  const MerkleTree = require('./merkle-tree')
5
6
  const BlockStore = require('./block-store')
6
7
  const Bitfield = require('./bitfield')
7
- const { oplogHeader, oplogEntry } = require('./messages')
8
+ const { BAD_ARGUMENT, STORAGE_EMPTY, STORAGE_CONFLICT, INVALID_SIGNATURE } = require('./errors')
9
+ const m = require('./messages')
8
10
 
9
11
  module.exports = class Core {
10
- constructor (header, crypto, oplog, tree, blocks, bitfield, sign, onupdate) {
12
+ constructor (header, crypto, oplog, tree, blocks, bitfield, auth, legacy, onupdate) {
11
13
  this.onupdate = onupdate
12
14
  this.header = header
13
15
  this.crypto = crypto
@@ -15,7 +17,7 @@ module.exports = class Core {
15
17
  this.tree = tree
16
18
  this.blocks = blocks
17
19
  this.bitfield = bitfield
18
- this.defaultSign = sign
20
+ this.defaultAuth = auth
19
21
  this.truncating = 0
20
22
 
21
23
  this._maxOplogSize = 65536
@@ -23,6 +25,7 @@ module.exports = class Core {
23
25
  this._verifies = null
24
26
  this._verifiesFlushed = null
25
27
  this._mutex = new Mutex()
28
+ this._legacy = legacy
26
29
  }
27
30
 
28
31
  static async open (storage, opts = {}) {
@@ -49,27 +52,46 @@ module.exports = class Core {
49
52
  }
50
53
  }
51
54
 
52
- // TODO: we should prob have a general "auth" abstraction instead somewhere?
53
- static createSigner (crypto, { publicKey, secretKey }) {
54
- if (!crypto.validateKeyPair({ publicKey, secretKey })) throw new Error('Invalid key pair')
55
- return signable => crypto.sign(signable, secretKey)
55
+ static createAuth (crypto, { publicKey, secretKey }, opts = {}) {
56
+ if (secretKey && !crypto.validateKeyPair({ publicKey, secretKey })) {
57
+ throw BAD_ARGUMENT('Invalid key pair')
58
+ }
59
+
60
+ const sign = opts.sign
61
+ ? opts.sign
62
+ : secretKey
63
+ ? (signable) => crypto.sign(signable, secretKey)
64
+ : undefined
65
+
66
+ return {
67
+ sign,
68
+ verify (signable, signature) {
69
+ return crypto.verify(signable, signature, publicKey)
70
+ }
71
+ }
56
72
  }
57
73
 
58
74
  static async resume (oplogFile, treeFile, bitfieldFile, dataFile, opts) {
59
- const overwrite = opts.overwrite === true
75
+ let overwrite = opts.overwrite === true
76
+
77
+ const force = opts.force === true
60
78
  const createIfMissing = opts.createIfMissing !== false
61
79
  const crypto = opts.crypto || hypercoreCrypto
62
80
 
63
81
  const oplog = new Oplog(oplogFile, {
64
- headerEncoding: oplogHeader,
65
- entryEncoding: oplogEntry
82
+ headerEncoding: m.oplog.header,
83
+ entryEncoding: m.oplog.entry
66
84
  })
67
85
 
68
86
  let { header, entries } = await oplog.open()
69
87
 
70
- if (!header || overwrite === true) {
88
+ if (force && opts.keyPair && header && header.signer && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
89
+ overwrite = true
90
+ }
91
+
92
+ if (!header || overwrite) {
71
93
  if (!createIfMissing) {
72
- throw new Error('No hypercore is stored here')
94
+ throw STORAGE_EMPTY('No Hypercore is stored here')
73
95
  }
74
96
 
75
97
  header = {
@@ -84,14 +106,15 @@ module.exports = class Core {
84
106
  signer: opts.keyPair || crypto.keyPair(),
85
107
  hints: {
86
108
  reorgs: []
87
- }
109
+ },
110
+ contiguousLength: 0
88
111
  }
89
112
 
90
113
  await oplog.flush(header)
91
114
  }
92
115
 
93
- if (opts.keyPair && !header.signer.publicKey.equals(opts.keyPair.publicKey)) {
94
- throw new Error('Another hypercore is stored here')
116
+ if (opts.keyPair && !b4a.equals(header.signer.publicKey, opts.keyPair.publicKey)) {
117
+ throw STORAGE_CONFLICT('Another Hypercore is stored here')
95
118
  }
96
119
 
97
120
  const tree = await MerkleTree.open(treeFile, { crypto, ...header.tree })
@@ -102,9 +125,18 @@ module.exports = class Core {
102
125
  await tree.clear()
103
126
  await blocks.clear()
104
127
  await bitfield.clear()
128
+ entries = []
129
+ } else if (bitfield.resumed && header.tree.length === 0) {
130
+ // If this was an old bitfield, reset it since it loads based on disk size atm (TODO: change that)
131
+ await bitfield.clear()
132
+ }
133
+
134
+ // compat from earlier version that do not store contig length
135
+ if (header.contiguousLength === 0) {
136
+ while (bitfield.get(header.contiguousLength)) header.contiguousLength++
105
137
  }
106
138
 
107
- const sign = opts.sign || (header.signer.secretKey ? this.createSigner(crypto, header.signer) : null)
139
+ const auth = opts.auth || this.createAuth(crypto, header.signer)
108
140
 
109
141
  for (const e of entries) {
110
142
  if (e.userData) {
@@ -118,7 +150,8 @@ module.exports = class Core {
118
150
  }
119
151
 
120
152
  if (e.bitfield) {
121
- bitfield.setRange(e.bitfield.start, e.bitfield.length)
153
+ bitfield.setRange(e.bitfield.start, e.bitfield.length, !e.bitfield.drop)
154
+ updateContig(header, e.bitfield, bitfield)
122
155
  }
123
156
 
124
157
  if (e.treeUpgrade) {
@@ -135,7 +168,7 @@ module.exports = class Core {
135
168
  }
136
169
  }
137
170
 
138
- return new this(header, crypto, oplog, tree, blocks, bitfield, sign, opts.onupdate || noop)
171
+ return new this(header, crypto, oplog, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop)
139
172
  }
140
173
 
141
174
  _shouldFlush () {
@@ -176,7 +209,7 @@ module.exports = class Core {
176
209
 
177
210
  for (const u of this.header.userData) {
178
211
  if (u.key !== key) continue
179
- if (value && u.value.equals(value)) return
212
+ if (value && b4a.equals(u.value, value)) return
180
213
  empty = false
181
214
  break
182
215
  }
@@ -200,13 +233,13 @@ module.exports = class Core {
200
233
  }
201
234
  }
202
235
 
203
- async truncate (length, fork, sign = this.defaultSign) {
236
+ async truncate (length, fork, auth = this.defaultAuth) {
204
237
  this.truncating++
205
238
  await this._mutex.lock()
206
239
 
207
240
  try {
208
241
  const batch = await this.tree.truncate(length, fork)
209
- batch.signature = await sign(batch.signable())
242
+ batch.signature = await auth.sign(batch.signable())
210
243
  await this._truncate(batch, null)
211
244
  } finally {
212
245
  this.truncating--
@@ -214,19 +247,63 @@ module.exports = class Core {
214
247
  }
215
248
  }
216
249
 
217
- async append (values, sign = this.defaultSign, hooks = {}) {
250
+ async clear (start, end) {
251
+ await this._mutex.lock()
252
+
253
+ const entry = {
254
+ userData: null,
255
+ treeNodes: null,
256
+ treeUpgrade: null,
257
+ bitfield: {
258
+ start,
259
+ length: end - start,
260
+ drop: true
261
+ }
262
+ }
263
+
264
+ await this.oplog.append([entry], false)
265
+
266
+ this.bitfield.setRange(start, end - start, false)
267
+
268
+ if (start < this.header.contiguousLength) {
269
+ this.header.contiguousLength = start
270
+ }
271
+
272
+ start = this.bitfield.lastSet(start) + 1
273
+ end = this.bitfield.firstSet(end)
274
+
275
+ if (end === -1) end = this.tree.length
276
+
277
+ try {
278
+ const offset = await this.tree.byteOffset(start * 2)
279
+ const [byteEnd, byteEndLength] = await this.tree.byteRange((end - 1) * 2)
280
+ const length = (byteEnd + byteEndLength) - offset
281
+
282
+ await this.blocks.clear(offset, length)
283
+
284
+ this.onupdate(0, entry.bitfield, null, null)
285
+
286
+ if (this._shouldFlush()) await this._flushOplog()
287
+ } finally {
288
+ this._mutex.unlock()
289
+ }
290
+ }
291
+
292
+ async append (values, auth = this.defaultAuth, hooks = {}) {
218
293
  await this._mutex.lock()
219
294
 
220
295
  try {
221
296
  if (hooks.preappend) await hooks.preappend(values)
222
297
 
223
- if (!values.length) return this.tree.length
298
+ if (!values.length) {
299
+ return { length: this.tree.length, byteLength: this.tree.byteLength }
300
+ }
224
301
 
225
302
  const batch = this.tree.batch()
226
303
  for (const val of values) batch.append(val)
227
304
 
228
305
  const hash = batch.hash()
229
- batch.signature = await sign(batch.signable(hash))
306
+ batch.signature = await auth.sign(this._legacy ? batch.signableLegacy(hash) : batch.signable(hash))
230
307
 
231
308
  const entry = {
232
309
  userData: null,
@@ -239,7 +316,8 @@ module.exports = class Core {
239
316
  }
240
317
  }
241
318
 
242
- await this._appendBlocks(values)
319
+ const byteLength = await this._appendBlocks(values)
320
+
243
321
  await this.oplog.append([entry], false)
244
322
 
245
323
  this.bitfield.setRange(batch.ancestors, batch.length - batch.ancestors, true)
@@ -248,21 +326,28 @@ module.exports = class Core {
248
326
  this.header.tree.length = batch.length
249
327
  this.header.tree.rootHash = hash
250
328
  this.header.tree.signature = batch.signature
251
- this.onupdate(0b01, entry.bitfield, null, null)
329
+
330
+ const status = 0b0001 | updateContig(this.header, entry.bitfield, this.bitfield)
331
+ this.onupdate(status, entry.bitfield, null, null)
252
332
 
253
333
  if (this._shouldFlush()) await this._flushOplog()
254
334
 
255
- return batch.ancestors
335
+ return { length: batch.length, byteLength }
256
336
  } finally {
257
337
  this._mutex.unlock()
258
338
  }
259
339
  }
260
340
 
341
+ _signed (batch, hash, auth = this.defaultAuth) {
342
+ const signable = this._legacy ? batch.signableLegacy(hash) : batch.signable(hash)
343
+ return auth.verify(signable, batch.signature)
344
+ }
345
+
261
346
  async _verifyExclusive ({ batch, bitfield, value, from }) {
262
347
  // TODO: move this to tree.js
263
348
  const hash = batch.hash()
264
- if (!batch.signature || !this.crypto.verify(batch.signable(hash), batch.signature, this.header.signer.publicKey)) {
265
- throw new Error('Remote signature does not match')
349
+ if (!batch.signature || !this._signed(batch, hash)) {
350
+ throw INVALID_SIGNATURE('Proof contains an invalid signature')
266
351
  }
267
352
 
268
353
  await this._mutex.lock()
@@ -281,14 +366,21 @@ module.exports = class Core {
281
366
 
282
367
  await this.oplog.append([entry], false)
283
368
 
284
- if (bitfield) this.bitfield.set(bitfield.start, true)
369
+ let status = 0b0001
370
+
371
+ if (bitfield) {
372
+ this.bitfield.set(bitfield.start, true)
373
+ status |= updateContig(this.header, bitfield, this.bitfield)
374
+ }
375
+
285
376
  batch.commit()
286
377
 
287
378
  this.header.tree.fork = batch.fork
288
379
  this.header.tree.length = batch.length
289
380
  this.header.tree.rootHash = batch.rootHash
290
381
  this.header.tree.signature = batch.signature
291
- this.onupdate(0b01, bitfield, value, from)
382
+
383
+ this.onupdate(status, bitfield, value, from)
292
384
 
293
385
  if (this._shouldFlush()) await this._flushOplog()
294
386
  } finally {
@@ -335,9 +427,16 @@ module.exports = class Core {
335
427
  continue
336
428
  }
337
429
 
338
- if (bitfield) this.bitfield.set(bitfield.start, true)
430
+ let status = 0
431
+
432
+ if (bitfield) {
433
+ this.bitfield.set(bitfield.start, true)
434
+ status = updateContig(this.header, bitfield, this.bitfield)
435
+ }
436
+
339
437
  batch.commit()
340
- this.onupdate(0, bitfield, value, from)
438
+
439
+ this.onupdate(status, bitfield, value, from)
341
440
  }
342
441
 
343
442
  if (this._shouldFlush()) await this._flushOplog()
@@ -415,13 +514,15 @@ module.exports = class Core {
415
514
  addReorgHint(this.header.hints.reorgs, this.tree, batch)
416
515
  batch.commit()
417
516
 
418
- const appended = batch.length > batch.ancestors
517
+ const contigStatus = updateContig(this.header, entry.bitfield, this.bitfield)
518
+ const status = ((batch.length > batch.ancestors) ? 0b0011 : 0b0010) | contigStatus
419
519
 
420
520
  this.header.tree.fork = batch.fork
421
521
  this.header.tree.length = batch.length
422
522
  this.header.tree.rootHash = batch.hash()
423
523
  this.header.tree.signature = batch.signature
424
- this.onupdate(appended ? 0b11 : 0b10, entry.bitfield, null, from)
524
+
525
+ this.onupdate(status, entry.bitfield, null, from)
425
526
 
426
527
  // TODO: there is a bug in the merkle tree atm where it cannot handle unflushed
427
528
  // truncates if we append or download anything after the truncation point later on
@@ -442,6 +543,36 @@ module.exports = class Core {
442
543
  }
443
544
  }
444
545
 
546
+ function updateContig (header, upd, bitfield) {
547
+ const end = upd.start + upd.length
548
+
549
+ let c = header.contiguousLength
550
+
551
+ if (upd.drop) {
552
+ // If we dropped a block in the current contig range, "downgrade" it
553
+ if (c <= end && c > upd.start) {
554
+ c = upd.start
555
+ }
556
+ } else {
557
+ if (c <= end && c >= upd.start) {
558
+ c = end
559
+ while (bitfield.get(c)) c++
560
+ }
561
+ }
562
+
563
+ if (c === header.contiguousLength) {
564
+ return 0b0000
565
+ }
566
+
567
+ if (c > header.contiguousLength) {
568
+ header.contiguousLength = c
569
+ return 0b0100
570
+ }
571
+
572
+ header.contiguousLength = c
573
+ return 0b1000
574
+ }
575
+
445
576
  function addReorgHint (list, tree, batch) {
446
577
  if (tree.length === 0 || tree.fork === batch.fork) return
447
578