@atproto/repo 0.10.2 → 0.10.3

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +22 -17
  3. package/jest.config.cjs +0 -24
  4. package/src/block-map.ts +0 -131
  5. package/src/car.ts +0 -357
  6. package/src/cid-set.ts +0 -55
  7. package/src/data-diff.ts +0 -117
  8. package/src/error.ts +0 -43
  9. package/src/index.ts +0 -11
  10. package/src/logger.ts +0 -7
  11. package/src/mst/diff.ts +0 -114
  12. package/src/mst/index.ts +0 -4
  13. package/src/mst/mst.ts +0 -892
  14. package/src/mst/util.ts +0 -160
  15. package/src/mst/walker.ts +0 -118
  16. package/src/parse.ts +0 -44
  17. package/src/readable-repo.ts +0 -86
  18. package/src/repo.ts +0 -236
  19. package/src/storage/index.ts +0 -4
  20. package/src/storage/memory-blockstore.ts +0 -76
  21. package/src/storage/readable-blockstore.ts +0 -55
  22. package/src/storage/sync-storage.ts +0 -35
  23. package/src/storage/types.ts +0 -47
  24. package/src/sync/consumer.ts +0 -207
  25. package/src/sync/index.ts +0 -2
  26. package/src/sync/provider.ts +0 -67
  27. package/src/types.ts +0 -227
  28. package/src/util.ts +0 -146
  29. package/tests/_keys.ts +0 -156
  30. package/tests/_util.ts +0 -265
  31. package/tests/car-file-fixtures.json +0 -28
  32. package/tests/car.test.ts +0 -125
  33. package/tests/commit-data.test.ts +0 -94
  34. package/tests/commit-proof-fixtures.json +0 -118
  35. package/tests/commit-proofs.test.ts +0 -63
  36. package/tests/covering-proofs.test.ts +0 -256
  37. package/tests/mst.test.ts +0 -450
  38. package/tests/proofs.test.ts +0 -155
  39. package/tests/repo.test.ts +0 -106
  40. package/tests/sync.test.ts +0 -95
  41. package/tsconfig.build.json +0 -8
  42. package/tsconfig.build.tsbuildinfo +0 -1
  43. package/tsconfig.json +0 -7
  44. package/tsconfig.tests.json +0 -7
