···11+export * from "./block-map.ts";
22+export * from "./cid-set.ts";
33+export * from "./repo.ts";
44+export * from "./mst/index.ts";
55+export * from "./parse.ts";
66+export * from "./storage/index.ts";
77+export * from "./sync/index.ts";
88+export * from "./types.ts";
99+export * from "./data-diff.ts";
1010+export * from "./car.ts";
1111+export * from "./util.ts";
+114
repo/mst/diff.ts
···11+import { DataDiff } from "../data-diff.ts";
22+import type { MST } from "./mst.ts";
33+import { MstWalker } from "./walker.ts";
44+55+export const nullDiff = async (tree: MST): Promise<DataDiff> => {
66+ const diff = new DataDiff();
77+ for await (const entry of tree.walk()) {
88+ await diff.nodeAdd(entry);
99+ }
1010+ return diff;
1111+};
1212+1313+export const mstDiff = async (
1414+ curr: MST,
1515+ prev: MST | null,
1616+): Promise<DataDiff> => {
1717+ await curr.getPointer();
1818+ if (prev === null) {
1919+ return nullDiff(curr);
2020+ }
2121+2222+ await prev.getPointer();
2323+ const diff = new DataDiff();
2424+2525+ const leftWalker = new MstWalker(prev);
2626+ const rightWalker = new MstWalker(curr);
2727+ while (!leftWalker.status.done || !rightWalker.status.done) {
2828+ // if one walker is finished, continue walking the other & logging all nodes
2929+ if (leftWalker.status.done && !rightWalker.status.done) {
3030+ await diff.nodeAdd(rightWalker.status.curr);
3131+ await rightWalker.advance();
3232+ continue;
3333+ } else if (!leftWalker.status.done && rightWalker.status.done) {
3434+ await diff.nodeDelete(leftWalker.status.curr);
3535+ await leftWalker.advance();
3636+ continue;
3737+ }
3838+ if (leftWalker.status.done || rightWalker.status.done) break;
3939+ const left = leftWalker.status.curr;
4040+ const right = rightWalker.status.curr;
4141+ if (left === null || right === null) break;
4242+4343+ // if both pointers are leaves, record an update & advance both or record the lowest key and advance that pointer
4444+ if (left.isLeaf() && right.isLeaf()) {
4545+ if (left.key === right.key) {
4646+ if (!left.value.equals(right.value)) {
4747+ diff.leafUpdate(left.key, left.value, right.value);
4848+ }
4949+ await leftWalker.advance();
5050+ await rightWalker.advance();
5151+ } else if (left.key < right.key) {
5252+ diff.leafDelete(left.key, left.value);
5353+ await leftWalker.advance();
5454+ } else {
5555+ diff.leafAdd(right.key, right.value);
5656+ await rightWalker.advance();
5757+ }
5858+ continue;
5959+ }
6060+6161+ // next, ensure that we're on the same layer
6262+ // if one walker is at a higher layer than the other, we need to do one of two things
6363+ // if the higher walker is pointed at a tree, step into that tree to try to catch up with the lower
6464+ // if the higher walker is pointed at a leaf, then advance the lower walker to try to catch up the higher
6565+ if (leftWalker.layer() > rightWalker.layer()) {
6666+ if (left.isLeaf()) {
6767+ await diff.nodeAdd(right);
6868+ await rightWalker.advance();
6969+ } else {
7070+ await diff.nodeDelete(left);
7171+ await leftWalker.stepInto();
7272+ }
7373+ continue;
7474+ } else if (leftWalker.layer() < rightWalker.layer()) {
7575+ if (right.isLeaf()) {
7676+ await diff.nodeDelete(left);
7777+ await leftWalker.advance();
7878+ } else {
7979+ await diff.nodeAdd(right);
8080+ await rightWalker.stepInto();
8181+ }
8282+ continue;
8383+ }
8484+8585+ // if we're on the same level, and both pointers are trees, do a comparison
8686+ // if they're the same, step over. if they're different, step in to find the subdiff
8787+ if (left.isTree() && right.isTree()) {
8888+ if (left.pointer.equals(right.pointer)) {
8989+ await leftWalker.stepOver();
9090+ await rightWalker.stepOver();
9191+ } else {
9292+ await diff.nodeAdd(right);
9393+ await diff.nodeDelete(left);
9494+ await leftWalker.stepInto();
9595+ await rightWalker.stepInto();
9696+ }
9797+ continue;
9898+ }
9999+100100+ // finally, if one pointer is a tree and the other is a leaf, simply step into the tree
101101+ if (left.isLeaf() && right.isTree()) {
102102+ await diff.nodeAdd(right);
103103+ await rightWalker.stepInto();
104104+ continue;
105105+ } else if (left.isTree() && right.isLeaf()) {
106106+ await diff.nodeDelete(left);
107107+ await leftWalker.stepInto();
108108+ continue;
109109+ }
110110+111111+ throw new Error("Unidentifiable case in diff walk");
112112+ }
113113+ return diff;
114114+};
+4
repo/mst/index.ts
···11+export * from "./mst.ts";
22+export * from "./diff.ts";
33+export * from "./walker.ts";
44+export * as mstUtil from "./util.ts";
+892
repo/mst/mst.ts
···11+import type { CID } from "multiformats";
22+import { z } from "zod";
33+import { cidForCbor, dataToCborBlock, schema as common } from "@atp/common";
44+import { BlockMap } from "../block-map.ts";
55+import { CidSet } from "../cid-set.ts";
66+import { MissingBlockError, MissingBlocksError } from "../error.ts";
77+import * as parse from "../parse.ts";
88+import type { ReadableBlockstore } from "../storage/index.ts";
99+import type { CarBlock } from "../types.ts";
1010+import * as util from "./util.ts";
1111+1212+/**
1313+ * This is an implementation of a Merkle Search Tree (MST)
1414+ * The data structure is described here: https://hal.inria.fr/hal-02303490/document
1515+ * The MST is an ordered, insert-order-independent, deterministic tree.
1616+ * Keys are laid out in alphabetic order.
1717+ * The key insight of an MST is that each key is hashed and starting 0s are counted
1818+ * to determine which layer it falls on (5 zeros for ~32 fanout).
1919+ * This is a merkle tree, so each subtree is referred to by it's hash (CID).
2020+ * When a leaf is changed, ever tree on the path to that leaf is changed as well,
2121+ * thereby updating the root hash.
2222+ *
2323+ * For atproto, we use SHA-256 as the key hashing algorithm, and ~4 fanout
2424+ * (2-bits of zero per layer).
2525+ */
2626+2727+/**
2828+ * A couple notes on CBOR encoding:
2929+ *
3030+ * There are never two neighboring subtrees.
3131+ * Therefore, we can represent a node as an array of
3232+ * leaves & pointers to their right neighbor (possibly null),
3333+ * along with a pointer to the left-most subtree (also possibly null).
3434+ *
3535+ * Most keys in a subtree will have overlap.
3636+ * We do compression on prefixes by describing keys as:
3737+ * - the length of the prefix that it shares in common with the preceding key
3838+ * - the rest of the string
3939+ *
4040+ * For example:
4141+ * If the first leaf in a tree is `bsky/posts/abcdefg` and the second is `bsky/posts/abcdehi`
4242+ * Then the first will be described as `prefix: 0, key: 'bsky/posts/abcdefg'`,
4343+ * and the second will be described as `prefix: 16, key: 'hi'.`
4444+ */
4545+const subTreePointer = z.nullable(common.cid);
4646+const treeEntry = z.object({
4747+ p: z.number(), // prefix count of ascii chars that this key shares with the prev key
4848+ k: common.bytes, // the rest of the key outside the shared prefix
4949+ v: common.cid, // value
5050+ t: subTreePointer, // next subtree (to the right of leaf)
5151+});
5252+const nodeData = z.object({
5353+ l: subTreePointer, // left-most subtree
5454+ e: z.array(treeEntry), //entries
5555+});
5656+export type NodeData = z.infer<typeof nodeData>;
5757+5858+export const nodeDataDef = {
5959+ name: "mst node",
6060+ schema: nodeData,
6161+};
6262+6363+export type NodeEntry = MST | Leaf;
6464+6565+export type MstOpts = {
6666+ layer: number;
6767+};
6868+6969+export class MST {
7070+ storage: ReadableBlockstore;
7171+ entries: NodeEntry[] | null;
7272+ layer: number | null;
7373+ pointer: CID;
7474+ outdatedPointer = false;
7575+7676+ constructor(
7777+ storage: ReadableBlockstore,
7878+ pointer: CID,
7979+ entries: NodeEntry[] | null,
8080+ layer: number | null,
8181+ ) {
8282+ this.storage = storage;
8383+ this.entries = entries;
8484+ this.layer = layer;
8585+ this.pointer = pointer;
8686+ }
8787+8888+ static async create(
8989+ storage: ReadableBlockstore,
9090+ entries: NodeEntry[] = [],
9191+ opts?: Partial<MstOpts>,
9292+ ): Promise<MST> {
9393+ const pointer = await util.cidForEntries(entries);
9494+ const { layer = null } = opts || {};
9595+ return new MST(storage, pointer, entries, layer);
9696+ }
9797+9898+ static async fromData(
9999+ storage: ReadableBlockstore,
100100+ data: NodeData,
101101+ opts?: Partial<MstOpts>,
102102+ ): Promise<MST> {
103103+ const { layer = null } = opts || {};
104104+ const entries = util.deserializeNodeData(storage, data, opts);
105105+ const pointer = await cidForCbor(data);
106106+ return new MST(storage, pointer, entries, layer);
107107+ }
108108+109109+ // this is really a *lazy* load, doesn't actually touch storage
110110+ static load(
111111+ storage: ReadableBlockstore,
112112+ cid: CID,
113113+ opts?: Partial<MstOpts>,
114114+ ): MST {
115115+ const { layer = null } = opts || {};
116116+ return new MST(storage, cid, null, layer);
117117+ }
118118+119119+ // Immutability
120120+ // -------------------
121121+122122+ // We never mutate an MST, we just return a new MST with updated values
123123+ newTree(entries: NodeEntry[]): MST {
124124+ const mst = new MST(this.storage, this.pointer, entries, this.layer);
125125+ mst.outdatedPointer = true;
126126+ return mst;
127127+ }
128128+129129+ // Getters (lazy load)
130130+ // -------------------
131131+132132+ // We don't want to load entries of every subtree, just the ones we need
133133+ getEntries(): NodeEntry[] {
134134+ if (this.entries) return [...this.entries];
135135+ if (this.pointer) {
136136+ const data = this.storage.readObj(this.pointer, nodeDataDef);
137137+ const firstLeaf = data.e[0];
138138+ const layer = firstLeaf !== undefined
139139+ ? util.leadingZerosOnHash(firstLeaf.k as Uint8Array)
140140+ : undefined;
141141+ this.entries = util.deserializeNodeData(this.storage, data, {
142142+ layer,
143143+ });
144144+145145+ return this.entries;
146146+ }
147147+ throw new Error("No entries or CID provided");
148148+ }
149149+150150+ // We don't hash the node on every mutation for performance reasons
151151+ // Instead we keep track of whether the pointer is outdated and only (recursively) calculate when needed
152152+ async getPointer(): Promise<CID> {
153153+ if (!this.outdatedPointer) return this.pointer;
154154+ const { cid } = await this.serialize();
155155+ this.pointer = cid;
156156+ this.outdatedPointer = false;
157157+ return this.pointer;
158158+ }
159159+160160+ async serialize(): Promise<{ cid: CID; bytes: Uint8Array }> {
161161+ let entries = this.getEntries();
162162+ const outdated = entries.filter(
163163+ (e) => e.isTree() && e.outdatedPointer,
164164+ ) as MST[];
165165+ if (outdated.length > 0) {
166166+ await Promise.all(outdated.map((e) => e.getPointer()));
167167+ entries = this.getEntries();
168168+ }
169169+ const data = util.serializeNodeData(entries);
170170+ const block = await dataToCborBlock(data);
171171+ return {
172172+ cid: block.cid,
173173+ bytes: block.bytes,
174174+ };
175175+ }
176176+177177+ // In most cases, we get the layer of a node from a hint on creation
178178+ // In the case of the topmost node in the tree, we look for a key in the node & determine the layer
179179+ // In the case where we don't find one, we recurse down until we do.
180180+ // If we still can't find one, then we have an empty tree and the node is layer 0
181181+ async getLayer(): Promise<number> {
182182+ this.layer = await this.attemptGetLayer();
183183+ if (this.layer === null) this.layer = 0;
184184+ return this.layer;
185185+ }
186186+187187+ async attemptGetLayer(): Promise<number | null> {
188188+ if (this.layer !== null) return this.layer;
189189+ const entries = this.getEntries();
190190+ let layer = util.layerForEntries(entries);
191191+ if (layer === null) {
192192+ for (const entry of entries) {
193193+ if (entry.isTree()) {
194194+ const childLayer = await entry.attemptGetLayer();
195195+ if (childLayer !== null) {
196196+ layer = childLayer + 1;
197197+ break;
198198+ }
199199+ }
200200+ }
201201+ }
202202+ if (layer !== null) this.layer = layer;
203203+ return layer;
204204+ }
205205+206206+ // Core functionality
207207+ // -------------------
208208+209209+ // Return the necessary blocks to persist the MST to repo storage
210210+ async getUnstoredBlocks(): Promise<{ root: CID; blocks: BlockMap }> {
211211+ const blocks = new BlockMap();
212212+ const pointer = await this.getPointer();
213213+ const alreadyHas = this.storage.has(pointer);
214214+ if (alreadyHas) return { root: pointer, blocks };
215215+ const entries = this.getEntries();
216216+ const data = util.serializeNodeData(entries);
217217+ await blocks.add(data);
218218+ for (const entry of entries) {
219219+ if (entry.isTree()) {
220220+ const subtree = await entry.getUnstoredBlocks();
221221+ blocks.addMap(subtree.blocks);
222222+ }
223223+ }
224224+ return { root: pointer, blocks: blocks };
225225+ }
226226+227227+ // Adds a new leaf for the given key/value pair
228228+ // Throws if a leaf with that key already exists
229229+ async add(key: string, value: CID, knownZeros?: number): Promise<MST> {
230230+ util.ensureValidMstKey(key);
231231+ const keyZeros = knownZeros ?? (util.leadingZerosOnHash(key));
232232+ const layer = await this.getLayer();
233233+ const newLeaf = new Leaf(key, value);
234234+ if (keyZeros === layer) {
235235+ // it belongs in this layer
236236+ const index = this.findGtOrEqualLeafIndex(key);
237237+ const found = this.atIndex(index);
238238+ if (found?.isLeaf() && found.key === key) {
239239+ throw new Error(`There is already a value at key: ${key}`);
240240+ }
241241+ const prevNode = this.atIndex(index - 1);
242242+ if (!prevNode || prevNode.isLeaf()) {
243243+ // if entry before is a leaf, (or we're on far left) we can just splice in
244244+ return this.spliceIn(newLeaf, index);
245245+ } else {
246246+ // else we try to split the subtree around the key
247247+ const splitSubTree = await prevNode.splitAround(key);
248248+ return this.replaceWithSplit(
249249+ index - 1,
250250+ splitSubTree[0],
251251+ newLeaf,
252252+ splitSubTree[1],
253253+ );
254254+ }
255255+ } else if (keyZeros < layer) {
256256+ // it belongs on a lower layer
257257+ const index = this.findGtOrEqualLeafIndex(key);
258258+ const prevNode = this.atIndex(index - 1);
259259+ if (prevNode && prevNode.isTree()) {
260260+ // if entry before is a tree, we add it to that tree
261261+ const newSubtree = await prevNode.add(key, value, keyZeros);
262262+ return this.updateEntry(index - 1, newSubtree);
263263+ } else {
264264+ const subTree = await this.createChild();
265265+ const newSubTree = await subTree.add(key, value, keyZeros);
266266+ return this.spliceIn(newSubTree, index);
267267+ }
268268+ } else {
269269+ // it belongs on a higher layer & we must push the rest of the tree down
270270+ const split = await this.splitAround(key);
271271+ // if the newly added key has >=2 more leading zeros than the current highest layer
272272+ // then we need to add in structural nodes in between as well
273273+ let left: MST | null = split[0];
274274+ let right: MST | null = split[1];
275275+ const layer = await this.getLayer();
276276+ const extraLayersToAdd = keyZeros - layer;
277277+ // intentionally starting at 1, since first layer is taken care of by split
278278+ for (let i = 1; i < extraLayersToAdd; i++) {
279279+ if (left !== null) {
280280+ left = await left.createParent();
281281+ }
282282+ if (right !== null) {
283283+ right = await right.createParent();
284284+ }
285285+ }
286286+ const updated: NodeEntry[] = [];
287287+ if (left) updated.push(left);
288288+ updated.push(new Leaf(key, value));
289289+ if (right) updated.push(right);
290290+ const newRoot = await MST.create(this.storage, updated, {
291291+ layer: keyZeros,
292292+ });
293293+ newRoot.outdatedPointer = true;
294294+ return newRoot;
295295+ }
296296+ }
297297+298298+ // Gets the value at the given key
299299+ get(key: string): CID | null {
300300+ const index = this.findGtOrEqualLeafIndex(key);
301301+ const found = this.atIndex(index);
302302+ if (found && found.isLeaf() && found.key === key) {
303303+ return found.value;
304304+ }
305305+ const prev = this.atIndex(index - 1);
306306+ if (prev && prev.isTree()) {
307307+ return prev.get(key);
308308+ }
309309+ return null;
310310+ }
311311+312312+ // Edits the value at the given key
313313+ // Throws if the given key does not exist
314314+ async update(key: string, value: CID): Promise<MST> {
315315+ util.ensureValidMstKey(key);
316316+ const index = this.findGtOrEqualLeafIndex(key);
317317+ const found = this.atIndex(index);
318318+ if (found && found.isLeaf() && found.key === key) {
319319+ return this.updateEntry(index, new Leaf(key, value));
320320+ }
321321+ const prev = this.atIndex(index - 1);
322322+ if (prev && prev.isTree()) {
323323+ const updatedTree = await prev.update(key, value);
324324+ return this.updateEntry(index - 1, updatedTree);
325325+ }
326326+ throw new Error(`Could not find a record with key: ${key}`);
327327+ }
328328+329329+ // Deletes the value at the given key
330330+ async delete(key: string): Promise<MST> {
331331+ const altered = await this.deleteRecurse(key);
332332+ return altered.trimTop();
333333+ }
334334+335335+ async deleteRecurse(key: string): Promise<MST> {
336336+ const index = this.findGtOrEqualLeafIndex(key);
337337+ const found = this.atIndex(index);
338338+ // if found, remove it on this level
339339+ if (found?.isLeaf() && found.key === key) {
340340+ const prev = this.atIndex(index - 1);
341341+ const next = this.atIndex(index + 1);
342342+ if (prev?.isTree() && next?.isTree()) {
343343+ const merged = await prev.appendMerge(next);
344344+ return this.newTree([
345345+ ...(this.slice(0, index - 1)),
346346+ merged,
347347+ ...(this.slice(index + 2)),
348348+ ]);
349349+ } else {
350350+ return this.removeEntry(index);
351351+ }
352352+ }
353353+ // else recurse down to find it
354354+ const prev = this.atIndex(index - 1);
355355+ if (prev?.isTree()) {
356356+ const subtree = await prev.deleteRecurse(key);
357357+ const subTreeEntries = subtree.getEntries();
358358+ if (subTreeEntries.length === 0) {
359359+ return this.removeEntry(index - 1);
360360+ } else {
361361+ return this.updateEntry(index - 1, subtree);
362362+ }
363363+ } else {
364364+ throw new Error(`Could not find a record with key: ${key}`);
365365+ }
366366+ }
367367+368368+ // Simple Operations
369369+ // -------------------
370370+371371+ // update entry in place
372372+ updateEntry(index: number, entry: NodeEntry): MST {
373373+ const update = [
374374+ ...(this.slice(0, index)),
375375+ entry,
376376+ ...(this.slice(index + 1)),
377377+ ];
378378+ return this.newTree(update);
379379+ }
380380+381381+ // remove entry at index
382382+ removeEntry(index: number): MST {
383383+ const updated = [
384384+ ...(this.slice(0, index)),
385385+ ...(this.slice(index + 1)),
386386+ ];
387387+ return this.newTree(updated);
388388+ }
389389+390390+ // append entry to end of the node
391391+ append(entry: NodeEntry): MST {
392392+ const entries = this.getEntries();
393393+ return this.newTree([...entries, entry]);
394394+ }
395395+396396+ // prepend entry to start of the node
397397+ prepend(entry: NodeEntry): MST {
398398+ const entries = this.getEntries();
399399+ return this.newTree([entry, ...entries]);
400400+ }
401401+402402+ // returns entry at index
403403+ atIndex(index: number): NodeEntry | null {
404404+ const entries = this.getEntries();
405405+ return entries[index] ?? null;
406406+ }
407407+408408+ // returns a slice of the node (like array.slice)
409409+ slice(
410410+ start?: number | undefined,
411411+ end?: number | undefined,
412412+ ): NodeEntry[] {
413413+ const entries = this.getEntries();
414414+ return entries.slice(start, end);
415415+ }
416416+417417+ // inserts entry at index
418418+ spliceIn(entry: NodeEntry, index: number): MST {
419419+ const update = [
420420+ ...this.slice(0, index),
421421+ entry,
422422+ ...this.slice(index),
423423+ ];
424424+ return this.newTree(update);
425425+ }
426426+427427+ // replaces an entry with [ Maybe(tree), Leaf, Maybe(tree) ]
428428+ replaceWithSplit(
429429+ index: number,
430430+ left: MST | null,
431431+ leaf: Leaf,
432432+ right: MST | null,
433433+ ): MST {
434434+ const update = this.slice(0, index);
435435+ if (left) update.push(left);
436436+ update.push(leaf);
437437+ if (right) update.push(right);
438438+ update.push(...this.slice(index + 1));
439439+ return this.newTree(update);
440440+ }
441441+442442+ // if the topmost node in the tree only points to another tree, trim the top and return the subtree
443443+ trimTop(): MST {
444444+ let entries: NodeEntry[];
445445+ try {
446446+ entries = this.getEntries();
447447+ } catch (err) {
448448+ if (err instanceof MissingBlockError) {
449449+ return this;
450450+ } else {
451451+ throw err;
452452+ }
453453+ }
454454+ if (entries.length === 1 && entries[0].isTree()) {
455455+ return entries[0].trimTop();
456456+ } else {
457457+ return this;
458458+ }
459459+ }
460460+461461+ // Subtree & Splits
462462+ // -------------------
463463+464464+ // Recursively splits a sub tree around a given key
465465+ async splitAround(key: string): Promise<[MST | null, MST | null]> {
466466+ const index = this.findGtOrEqualLeafIndex(key);
467467+ // split tree around key
468468+ const leftData = this.slice(0, index);
469469+ const rightData = this.slice(index);
470470+ let left = this.newTree(leftData);
471471+ let right = this.newTree(rightData);
472472+473473+ // if the far right of the left side is a subtree,
474474+ // we need to split it on the key as well
475475+ const lastInLeft = leftData[leftData.length - 1];
476476+ if (lastInLeft?.isTree()) {
477477+ left = left.removeEntry(leftData.length - 1);
478478+ const split = await lastInLeft.splitAround(key);
479479+ if (split[0]) {
480480+ left = left.append(split[0]);
481481+ }
482482+ if (split[1]) {
483483+ right = right.prepend(split[1]);
484484+ }
485485+ }
486486+487487+ return [
488488+ (left.getEntries()).length > 0 ? left : null,
489489+ (right.getEntries()).length > 0 ? right : null,
490490+ ];
491491+ }
492492+493493+ // The simple merge case where every key in the right tree is greater than every key in the left tree
494494+ // (used primarily for deletes)
495495+ async appendMerge(toMerge: MST): Promise<MST> {
496496+ if ((await this.getLayer()) !== (await toMerge.getLayer())) {
497497+ throw new Error(
498498+ "Trying to merge two nodes from different layers of the MST",
499499+ );
500500+ }
501501+ const thisEntries = this.getEntries();
502502+ const toMergeEntries = toMerge.getEntries();
503503+ const lastInLeft = thisEntries[thisEntries.length - 1];
504504+ const firstInRight = toMergeEntries[0];
505505+ if (lastInLeft?.isTree() && firstInRight?.isTree()) {
506506+ const merged = await lastInLeft.appendMerge(firstInRight);
507507+ return this.newTree([
508508+ ...thisEntries.slice(0, thisEntries.length - 1),
509509+ merged,
510510+ ...toMergeEntries.slice(1),
511511+ ]);
512512+ } else {
513513+ return this.newTree([...thisEntries, ...toMergeEntries]);
514514+ }
515515+ }
516516+517517+ // Create relatives
518518+ // -------------------
519519+520520+ async createChild(): Promise<MST> {
521521+ const layer = await this.getLayer();
522522+ return MST.create(this.storage, [], {
523523+ layer: layer - 1,
524524+ });
525525+ }
526526+527527+ async createParent(): Promise<MST> {
528528+ const layer = await this.getLayer();
529529+ const parent = await MST.create(this.storage, [this], {
530530+ layer: layer + 1,
531531+ });
532532+ parent.outdatedPointer = true;
533533+ return parent;
534534+ }
535535+536536+ // Finding insertion points
537537+ // -------------------
538538+539539+ // finds index of first leaf node that is greater than or equal to the value
540540+ findGtOrEqualLeafIndex(key: string): number {
541541+ const entries = this.getEntries();
542542+ const maybeIndex = entries.findIndex(
543543+ (entry) => entry.isLeaf() && entry.key >= key,
544544+ );
545545+ // if we can't find, we're on the end
546546+ return maybeIndex >= 0 ? maybeIndex : entries.length;
547547+ }
548548+549549+ // List operations (partial tree traversal)
550550+ // -------------------
551551+552552+ // @TODO write tests for these
553553+554554+ // Walk tree starting at key
555555+ async *walkFrom(key: string): AsyncIterable<NodeEntry> {
556556+ yield this;
557557+ const index = this.findGtOrEqualLeafIndex(key);
558558+ const entries = this.getEntries();
559559+ const found = entries[index];
560560+ if (found && found.isLeaf() && found.key === key) {
561561+ yield found;
562562+ } else {
563563+ const prev = entries[index - 1];
564564+ if (prev) {
565565+ if (prev.isLeaf() && prev.key === key) {
566566+ yield prev;
567567+ } else if (prev.isTree()) {
568568+ yield* prev.walkFrom(key);
569569+ }
570570+ }
571571+ }
572572+573573+ for (let i = index; i < entries.length; i++) {
574574+ const entry = entries[i];
575575+ if (entry.isLeaf()) {
576576+ yield entry;
577577+ } else {
578578+ yield* entry.walkFrom(key);
579579+ }
580580+ }
581581+ }
582582+583583+ async *walkLeavesFrom(key: string): AsyncIterable<Leaf> {
584584+ for await (const node of this.walkFrom(key)) {
585585+ if (node.isLeaf()) {
586586+ yield node;
587587+ }
588588+ }
589589+ }
590590+591591+ async list(
592592+ count = Number.MAX_SAFE_INTEGER,
593593+ after?: string,
594594+ before?: string,
595595+ ): Promise<Leaf[]> {
596596+ const vals: Leaf[] = [];
597597+ for await (const leaf of this.walkLeavesFrom(after || "")) {
598598+ if (leaf.key === after) continue;
599599+ if (vals.length >= count) break;
600600+ if (before && leaf.key >= before) break;
601601+ vals.push(leaf);
602602+ }
603603+ return vals;
604604+ }
605605+606606+ async listWithPrefix(
607607+ prefix: string,
608608+ count = Number.MAX_SAFE_INTEGER,
609609+ ): Promise<Leaf[]> {
610610+ const vals: Leaf[] = [];
611611+ for await (const leaf of this.walkLeavesFrom(prefix)) {
612612+ if (vals.length >= count || !leaf.key.startsWith(prefix)) break;
613613+ vals.push(leaf);
614614+ }
615615+ return vals;
616616+ }
617617+618618+ // Full tree traversal
619619+ // -------------------
620620+621621+ // Walk full tree & emit nodes, consumer can bail at any point by returning false
622622+ async *walk(): AsyncIterable<NodeEntry> {
623623+ yield this;
624624+ const entries = this.getEntries();
625625+ for (const entry of entries) {
626626+ if (entry.isTree()) {
627627+ for await (const e of entry.walk()) {
628628+ yield e;
629629+ }
630630+ } else {
631631+ yield entry;
632632+ }
633633+ }
634634+ }
635635+636636+ // Walk full tree & emit nodes, consumer can bail at any point by returning false
637637+ async paths(): Promise<NodeEntry[][]> {
638638+ const entries = this.getEntries();
639639+ let paths: NodeEntry[][] = [];
640640+ for (const entry of entries) {
641641+ if (entry.isLeaf()) {
642642+ paths.push([entry]);
643643+ }
644644+ if (entry.isTree()) {
645645+ const subPaths = await entry.paths();
646646+ paths = [...paths, ...subPaths.map((p) => [entry, ...p])];
647647+ }
648648+ }
649649+ return paths;
650650+ }
651651+652652+ // Walks tree & returns all nodes
653653+ async allNodes(): Promise<NodeEntry[]> {
654654+ const nodes: NodeEntry[] = [];
655655+ for await (const entry of this.walk()) {
656656+ nodes.push(entry);
657657+ }
658658+ return nodes;
659659+ }
660660+661661+ // Walks tree & returns all cids
662662+ async allCids(): Promise<CidSet> {
663663+ const cids = new CidSet();
664664+ const entries = this.getEntries();
665665+ for (const entry of entries) {
666666+ if (entry.isLeaf()) {
667667+ cids.add(entry.value);
668668+ } else {
669669+ const subtreeCids = await entry.allCids();
670670+ cids.addSet(subtreeCids);
671671+ }
672672+ }
673673+ cids.add(await this.getPointer());
674674+ return cids;
675675+ }
676676+677677+ // Walks tree & returns all leaves
678678+ async leaves() {
679679+ const leaves: Leaf[] = [];
680680+ for await (const entry of this.walk()) {
681681+ if (entry.isLeaf()) leaves.push(entry);
682682+ }
683683+ return leaves;
684684+ }
685685+686686+ // Returns total leaf count
687687+ async leafCount(): Promise<number> {
688688+ const leaves = await this.leaves();
689689+ return leaves.length;
690690+ }
691691+692692+ // Reachable tree traversal
693693+ // -------------------
694694+695695+ // Walk reachable branches of tree & emit nodes, consumer can bail at any point by returning false
696696+ async *walkReachable(): AsyncIterable<NodeEntry> {
697697+ yield this;
698698+ const entries = this.getEntries();
699699+ for (const entry of entries) {
700700+ if (entry.isTree()) {
701701+ try {
702702+ for await (const e of entry.walkReachable()) {
703703+ yield e;
704704+ }
705705+ } catch (err) {
706706+ if (err instanceof MissingBlockError) {
707707+ continue;
708708+ } else {
709709+ throw err;
710710+ }
711711+ }
712712+ } else {
713713+ yield entry;
714714+ }
715715+ }
716716+ }
717717+718718+ async reachableLeaves(): Promise<Leaf[]> {
719719+ const leaves: Leaf[] = [];
720720+ for await (const entry of this.walkReachable()) {
721721+ if (entry.isLeaf()) leaves.push(entry);
722722+ }
723723+ return leaves;
724724+ }
725725+726726+ // Sync Protocol
727727+728728+ async *carBlockStream(): AsyncIterable<CarBlock> {
729729+ const leaves = new CidSet();
730730+ let toFetch = new CidSet();
731731+ toFetch.add(await this.getPointer());
732732+ while (toFetch.size() > 0) {
733733+ const nextLayer = new CidSet();
734734+ const fetched = this.storage.getBlocks(toFetch.toList());
735735+ if (fetched.missing.length > 0) {
736736+ throw new MissingBlocksError("mst node", fetched.missing);
737737+ }
738738+ for (const cid of toFetch.toList()) {
739739+ const found = parse.getAndParseByDef(
740740+ fetched.blocks,
741741+ cid,
742742+ nodeDataDef,
743743+ );
744744+ yield { cid, bytes: found.bytes };
745745+ const entries = util.deserializeNodeData(this.storage, found.obj);
746746+747747+ for (const entry of entries) {
748748+ if (entry.isLeaf()) {
749749+ leaves.add(entry.value);
750750+ } else {
751751+ nextLayer.add(await entry.getPointer());
752752+ }
753753+ }
754754+ }
755755+ toFetch = nextLayer;
756756+ }
757757+ const leafData = this.storage.getBlocks(leaves.toList());
758758+ if (leafData.missing.length > 0) {
759759+ throw new MissingBlocksError("mst leaf", leafData.missing);
760760+ }
761761+762762+ for (const leaf of leafData.blocks.entries()) {
763763+ yield leaf;
764764+ }
765765+ }
766766+767767+ async cidsForPath(key: string): Promise<CID[]> {
768768+ const cids: CID[] = [await this.getPointer()];
769769+ const index = this.findGtOrEqualLeafIndex(key);
770770+ const found = this.atIndex(index);
771771+ if (found && found.isLeaf() && found.key === key) {
772772+ return [...cids, found.value];
773773+ }
774774+ const prev = this.atIndex(index - 1);
775775+ if (prev && prev.isTree()) {
776776+ return [...cids, ...(await prev.cidsForPath(key))];
777777+ }
778778+ return cids;
779779+ }
780780+781781+ // A covering proof is all MST nodes (leaves excluded) needed to prove the value of a given leaf
782782+ // and its siblings to its immediate right and left (if applicable)
783783+ // We simply find the immediately preceeding node and then walk from that node until we reach the
784784+ // first key that is greater than the requested key (the right sibling)
785785+ async getCoveringProof(key: string): Promise<BlockMap> {
786786+ const [self, left, right] = await Promise.all([
787787+ this.proofForKey(key),
788788+ this.proofForLeftSib(key),
789789+ this.proofForRightSib(key),
790790+ ]);
791791+ return self.addMap(left).addMap(right);
792792+ }
793793+794794+ async proofForKey(key: string): Promise<BlockMap> {
795795+ const index = this.findGtOrEqualLeafIndex(key);
796796+ const found = this.atIndex(index);
797797+ let blocks: BlockMap;
798798+ if (found && found.isLeaf() && found.key === key) {
799799+ blocks = new BlockMap();
800800+ } else {
801801+ const prev = this.atIndex(index - 1);
802802+ if (!prev || prev.isLeaf()) {
803803+ return new BlockMap();
804804+ } else {
805805+ blocks = await prev.proofForKey(key);
806806+ }
807807+ }
808808+ const serialized = await this.serialize();
809809+ return blocks.set(serialized.cid, serialized.bytes);
810810+ }
811811+812812+ async proofForLeftSib(key: string): Promise<BlockMap> {
813813+ const index = this.findGtOrEqualLeafIndex(key);
814814+ const prev = this.atIndex(index - 1);
815815+ let blocks: BlockMap;
816816+ if (!prev || prev.isLeaf()) {
817817+ blocks = new BlockMap();
818818+ } else {
819819+ blocks = await prev.proofForLeftSib(key);
820820+ }
821821+ const serialized = await this.serialize();
822822+ return blocks.set(serialized.cid, serialized.bytes);
823823+ }
824824+825825+ async proofForRightSib(key: string): Promise<BlockMap> {
826826+ const index = this.findGtOrEqualLeafIndex(key);
827827+ let found = this.atIndex(index);
828828+ if (!found) {
829829+ found = this.atIndex(index - 1);
830830+ }
831831+ let blocks: BlockMap;
832832+ if (!found) {
833833+ // shouldn't ever hit, null case
834834+ blocks = new BlockMap();
835835+ } else if (found.isTree()) {
836836+ blocks = await found.proofForRightSib(key);
837837+ // recurse down
838838+ } else {
839839+ const node = found.key === key
840840+ ? this.atIndex(index + 1)
841841+ : this.atIndex(index - 1);
842842+ if (!node || node.isLeaf()) {
843843+ blocks = new BlockMap();
844844+ } else {
845845+ blocks = await node.proofForRightSib(key);
846846+ }
847847+ }
848848+ const serialized = await this.serialize();
849849+ return blocks.set(serialized.cid, serialized.bytes);
850850+ }
851851+852852+ // Matching Leaf interface
853853+ // -------------------
854854+855855+ isTree(): this is MST {
856856+ return true;
857857+ }
858858+859859+ isLeaf(): this is Leaf {
860860+ return false;
861861+ }
862862+863863+ async equals(other: NodeEntry): Promise<boolean> {
864864+ if (other.isLeaf()) return false;
865865+ const thisPointer = await this.getPointer();
866866+ const otherPointer = await other.getPointer();
867867+ return thisPointer.equals(otherPointer);
868868+ }
869869+}
870870+871871+export class Leaf {
872872+ constructor(
873873+ public key: string,
874874+ public value: CID,
875875+ ) {}
876876+877877+ isTree(): this is MST {
878878+ return false;
879879+ }
880880+881881+ isLeaf(): this is Leaf {
882882+ return true;
883883+ }
884884+885885+ equals(entry: NodeEntry): boolean {
886886+ if (entry.isLeaf()) {
887887+ return this.key === entry.key && this.value.equals(entry.value);
888888+ } else {
889889+ return false;
890890+ }
891891+ }
892892+}
···11+export * from "./readable-blockstore.ts";
22+export * from "./memory-blockstore.ts";
33+export * from "./sync-storage.ts";
44+export * from "./types.ts";