package/src/mst/mst.ts DELETED
@@ -1,892 +0,0 @@
1
- import { z } from 'zod'
2
- import { cidForLex, encode } from '@atproto/lex-cbor'
3
- import { Cid, cidForCbor } from '@atproto/lex-data'
4
- import { BlockMap } from '../block-map.js'
5
- import { CidSet } from '../cid-set.js'
6
- import { MissingBlockError, MissingBlocksError } from '../error.js'
7
- import * as parse from '../parse.js'
8
- import { ReadableBlockstore } from '../storage/index.js'
9
- import { CarBlock, schema } from '../types.js'
10
- import * as util from './util.js'
11
-
12
- /**
13
- * This is an implementation of a Merkle Search Tree (MST)
14
- * The data structure is described here: https://hal.inria.fr/hal-02303490/document
15
- * The MST is an ordered, insert-order-independent, deterministic tree.
16
- * Keys are laid out in alphabetic order.
17
- * The key insight of an MST is that each key is hashed and starting 0s are counted
18
- * to determine which layer it falls on (5 zeros for ~32 fanout).
19
- * This is a merkle tree, so each subtree is referred to by it's hash (Cid).
20
- * When a leaf is changed, ever tree on the path to that leaf is changed as well,
21
- * thereby updating the root hash.
22
- *
23
- * For atproto, we use SHA-256 as the key hashing algorithm, and ~4 fanout
24
- * (2-bits of zero per layer).
25
- */
26
-
27
- /**
28
- * A couple notes on CBOR encoding:
29
- *
30
- * There are never two neighboring subtrees.
31
- * Therefore, we can represent a node as an array of
32
- * leaves & pointers to their right neighbor (possibly null),
33
- * along with a pointer to the left-most subtree (also possibly null).
34
- *
35
- * Most keys in a subtree will have overlap.
36
- * We do compression on prefixes by describing keys as:
37
- * - the length of the prefix that it shares in common with the preceding key
38
- * - the rest of the string
39
- *
40
- * For example:
41
- * If the first leaf in a tree is `bsky/posts/abcdefg` and the second is `bsky/posts/abcdehi`
42
- * Then the first will be described as `prefix: 0, key: 'bsky/posts/abcdefg'`,
43
- * and the second will be described as `prefix: 16, key: 'hi'.`
44
- */
45
- const subTreePointer = z.nullable(schema.cid)
46
- const treeEntry = z.object({
47
- p: z.number(), // prefix count of ascii chars that this key shares with the prev key
48
- k: schema.bytes, // the rest of the key outside the shared prefix
49
- v: schema.cid, // value
50
- t: subTreePointer, // next subtree (to the right of leaf)
51
- })
52
- const nodeData = z.object({
53
- l: subTreePointer, // left-most subtree
54
- e: z.array(treeEntry), //entries
55
- })
56
- export type NodeData = z.infer<typeof nodeData>
57
-
58
- export const nodeDataDef = {
59
- name: 'mst node',
60
- schema: nodeData,
61
- }
62
-
63
- export type NodeEntry = MST | Leaf
64
-
65
- export type MstOpts = {
66
- layer: number
67
- }
68
-
69
- export class MST {
70
- storage: ReadableBlockstore
71
- entries: NodeEntry[] | null
72
- layer: number | null
73
- pointer: Cid
74
- outdatedPointer = false
75
-
76
- constructor(
77
- storage: ReadableBlockstore,
78
- pointer: Cid,
79
- entries: NodeEntry[] | null,
80
- layer: number | null,
81
- ) {
82
- this.storage = storage
83
- this.entries = entries
84
- this.layer = layer
85
- this.pointer = pointer
86
- }
87
-
88
- static async create(
89
- storage: ReadableBlockstore,
90
- entries: NodeEntry[] = [],
91
- opts?: Partial<MstOpts>,
92
- ): Promise<MST> {
93
- const pointer = await util.cidForEntries(entries)
94
- const { layer = null } = opts || {}
95
- return new MST(storage, pointer, entries, layer)
96
- }
97
-
98
- static async fromData(
99
- storage: ReadableBlockstore,
100
- data: NodeData,
101
- opts?: Partial<MstOpts>,
102
- ): Promise<MST> {
103
- const { layer = null } = opts || {}
104
- const entries = await util.deserializeNodeData(storage, data, opts)
105
- const pointer = await cidForLex(data)
106
- return new MST(storage, pointer, entries, layer)
107
- }
108
-
109
- // this is really a *lazy* load, doesn't actually touch storage
110
- static load(
111
- storage: ReadableBlockstore,
112
- cid: Cid,
113
- opts?: Partial<MstOpts>,
114
- ): MST {
115
- const { layer = null } = opts || {}
116
- return new MST(storage, cid, null, layer)
117
- }
118
-
119
- // Immutability
120
- // -------------------
121
-
122
- // We never mutate an MST, we just return a new MST with updated values
123
- async newTree(entries: NodeEntry[]): Promise<MST> {
124
- const mst = new MST(this.storage, this.pointer, entries, this.layer)
125
- mst.outdatedPointer = true
126
- return mst
127
- }
128
-
129
- // Getters (lazy load)
130
- // -------------------
131
-
132
- // We don't want to load entries of every subtree, just the ones we need
133
- async getEntries(): Promise<NodeEntry[]> {
134
- if (this.entries) return [...this.entries]
135
- if (this.pointer) {
136
- const data = await this.storage.readObj(this.pointer, nodeDataDef)
137
- const firstLeaf = data.e[0]
138
- const layer =
139
- firstLeaf !== undefined
140
- ? await util.leadingZerosOnHash(firstLeaf.k)
141
- : undefined
142
- this.entries = await util.deserializeNodeData(this.storage, data, {
143
- layer,
144
- })
145
-
146
- return this.entries
147
- }
148
- throw new Error('No entries or Cid provided')
149
- }
150
-
151
- // We don't hash the node on every mutation for performance reasons
152
- // Instead we keep track of whether the pointer is outdated and only (recursively) calculate when needed
153
- async getPointer(): Promise<Cid> {
154
- if (!this.outdatedPointer) return this.pointer
155
- const { cid } = await this.serialize()
156
- this.pointer = cid
157
- this.outdatedPointer = false
158
- return this.pointer
159
- }
160
-
161
- async serialize(): Promise<{ cid: Cid; bytes: Uint8Array }> {
162
- let entries = await this.getEntries()
163
- const outdated = entries.filter(
164
- (e) => e.isTree() && e.outdatedPointer,
165
- ) as MST[]
166
- if (outdated.length > 0) {
167
- await Promise.all(outdated.map((e) => e.getPointer()))
168
- entries = await this.getEntries()
169
- }
170
- const data = util.serializeNodeData(entries)
171
- const bytes = encode(data)
172
- const cid = await cidForCbor(bytes)
173
- return { cid, bytes }
174
- }
175
-
176
- // In most cases, we get the layer of a node from a hint on creation
177
- // In the case of the topmost node in the tree, we look for a key in the node & determine the layer
178
- // In the case where we don't find one, we recurse down until we do.
179
- // If we still can't find one, then we have an empty tree and the node is layer 0
180
- async getLayer(): Promise<number> {
181
- this.layer = await this.attemptGetLayer()
182
- if (this.layer === null) this.layer = 0
183
- return this.layer
184
- }
185
-
186
- async attemptGetLayer(): Promise<number | null> {
187
- if (this.layer !== null) return this.layer
188
- const entries = await this.getEntries()
189
- let layer = await util.layerForEntries(entries)
190
- if (layer === null) {
191
- for (const entry of entries) {
192
- if (entry.isTree()) {
193
- const childLayer = await entry.attemptGetLayer()
194
- if (childLayer !== null) {
195
- layer = childLayer + 1
196
- break
197
- }
198
- }
199
- }
200
- }
201
- if (layer !== null) this.layer = layer
202
- return layer
203
- }
204
-
205
- // Core functionality
206
- // -------------------
207
-
208
- // Return the necessary blocks to persist the MST to repo storage
209
- async getUnstoredBlocks(): Promise<{ root: Cid; blocks: BlockMap }> {
210
- const blocks = new BlockMap()
211
- const pointer = await this.getPointer()
212
- const alreadyHas = await this.storage.has(pointer)
213
- if (alreadyHas) return { root: pointer, blocks }
214
- const entries = await this.getEntries()
215
- const data = util.serializeNodeData(entries)
216
- await blocks.add(data)
217
- for (const entry of entries) {
218
- if (entry.isTree()) {
219
- const subtree = await entry.getUnstoredBlocks()
220
- blocks.addMap(subtree.blocks)
221
- }
222
- }
223
- return { root: pointer, blocks: blocks }
224
- }
225
-
226
- // Adds a new leaf for the given key/value pair
227
- // Throws if a leaf with that key already exists
228
- async add(key: string, value: Cid, knownZeros?: number): Promise<MST> {
229
- util.ensureValidMstKey(key)
230
- const keyZeros = knownZeros ?? (await util.leadingZerosOnHash(key))
231
- const layer = await this.getLayer()
232
- const newLeaf = new Leaf(key, value)
233
- if (keyZeros === layer) {
234
- // it belongs in this layer
235
- const index = await this.findGtOrEqualLeafIndex(key)
236
- const found = await this.atIndex(index)
237
- if (found?.isLeaf() && found.key === key) {
238
- throw new Error(`There is already a value at key: ${key}`)
239
- }
240
- const prevNode = await this.atIndex(index - 1)
241
- if (!prevNode || prevNode.isLeaf()) {
242
- // if entry before is a leaf, (or we're on far left) we can just splice in
243
- return this.spliceIn(newLeaf, index)
244
- } else {
245
- // else we try to split the subtree around the key
246
- const splitSubTree = await prevNode.splitAround(key)
247
- return this.replaceWithSplit(
248
- index - 1,
249
- splitSubTree[0],
250
- newLeaf,
251
- splitSubTree[1],
252
- )
253
- }
254
- } else if (keyZeros < layer) {
255
- // it belongs on a lower layer
256
- const index = await this.findGtOrEqualLeafIndex(key)
257
- const prevNode = await this.atIndex(index - 1)
258
- if (prevNode && prevNode.isTree()) {
259
- // if entry before is a tree, we add it to that tree
260
- const newSubtree = await prevNode.add(key, value, keyZeros)
261
- return this.updateEntry(index - 1, newSubtree)
262
- } else {
263
- const subTree = await this.createChild()
264
- const newSubTree = await subTree.add(key, value, keyZeros)
265
- return this.spliceIn(newSubTree, index)
266
- }
267
- } else {
268
- // it belongs on a higher layer & we must push the rest of the tree down
269
- const split = await this.splitAround(key)
270
- // if the newly added key has >=2 more leading zeros than the current highest layer
271
- // then we need to add in structural nodes in between as well
272
- let left: MST | null = split[0]
273
- let right: MST | null = split[1]
274
- const layer = await this.getLayer()
275
- const extraLayersToAdd = keyZeros - layer
276
- // intentionally starting at 1, since first layer is taken care of by split
277
- for (let i = 1; i < extraLayersToAdd; i++) {
278
- if (left !== null) {
279
- left = await left.createParent()
280
- }
281
- if (right !== null) {
282
- right = await right.createParent()
283
- }
284
- }
285
- const updated: NodeEntry[] = []
286
- if (left) updated.push(left)
287
- updated.push(new Leaf(key, value))
288
- if (right) updated.push(right)
289
- const newRoot = await MST.create(this.storage, updated, {
290
- layer: keyZeros,
291
- })
292
- newRoot.outdatedPointer = true
293
- return newRoot
294
- }
295
- }
296
-
297
- // Gets the value at the given key
298
- async get(key: string): Promise<Cid | null> {
299
- const index = await this.findGtOrEqualLeafIndex(key)
300
- const found = await this.atIndex(index)
301
- if (found && found.isLeaf() && found.key === key) {
302
- return found.value
303
- }
304
- const prev = await this.atIndex(index - 1)
305
- if (prev && prev.isTree()) {
306
- return prev.get(key)
307
- }
308
- return null
309
- }
310
-
311
- // Edits the value at the given key
312
- // Throws if the given key does not exist
313
- async update(key: string, value: Cid): Promise<MST> {
314
- util.ensureValidMstKey(key)
315
- const index = await this.findGtOrEqualLeafIndex(key)
316
- const found = await this.atIndex(index)
317
- if (found && found.isLeaf() && found.key === key) {
318
- return this.updateEntry(index, new Leaf(key, value))
319
- }
320
- const prev = await this.atIndex(index - 1)
321
- if (prev && prev.isTree()) {
322
- const updatedTree = await prev.update(key, value)
323
- return this.updateEntry(index - 1, updatedTree)
324
- }
325
- throw new Error(`Could not find a record with key: ${key}`)
326
- }
327
-
328
- // Deletes the value at the given key
329
- async delete(key: string): Promise<MST> {
330
- const altered = await this.deleteRecurse(key)
331
- return altered.trimTop()
332
- }
333
-
334
- async deleteRecurse(key: string): Promise<MST> {
335
- const index = await this.findGtOrEqualLeafIndex(key)
336
- const found = await this.atIndex(index)
337
- // if found, remove it on this level
338
- if (found?.isLeaf() && found.key === key) {
339
- const prev = await this.atIndex(index - 1)
340
- const next = await this.atIndex(index + 1)
341
- if (prev?.isTree() && next?.isTree()) {
342
- const merged = await prev.appendMerge(next)
343
- return this.newTree([
344
- ...(await this.slice(0, index - 1)),
345
- merged,
346
- ...(await this.slice(index + 2)),
347
- ])
348
- } else {
349
- return this.removeEntry(index)
350
- }
351
- }
352
- // else recurse down to find it
353
- const prev = await this.atIndex(index - 1)
354
- if (prev?.isTree()) {
355
- const subtree = await prev.deleteRecurse(key)
356
- const subTreeEntries = await subtree.getEntries()
357
- if (subTreeEntries.length === 0) {
358
- return this.removeEntry(index - 1)
359
- } else {
360
- return this.updateEntry(index - 1, subtree)
361
- }
362
- } else {
363
- throw new Error(`Could not find a record with key: ${key}`)
364
- }
365
- }
366
-
367
- // Simple Operations
368
- // -------------------
369
-
370
- // update entry in place
371
- async updateEntry(index: number, entry: NodeEntry): Promise<MST> {
372
- const update = [
373
- ...(await this.slice(0, index)),
374
- entry,
375
- ...(await this.slice(index + 1)),
376
- ]
377
- return this.newTree(update)
378
- }
379
-
380
- // remove entry at index
381
- async removeEntry(index: number): Promise<MST> {
382
- const updated = [
383
- ...(await this.slice(0, index)),
384
- ...(await this.slice(index + 1)),
385
- ]
386
- return this.newTree(updated)
387
- }
388
-
389
- // append entry to end of the node
390
- async append(entry: NodeEntry): Promise<MST> {
391
- const entries = await this.getEntries()
392
- return this.newTree([...entries, entry])
393
- }
394
-
395
- // prepend entry to start of the node
396
- async prepend(entry: NodeEntry): Promise<MST> {
397
- const entries = await this.getEntries()
398
- return this.newTree([entry, ...entries])
399
- }
400
-
401
- // returns entry at index
402
- async atIndex(index: number): Promise<NodeEntry | null> {
403
- const entries = await this.getEntries()
404
- return entries[index] ?? null
405
- }
406
-
407
- // returns a slice of the node (like array.slice)
408
- async slice(
409
- start?: number | undefined,
410
- end?: number | undefined,
411
- ): Promise<NodeEntry[]> {
412
- const entries = await this.getEntries()
413
- return entries.slice(start, end)
414
- }
415
-
416
- // inserts entry at index
417
- async spliceIn(entry: NodeEntry, index: number): Promise<MST> {
418
- const update = [
419
- ...(await this.slice(0, index)),
420
- entry,
421
- ...(await this.slice(index)),
422
- ]
423
- return this.newTree(update)
424
- }
425
-
426
- // replaces an entry with [ Maybe(tree), Leaf, Maybe(tree) ]
427
- async replaceWithSplit(
428
- index: number,
429
- left: MST | null,
430
- leaf: Leaf,
431
- right: MST | null,
432
- ): Promise<MST> {
433
- const update = await this.slice(0, index)
434
- if (left) update.push(left)
435
- update.push(leaf)
436
- if (right) update.push(right)
437
- update.push(...(await this.slice(index + 1)))
438
- return this.newTree(update)
439
- }
440
-
441
- // if the topmost node in the tree only points to another tree, trim the top and return the subtree
442
- async trimTop(): Promise<MST> {
443
- let entries: NodeEntry[]
444
- try {
445
- entries = await this.getEntries()
446
- } catch (err) {
447
- if (err instanceof MissingBlockError) {
448
- return this
449
- } else {
450
- throw err
451
- }
452
- }
453
- if (entries.length === 1 && entries[0].isTree()) {
454
- return entries[0].trimTop()
455
- } else {
456
- return this
457
- }
458
- }
459
-
460
- // Subtree & Splits
461
- // -------------------
462
-
463
- // Recursively splits a sub tree around a given key
464
- async splitAround(key: string): Promise<[MST | null, MST | null]> {
465
- const index = await this.findGtOrEqualLeafIndex(key)
466
- // split tree around key
467
- const leftData = await this.slice(0, index)
468
- const rightData = await this.slice(index)
469
- let left = await this.newTree(leftData)
470
- let right = await this.newTree(rightData)
471
-
472
- // if the far right of the left side is a subtree,
473
- // we need to split it on the key as well
474
- const lastInLeft = leftData[leftData.length - 1]
475
- if (lastInLeft?.isTree()) {
476
- left = await left.removeEntry(leftData.length - 1)
477
- const split = await lastInLeft.splitAround(key)
478
- if (split[0]) {
479
- left = await left.append(split[0])
480
- }
481
- if (split[1]) {
482
- right = await right.prepend(split[1])
483
- }
484
- }
485
-
486
- return [
487
- (await left.getEntries()).length > 0 ? left : null,
488
- (await right.getEntries()).length > 0 ? right : null,
489
- ]
490
- }
491
-
492
- // The simple merge case where every key in the right tree is greater than every key in the left tree
493
- // (used primarily for deletes)
494
- async appendMerge(toMerge: MST): Promise<MST> {
495
- if ((await this.getLayer()) !== (await toMerge.getLayer())) {
496
- throw new Error(
497
- 'Trying to merge two nodes from different layers of the MST',
498
- )
499
- }
500
- const thisEntries = await this.getEntries()
501
- const toMergeEntries = await toMerge.getEntries()
502
- const lastInLeft = thisEntries[thisEntries.length - 1]
503
- const firstInRight = toMergeEntries[0]
504
- if (lastInLeft?.isTree() && firstInRight?.isTree()) {
505
- const merged = await lastInLeft.appendMerge(firstInRight)
506
- return this.newTree([
507
- ...thisEntries.slice(0, thisEntries.length - 1),
508
- merged,
509
- ...toMergeEntries.slice(1),
510
- ])
511
- } else {
512
- return this.newTree([...thisEntries, ...toMergeEntries])
513
- }
514
- }
515
-
516
- // Create relatives
517
- // -------------------
518
-
519
- async createChild(): Promise<MST> {
520
- const layer = await this.getLayer()
521
- return MST.create(this.storage, [], {
522
- layer: layer - 1,
523
- })
524
- }
525
-
526
- async createParent(): Promise<MST> {
527
- const layer = await this.getLayer()
528
- const parent = await MST.create(this.storage, [this], {
529
- layer: layer + 1,
530
- })
531
- parent.outdatedPointer = true
532
- return parent
533
- }
534
-
535
- // Finding insertion points
536
- // -------------------
537
-
538
- // finds index of first leaf node that is greater than or equal to the value
539
- async findGtOrEqualLeafIndex(key: string): Promise<number> {
540
- const entries = await this.getEntries()
541
- const maybeIndex = entries.findIndex(
542
- (entry) => entry.isLeaf() && entry.key >= key,
543
- )
544
- // if we can't find, we're on the end
545
- return maybeIndex >= 0 ? maybeIndex : entries.length
546
- }
547
-
548
- // List operations (partial tree traversal)
549
- // -------------------
550
-
551
- // @TODO write tests for these
552
-
553
- // Walk tree starting at key
554
- async *walkFrom(key: string): AsyncIterable<NodeEntry> {
555
- yield this
556
- const index = await this.findGtOrEqualLeafIndex(key)
557
- const entries = await this.getEntries()
558
- const found = entries[index]
559
- if (found && found.isLeaf() && found.key === key) {
560
- yield found
561
- } else {
562
- const prev = entries[index - 1]
563
- if (prev) {
564
- if (prev.isLeaf() && prev.key === key) {
565
- yield prev
566
- } else if (prev.isTree()) {
567
- yield* prev.walkFrom(key)
568
- }
569
- }
570
- }
571
-
572
- for (let i = index; i < entries.length; i++) {
573
- const entry = entries[i]
574
- if (entry.isLeaf()) {
575
- yield entry
576
- } else {
577
- yield* entry.walkFrom(key)
578
- }
579
- }
580
- }
581
-
582
- async *walkLeavesFrom(key: string): AsyncIterable<Leaf> {
583
- for await (const node of this.walkFrom(key)) {
584
- if (node.isLeaf()) {
585
- yield node
586
- }
587
- }
588
- }
589
-
590
- async list(
591
- count = Number.MAX_SAFE_INTEGER,
592
- after?: string,
593
- before?: string,
594
- ): Promise<Leaf[]> {
595
- const vals: Leaf[] = []
596
- for await (const leaf of this.walkLeavesFrom(after || '')) {
597
- if (leaf.key === after) continue
598
- if (vals.length >= count) break
599
- if (before && leaf.key >= before) break
600
- vals.push(leaf)
601
- }
602
- return vals
603
- }
604
-
605
- async listWithPrefix(
606
- prefix: string,
607
- count = Number.MAX_SAFE_INTEGER,
608
- ): Promise<Leaf[]> {
609
- const vals: Leaf[] = []
610
- for await (const leaf of this.walkLeavesFrom(prefix)) {
611
- if (vals.length >= count || !leaf.key.startsWith(prefix)) break
612
- vals.push(leaf)
613
- }
614
- return vals
615
- }
616
-
617
- // Full tree traversal
618
- // -------------------
619
-
620
- // Walk full tree & emit nodes, consumer can bail at any point by returning false
621
- async *walk(): AsyncIterable<NodeEntry> {
622
- yield this
623
- const entries = await this.getEntries()
624
- for (const entry of entries) {
625
- if (entry.isTree()) {
626
- for await (const e of entry.walk()) {
627
- yield e
628
- }
629
- } else {
630
- yield entry
631
- }
632
- }
633
- }
634
-
635
- // Walk full tree & emit nodes, consumer can bail at any point by returning false
636
- async paths(): Promise<NodeEntry[][]> {
637
- const entries = await this.getEntries()
638
- let paths: NodeEntry[][] = []
639
- for (const entry of entries) {
640
- if (entry.isLeaf()) {
641
- paths.push([entry])
642
- }
643
- if (entry.isTree()) {
644
- const subPaths = await entry.paths()
645
- paths = [...paths, ...subPaths.map((p) => [entry, ...p])]
646
- }
647
- }
648
- return paths
649
- }
650
-
651
- // Walks tree & returns all nodes
652
- async allNodes(): Promise<NodeEntry[]> {
653
- const nodes: NodeEntry[] = []
654
- for await (const entry of this.walk()) {
655
- nodes.push(entry)
656
- }
657
- return nodes
658
- }
659
-
660
- // Walks tree & returns all cids
661
- async allCids(): Promise<CidSet> {
662
- const cids = new CidSet()
663
- const entries = await this.getEntries()
664
- for (const entry of entries) {
665
- if (entry.isLeaf()) {
666
- cids.add(entry.value)
667
- } else {
668
- const subtreeCids = await entry.allCids()
669
- cids.addSet(subtreeCids)
670
- }
671
- }
672
- cids.add(await this.getPointer())
673
- return cids
674
- }
675
-
676
- // Walks tree & returns all leaves
677
- async leaves() {
678
- const leaves: Leaf[] = []
679
- for await (const entry of this.walk()) {
680
- if (entry.isLeaf()) leaves.push(entry)
681
- }
682
- return leaves
683
- }
684
-
685
- // Returns total leaf count
686
- async leafCount(): Promise<number> {
687
- const leaves = await this.leaves()
688
- return leaves.length
689
- }
690
-
691
- // Reachable tree traversal
692
- // -------------------
693
-
694
- // Walk reachable branches of tree & emit nodes, consumer can bail at any point by returning false
695
- async *walkReachable(): AsyncIterable<NodeEntry> {
696
- yield this
697
- const entries = await this.getEntries()
698
- for (const entry of entries) {
699
- if (entry.isTree()) {
700
- try {
701
- for await (const e of entry.walkReachable()) {
702
- yield e
703
- }
704
- } catch (err) {
705
- if (err instanceof MissingBlockError) {
706
- continue
707
- } else {
708
- throw err
709
- }
710
- }
711
- } else {
712
- yield entry
713
- }
714
- }
715
- }
716
-
717
- async reachableLeaves(): Promise<Leaf[]> {
718
- const leaves: Leaf[] = []
719
- for await (const entry of this.walkReachable()) {
720
- if (entry.isLeaf()) leaves.push(entry)
721
- }
722
- return leaves
723
- }
724
-
725
- // Sync Protocol
726
-
727
- async *carBlockStream(): AsyncIterable<CarBlock> {
728
- const leaves = new CidSet()
729
- let toFetch = new CidSet()
730
- toFetch.add(await this.getPointer())
731
- while (toFetch.size() > 0) {
732
- const nextLayer = new CidSet()
733
- const fetched = await this.storage.getBlocks(toFetch.toList())
734
- if (fetched.missing.length > 0) {
735
- throw new MissingBlocksError('mst node', fetched.missing)
736
- }
737
- for (const cid of toFetch.toList()) {
738
- const found = await parse.getAndParseByDef(
739
- fetched.blocks,
740
- cid,
741
- nodeDataDef,
742
- )
743
- yield { cid, bytes: found.bytes }
744
- const entries = await util.deserializeNodeData(this.storage, found.obj)
745
-
746
- for (const entry of entries) {
747
- if (entry.isLeaf()) {
748
- leaves.add(entry.value)
749
- } else {
750
- nextLayer.add(await entry.getPointer())
751
- }
752
- }
753
- }
754
- toFetch = nextLayer
755
- }
756
- const leafData = await this.storage.getBlocks(leaves.toList())
757
- if (leafData.missing.length > 0) {
758
- throw new MissingBlocksError('mst leaf', leafData.missing)
759
- }
760
-
761
- for (const leaf of leafData.blocks.entries()) {
762
- yield leaf
763
- }
764
- }
765
-
766
- async cidsForPath(key: string): Promise<Cid[]> {
767
- const cids: Cid[] = [await this.getPointer()]
768
- const index = await this.findGtOrEqualLeafIndex(key)
769
- const found = await this.atIndex(index)
770
- if (found && found.isLeaf() && found.key === key) {
771
- return [...cids, found.value]
772
- }
773
- const prev = await this.atIndex(index - 1)
774
- if (prev && prev.isTree()) {
775
- return [...cids, ...(await prev.cidsForPath(key))]
776
- }
777
- return cids
778
- }
779
-
780
- // A covering proof is all MST nodes (leaves excluded) needed to prove the value of a given leaf
781
- // and its siblings to its immediate right and left (if applicable)
782
- // We simply find the immediately preceeding node and then walk from that node until we reach the
783
- // first key that is greater than the requested key (the right sibling)
784
- async getCoveringProof(key: string): Promise<BlockMap> {
785
- const [self, left, right] = await Promise.all([
786
- this.proofForKey(key),
787
- this.proofForLeftSib(key),
788
- this.proofForRightSib(key),
789
- ])
790
- return self.addMap(left).addMap(right)
791
- }
792
-
793
- async proofForKey(key: string): Promise<BlockMap> {
794
- const index = await this.findGtOrEqualLeafIndex(key)
795
- const found = await this.atIndex(index)
796
- let blocks: BlockMap
797
- if (found && found.isLeaf() && found.key === key) {
798
- blocks = new BlockMap()
799
- } else {
800
- const prev = await this.atIndex(index - 1)
801
- if (!prev || prev.isLeaf()) {
802
- return new BlockMap()
803
- } else {
804
- blocks = await prev.proofForKey(key)
805
- }
806
- }
807
- const serialized = await this.serialize()
808
- return blocks.set(serialized.cid, serialized.bytes)
809
- }
810
-
811
- async proofForLeftSib(key: string): Promise<BlockMap> {
812
- const index = await this.findGtOrEqualLeafIndex(key)
813
- const prev = await this.atIndex(index - 1)
814
- let blocks: BlockMap
815
- if (!prev || prev.isLeaf()) {
816
- blocks = new BlockMap()
817
- } else {
818
- blocks = await prev.proofForLeftSib(key)
819
- }
820
- const serialized = await this.serialize()
821
- return blocks.set(serialized.cid, serialized.bytes)
822
- }
823
-
824
- async proofForRightSib(key: string): Promise<BlockMap> {
825
- const index = await this.findGtOrEqualLeafIndex(key)
826
- let found = await this.atIndex(index)
827
- if (!found) {
828
- found = await this.atIndex(index - 1)
829
- }
830
- let blocks: BlockMap
831
- if (!found) {
832
- // shouldn't ever hit, null case
833
- blocks = new BlockMap()
834
- } else if (found.isTree()) {
835
- blocks = await found.proofForRightSib(key)
836
- // recurse down
837
- } else {
838
- const node =
839
- found.key === key
840
- ? await this.atIndex(index + 1)
841
- : await this.atIndex(index - 1)
842
- if (!node || node.isLeaf()) {
843
- blocks = new BlockMap()
844
- } else {
845
- blocks = await node.proofForRightSib(key)
846
- }
847
- }
848
- const serialized = await this.serialize()
849
- return blocks.set(serialized.cid, serialized.bytes)
850
- }
851
-
852
- // Matching Leaf interface
853
- // -------------------
854
-
855
- isTree(): this is MST {
856
- return true
857
- }
858
-
859
- isLeaf(): this is Leaf {
860
- return false
861
- }
862
-
863
- async equals(other: NodeEntry): Promise<boolean> {
864
- if (other.isLeaf()) return false
865
- const thisPointer = await this.getPointer()
866
- const otherPointer = await other.getPointer()
867
- return thisPointer.equals(otherPointer)
868
- }
869
- }
870
-
871
- export class Leaf {
872
- constructor(
873
- public key: string,
874
- public value: Cid,
875
- ) {}
876
-
877
- isTree(): this is MST {
878
- return false
879
- }
880
-
881
- isLeaf(): this is Leaf {
882
- return true
883
- }
884
-
885
- equals(entry: NodeEntry): boolean {
886
- if (entry.isLeaf()) {
887
- return this.key === entry.key && this.value.equals(entry.value)
888
- } else {
889
- return false
890
- }
891
- }
892
- }