redoing a bunch of stuff, all in memory

This commit is contained in:
dholms 2022-07-27 22:26:13 -05:00
parent 197b75f116
commit 82e9334294
3 changed files with 597 additions and 531 deletions
packages/common
src/repo
tests

@ -1,434 +0,0 @@
import { CID } from 'multiformats'
import * as uint8arrays from 'uint8arrays'
import IpldStore from '../blockstore/ipld-store'
import { sha256 } from '@adxp/crypto'
import z from 'zod'
import { schema } from '../common/types'
import * as check from '../common/check'
const leafPointer = z.tuple([z.string(), schema.cid])
const treePointer = schema.cid
const treeEntry = z.union([leafPointer, treePointer])
const nodeSchema = z.array(treeEntry)
type LeafPointer = z.infer<typeof leafPointer>
type TreePointer = z.infer<typeof treePointer>
type TreeEntry = z.infer<typeof treeEntry>
type Node = z.infer<typeof nodeSchema>
export const leadingZerosOnHash = async (key: string): Promise<number> => {
const hash = await sha256(key)
const b32 = uint8arrays.toString(hash, 'base32')
let count = 0
for (const char of b32) {
if (char === 'a') {
// 'a' is 0 in b32
count++
} else {
break
}
}
return count
}
const spliceIn = <T>(array: T[], item: T, index: number): T[] => {
return [...array.slice(0, index), item, ...array.slice(index)]
}
export class MST {
blockstore: IpldStore
cid: CID
node: Node
zeros: number
constructor(blockstore: IpldStore, cid: CID, node: Node, zeros: number) {
this.blockstore = blockstore
this.cid = cid
this.node = node
this.zeros = zeros
}
static async create(blockstore: IpldStore, zeros = 0): Promise<MST> {
return MST.fromData(blockstore, [], zeros)
}
static async fromData(
blockstore: IpldStore,
node: Node,
zeros: number,
): Promise<MST> {
const cid = await blockstore.put(node as any)
return new MST(blockstore, cid, node, zeros)
}
static async load(
blockstore: IpldStore,
cid: CID,
zeros?: number,
): Promise<MST> {
const node = await blockstore.get(cid, nodeSchema)
if (zeros === undefined) {
const firstLeaf = node.find((entry) => check.is(entry, leafPointer))
if (!firstLeaf) {
throw new Error('not a valid mst node: no leaves')
}
zeros = await leadingZerosOnHash(firstLeaf[0])
}
return new MST(blockstore, cid, node, zeros)
}
async put(): Promise<CID> {
this.cid = await this.blockstore.put(this.node as any) // @TODO no any
return this.cid
}
async add(key: string, value: CID): Promise<CID> {
const keyZeros = await leadingZerosOnHash(key)
if (keyZeros === this.zeros) {
// it belongs in this layer
const index = this.findGtOrEqualLeafIndex(key)
const found = this.node[index]
if (found && found[0] === key) {
throw new Error(`There is already a value at key: ${key}`)
}
const prevNode = this.node[index - 1]
if (!prevNode || check.is(prevNode, leafPointer)) {
// if entry before is a leaf, (or we're on far left) we can just splice in
this.node = spliceIn(this.node, [key, value], index)
return this.put()
} else {
// else we need to investigate the subtree
const subTree = await MST.load(
this.blockstore,
prevNode,
this.zeros - 1,
)
// we try to split the subtree around the key
const splitSubTree = await subTree.splitAround(key)
const newNode = this.node.slice(0, index - 1)
if (splitSubTree[0]) newNode.push(splitSubTree[0])
newNode.push([key, value])
if (splitSubTree[1]) newNode.push(splitSubTree[1])
newNode.push(...this.node.slice(index))
this.node = newNode
return this.put()
}
} else if (keyZeros < this.zeros) {
// it belongs on a lower layer
const index = this.findGtOrEqualLeafIndex(key)
const prevNode = this.node[index - 1]
if (check.is(prevNode, treePointer)) {
// if entry before is a tree, we add it to that tree
const subTree = await MST.load(
this.blockstore,
prevNode,
this.zeros - 1,
)
const newSubTreeCid = await subTree.add(key, value)
this.node[index - 1] = newSubTreeCid
return this.put()
} else {
// else we need to create the subtree for it to go in
const subTree = await MST.create(this.blockstore, this.zeros - 1)
const newSubTreeCid = await subTree.add(key, value)
this.node = spliceIn(this.node, newSubTreeCid, index)
return this.put()
}
} else {
// it belongs on a higher layer & we must push the rest of the tree down
let split = await this.splitAround(key)
// if the newly added key has >=2 more leading zeros than the current highest layer
// then we need to add in structural nodes in between as well
let left: CID | null = split[0]
let right: CID | null = split[1]
const extraLayersToAdd = keyZeros - this.zeros
// intentionally starting at 1, since first layer is taken care of by split
for (let i = 1; i < extraLayersToAdd; i++) {
if (left !== null) {
const leftNode = await MST.fromData(
this.blockstore,
[left],
this.zeros + i,
)
left = leftNode.cid
}
if (right !== null) {
const rightNode = await MST.fromData(
this.blockstore,
[right],
this.zeros + i,
)
right = rightNode.cid
}
}
let newNode: Node = []
if (left) newNode.push(left)
newNode.push([key, value])
if (right) newNode.push(right)
this.node = newNode
this.zeros = keyZeros
return this.put()
}
}
// finds first leaf node that is greater than or equal to the value
findGtOrEqualLeafIndex(key: string): number {
const maybeIndex = this.node.findIndex(
(entry) => check.is(entry, leafPointer) && entry[0] >= key,
)
// if we can't find, we're on the end
return maybeIndex >= 0 ? maybeIndex : this.node.length
}
async splitAround(key: string): Promise<[CID | null, CID | null]> {
const index = this.findGtOrEqualLeafIndex(key)
const leftData = this.node.slice(0, index)
const rightData = this.node.slice(index)
if (leftData.length === 0) {
return [null, this.cid]
}
if (rightData.length === 0) {
return [this.cid, null]
}
const left = await MST.fromData(this.blockstore, leftData, this.zeros)
const right = await MST.fromData(this.blockstore, rightData, this.zeros)
const prev = leftData[leftData.length - 1]
if (check.is(prev, treePointer)) {
const prevSubtree = await MST.load(this.blockstore, prev, this.zeros - 1)
const prevSplit = await prevSubtree.splitAround(key)
if (prevSplit[0]) {
await left.append(prev)
}
if (prevSplit[1]) {
await right.prepend(prev)
}
}
return [left.cid, right.cid]
}
async append(entry: TreeEntry): Promise<CID> {
this.node = [...this.node, entry]
return this.put()
}
async prepend(entry: TreeEntry): Promise<CID> {
this.node = [entry, ...this.node]
return this.put()
}
async get(key: string): Promise<CID | null> {
const index = this.findGtOrEqualLeafIndex(key)
const found = this.node[index]
if (found && check.is(found, leafPointer) && found[0] === key) {
return found[1]
}
const prev = this.node[index - 1]
if (check.is(prev, treePointer)) {
const subTree = await MST.load(this.blockstore, prev, this.zeros - 1)
return subTree.get(key)
}
return null
}
async edit(key: string, value: CID): Promise<CID> {
const index = this.findGtOrEqualLeafIndex(key)
const found = this.node[index]
if (found && check.is(found, leafPointer) && found[0] === key) {
this.node[index][1] = value
return await this.put()
}
const prev = this.node[index - 1]
if (check.is(prev, treePointer)) {
const subTree = await MST.load(this.blockstore, prev, this.zeros - 1)
const subTreeCid = await subTree.edit(key, value)
this.node[index - 1] = subTreeCid
return await this.put()
}
throw new Error(`Could not find a record with key: ${key}`)
}
// async delete(key: string): Promise<void> {}
layerHasEntry(entry: TreeEntry): boolean {
let found: TreeEntry | undefined
if (check.is(entry, leafPointer)) {
found = this.node.find((e) => {
return (
check.is(e, leafPointer) && entry[0] === e[0] && entry[1].equals(e[1])
)
})
} else {
found = this.node.find((e) => {
return check.is(e, treePointer) && entry.equals(e)
})
}
return found !== undefined
}
async loadChild(cid: CID): Promise<MST> {
return MST.load(this.blockstore, cid, this.zeros - 1)
}
async mergeIn(toMerge: MST): Promise<CID> {
let newNode: Node = []
let thisI = 0,
toMergeI = 0
while (thisI < this.node.length && toMergeI < toMerge.node.length) {
const thisHead = this.node[thisI]
const toMergeHead = toMerge.node[toMergeI]
if (!thisHead) {
newNode.push(toMergeHead)
toMergeI++
} else if (!toMergeHead) {
newNode.push(thisHead)
thisI++
} else if (
check.is(thisHead, leafPointer) &&
check.is(toMergeHead, leafPointer)
) {
if (thisHead[0] === toMergeHead[0]) {
// on same, toMerge wins
newNode.push(toMergeHead)
thisI++
toMergeI++
} else if (thisHead[0] < toMergeHead[0]) {
newNode.push(thisHead)
thisI++
} else {
newNode.push(toMergeHead)
toMergeI++
}
} else if (
check.is(thisHead, treePointer) &&
check.is(toMergeHead, leafPointer)
) {
const toSplit = await this.loadChild(thisHead)
const split = await toSplit.splitAround(toMergeHead[0])
if (split[0] !== null) {
const prev = newNode[newNode.length - 1]
if (check.is(prev, treePointer)) {
const toMerge = await this.loadChild(split[0])
const toMergeIn = await this.loadChild(prev)
await toMerge.mergeIn(toMergeIn)
newNode.push(toMerge.cid)
} else {
newNode.push(split[0])
}
}
newNode.push(toMergeHead)
if (split[1] !== null) newNode.push(split[1])
thisI++
toMergeI++
} else if (
check.is(thisHead, leafPointer) &&
check.is(toMergeHead, treePointer)
) {
const toSplit = await this.loadChild(toMergeHead)
const split = await toSplit.splitAround(thisHead[0])
if (split[0] !== null) {
const prev = newNode[newNode.length - 1]
if (check.is(prev, treePointer)) {
const toMerge = await this.loadChild(prev)
const toMergeIn = await this.loadChild(split[0])
await toMerge.mergeIn(toMergeIn)
newNode.push(toMerge.cid)
} else {
newNode.push(split[0])
}
}
newNode.push(toMergeHead)
if (split[1] !== null) newNode.push(split[1])
thisI++
toMergeI++
} else if (
check.is(thisHead, treePointer) &&
check.is(toMergeHead, treePointer)
) {
const toMerge = await this.loadChild(thisHead)
const toMergeIn = await this.loadChild(toMergeHead)
await toMerge.mergeIn(toMergeIn)
newNode.push(toMerge.cid)
thisI++
toMergeI++
} else {
throw new Error('SHOULDNT ever reach this')
}
}
return this.put()
}
// toMerge wins on merge conflicts
async mergeInOld(toMerge: MST): Promise<CID> {
let lastIndex = 0
for (const entry of toMerge.node) {
if (check.is(entry, leafPointer)) {
lastIndex = this.findGtOrEqualLeafIndex(entry[0])
const found = this.node[lastIndex]
if (found && found[0] === entry[0]) {
// does nothing if same, overwrites if different
this.node[lastIndex] = entry
lastIndex++
} else {
this.node = spliceIn(this.node, entry, lastIndex)
lastIndex++
}
} else {
const nextEntryInNode = this.node[lastIndex]
if (!check.is(nextEntryInNode, treePointer)) {
// if the next is a leaf, we splice in before
this.node = spliceIn(this.node, entry, lastIndex)
lastIndex++
} else if (!nextEntryInNode.equals(entry)) {
// if it's a new subtree, then we have to merge the two children
const nodeChild = await MST.load(
this.blockstore,
nextEntryInNode,
this.zeros - 1,
)
const toMergeChild = await MST.load(
this.blockstore,
entry,
this.zeros - 1,
)
const mergedCid = await nodeChild.mergeIn(toMergeChild)
this.node[lastIndex] = mergedCid
lastIndex++
} else {
// if it's the same subtree, do nothing & increment index
lastIndex++
}
}
}
return this.put()
}
async walk(fn: (level: number, key: string | null) => void) {
for (const entry of this.node) {
if (check.is(entry, treePointer)) {
const subTree = await MST.load(this.blockstore, entry, this.zeros - 1)
fn(this.zeros, null)
await subTree.walk(fn)
} else {
fn(this.zeros, entry[0])
}
}
}
async structure() {
const tree: any = []
for (const entry of this.node) {
if (check.is(entry, treePointer)) {
const subTree = await MST.load(this.blockstore, entry, this.zeros - 1)
tree.push(['LINK', await subTree.structure()])
} else {
tree.push([entry[0], entry[1].toString()])
}
}
return tree
}
}
export default MST

@ -0,0 +1,518 @@
import * as Block from 'multiformats/block'
import { sha256 as blockHasher } from 'multiformats/hashes/sha2'
import * as blockCodec from '@ipld/dag-cbor'
import { CID } from 'multiformats'
import * as uint8arrays from 'uint8arrays'
import IpldStore from '../../blockstore/ipld-store'
import { sha256 } from '@adxp/crypto'
import z from 'zod'
import { schema } from '../../common/types'
import * as check from '../../common/check'
const leafPointer = z.tuple([z.string(), schema.cid])
const treePointer = schema.cid
const treeEntry = z.union([leafPointer, treePointer])
const nodeDataSchema = z.array(treeEntry)
// type LeafPointer = z.infer<typeof leafPointer>
// type TreePointer = z.infer<typeof treePointer>
// type TreeEntry = z.infer<typeof treeEntry>
type NodeData = z.infer<typeof nodeDataSchema>
export const leadingZerosOnHash = async (key: string): Promise<number> => {
const hash = await sha256(key)
const b32 = uint8arrays.toString(hash, 'base32')
let count = 0
for (const char of b32) {
if (char === 'a') {
// 'a' is 0 in b32
count++
} else {
break
}
}
return count
}
class MST {
blockstore: IpldStore
entries: NodeEntry[] | null
layer: number | null
pointer: CID
constructor(
blockstore: IpldStore,
pointer: CID,
entries: NodeEntry[] | null,
layer: number | null,
) {
this.blockstore = blockstore
this.entries = entries
this.layer = layer
this.pointer = pointer
}
static async getCid(entries: NodeEntry[]): Promise<CID> {
const data = entries.map((entry) => {
if (entry.isLeaf()) {
return [entry.key, entry.value]
} else {
return entry.pointer
}
})
const block = await Block.encode({
value: data as any,
codec: blockCodec,
hasher: blockHasher,
})
return block.cid
}
static async create(
blockstore: IpldStore,
entries: NodeEntry[] = [],
layer = 0,
): Promise<MST> {
const pointer = await MST.getCid(entries)
return new MST(blockstore, pointer, entries, layer)
}
static async fromData(
blockstore: IpldStore,
data: NodeData,
layer?: number,
): Promise<MST> {
const entries = data.map((entry) => {
if (check.is(entry, treePointer)) {
return MST.fromCid(blockstore, entry, layer ? layer - 1 : undefined)
} else {
return new Leaf(entry[0], entry[1])
}
})
const pointer = await MST.getCid(entries)
return new MST(blockstore, pointer, entries, layer ?? null)
}
static fromCid(blockstore: IpldStore, cid: CID, layer?: number): MST {
return new MST(blockstore, cid, null, layer ?? null)
}
async getEntries(): Promise<NodeEntry[]> {
if (this.entries) return this.entries
if (this.pointer) {
const data = await this.blockstore.get(this.pointer, nodeDataSchema)
this.entries = data.map((entry) => {
if (check.is(entry, treePointer)) {
// @TODO using this.layer instead of getLayer here??
return MST.fromCid(
this.blockstore,
entry,
this.layer ? this.layer - 1 : undefined,
)
} else {
return new Leaf(entry[0], entry[1])
}
})
return this.entries
}
throw new Error('No entries or CID provided')
}
async getLayer(): Promise<number> {
if (this.layer !== null) return this.layer
const entries = await this.getEntries()
const firstLeaf = entries.find((entry) => entry.isLeaf())
if (!firstLeaf) {
throw new Error('not a valid mst node: no leaves')
}
this.layer = await leadingZerosOnHash(firstLeaf[0])
return this.layer
}
async add(key: string, value: CID): Promise<MST> {
const keyZeros = await leadingZerosOnHash(key)
const layer = await this.getLayer()
const newLeaf = new Leaf(key, value)
if (keyZeros === layer) {
// it belongs in this layer
const index = await this.findGtOrEqualLeafIndex(key)
const found = await this.atIndex(index)
if (found && found.equals(newLeaf)) {
throw new Error(`There is already a value at key: ${key}`)
}
const prevNode = await this.atIndex(index - 1)
if (!prevNode || prevNode.isLeaf()) {
// if entry before is a leaf, (or we're on far left) we can just splice in
return this.spliceIn(newLeaf, index)
} else {
// else we try to split the subtree around the key
const splitSubTree = await prevNode.splitAround(key)
return this.replaceWithSplit(
index - 1,
splitSubTree[0],
newLeaf,
splitSubTree[1],
)
}
} else if (keyZeros < layer) {
// it belongs on a lower layer
const index = await this.findGtOrEqualLeafIndex(key)
const prevNode = await this.atIndex(index - 1)
if (prevNode && prevNode.isTree()) {
// if entry before is a tree, we add it to that tree
const newSubtree = await prevNode.add(key, value)
return this.updateEntry(index - 1, newSubtree)
} else {
const subTree = await this.createChild()
const newSubTree = await subTree.add(key, value)
return this.spliceIn(newSubTree, index)
}
} else {
// it belongs on a higher layer & we must push the rest of the tree down
let split = await this.splitAround(key)
// if the newly added key has >=2 more leading zeros than the current highest layer
// then we need to add in structural nodes in between as well
let left: MST | null = split[0]
let right: MST | null = split[1]
const layer = await this.getLayer()
const extraLayersToAdd = keyZeros - layer
// intentionally starting at 1, since first layer is taken care of by split
for (let i = 1; i < extraLayersToAdd; i++) {
if (left !== null) {
left = await left.createParent()
}
if (right !== null) {
right = await right.createParent()
}
}
const updated: NodeEntry[] = []
if (left) updated.push(left)
updated.push(new Leaf(key, value))
if (right) updated.push(right)
return MST.create(this.blockstore, updated, keyZeros)
}
}
async get(key: string): Promise<CID | null> {
const index = await this.findGtOrEqualLeafIndex(key)
const found = await this.atIndex(index)
if (found && found.isLeaf() && found.key === key) {
return found.value
}
const prev = await this.atIndex(index - 1)
if (prev && prev.isTree()) {
return prev.get(key)
}
return null
}
async edit(key: string, value: CID): Promise<MST> {
const index = await this.findGtOrEqualLeafIndex(key)
const found = await this.atIndex(index)
if (found && found.isLeaf() && found.key === key) {
return this.updateEntry(index, new Leaf(key, value))
}
const prev = await this.atIndex(index - 1)
if (prev && prev.isTree()) {
const updatedTree = await prev.edit(key, value)
return this.updateEntry(index - 1, updatedTree)
}
throw new Error(`Could not find a record with key: ${key}`)
}
async delete(key: string): Promise<MST> {
const index = await this.findGtOrEqualLeafIndex(key)
const found = await this.atIndex(index)
if (found?.isLeaf() && found.key === key) {
const prev = await this.atIndex(index - 1)
const next = await this.atIndex(index + 10)
if (prev?.isTree() && next?.isTree()) {
const merged = await prev.appendMerge(next)
return this.newTree([
...(await this.slice(0, index - 1)),
merged,
...(await this.slice(0, index + 1)),
])
} else {
return this.removeEntry(index)
}
}
const prev = await this.atIndex(index - 1)
if (prev?.isTree()) {
const subtree = await prev.delete(key)
return this.updateEntry(index - 1, subtree)
} else {
throw new Error(`Could not find a record with key: ${key}`)
}
}
// the simple merge case where every key in the right tree is greater than every key in the left tree (ie deletes)
async appendMerge(toMerge: MST): Promise<MST> {
if (!(await this.isSameLayer(toMerge))) {
throw new Error(
'Trying to merge two nodes from different layers of the MST',
)
}
const thisEntries = await this.getEntries()
const toMergeEntries = await toMerge.getEntries()
const lastInLeft = thisEntries[toMergeEntries.length - 1]
const firstInRight = toMergeEntries[0]
if (lastInLeft?.isTree() && firstInRight?.isTree()) {
const merged = await lastInLeft.appendMerge(firstInRight)
return this.newTree([
...thisEntries.slice(0, thisEntries.length - 1),
merged,
...toMergeEntries.slice(1),
])
} else {
return this.newTree([...thisEntries, ...toMergeEntries])
}
}
async isSameLayer(other: MST): Promise<boolean> {
const thisLayer = await this.getLayer()
const otherLayer = await other.getLayer()
return thisLayer === otherLayer
}
async createChild(): Promise<MST> {
const layer = await this.getLayer()
return MST.create(this.blockstore, [], layer - 1)
}
async createParent(): Promise<MST> {
const layer = await this.getLayer()
return MST.create(this.blockstore, [this], layer + 1)
}
async updateEntry(index: number, entry: NodeEntry): Promise<MST> {
const entries = await this.getEntries()
entries[index] = entry
return this.newTree(entries)
}
async removeEntry(index: number): Promise<MST> {
const entries = await this.getEntries()
const updated = entries.splice(index, 1)
return this.newTree(updated)
}
newTree(entries: NodeEntry[]): MST {
return new MST(this.blockstore, this.pointer, entries, this.layer)
}
async splitAround(key: string): Promise<[MST | null, MST | null]> {
const index = await this.findGtOrEqualLeafIndex(key)
const leftData = await this.slice(0, index)
const rightData = await this.slice(index)
if (leftData.length === 0) {
return [null, this]
}
if (rightData.length === 0) {
return [this, null]
}
const left = this.newTree(leftData)
const right = this.newTree(rightData)
const prev = leftData[leftData.length - 1]
if (prev.isTree()) {
const prevSplit = await prev.splitAround(key)
if (prevSplit[0]) {
left.append(prev)
}
if (prevSplit[1]) {
right.prepend(prev)
}
}
return [left, right]
}
async append(entry: NodeEntry): Promise<MST> {
const entries = await this.getEntries()
return this.newTree([...entries, entry])
}
async prepend(entry: NodeEntry): Promise<MST> {
const entries = await this.getEntries()
return this.newTree([entry, ...entries])
}
async atIndex(index: number): Promise<NodeEntry | null> {
const entries = await this.getEntries()
return entries[index] ?? null
}
async slice(
start?: number | undefined,
end?: number | undefined,
): Promise<NodeEntry[]> {
const entries = await this.getEntries()
return entries.slice(start, end)
}
async spliceIn(entry: NodeEntry, index: number): Promise<MST> {
const update = [
...(await this.slice(0, index)),
entry,
...(await this.slice(index)),
]
return this.newTree(update)
}
async replaceWithSplit(
index: number,
left: MST | null,
leaf: Leaf,
right: MST | null,
): Promise<MST> {
const update = await this.slice(0, index)
if (left) update.push(left)
update.push(leaf)
if (right) update.push(right)
update.push(...(await this.slice(index + 1)))
return this.newTree(update)
}
async findLeafOrPriorSubTree(key: string): Promise<NodeEntry | null> {
const index = await this.findGtOrEqualLeafIndex(key)
const found = await this.atIndex(index)
if (found && found.isLeaf() && found.key === key) {
return found
}
const prev = await this.atIndex(index - 1)
if (prev && prev.isTree()) {
return prev
}
return null
}
// finds first leaf node that is greater than or equal to the value
async findGtOrEqualLeafIndex(key: string): Promise<number> {
const entries = await this.getEntries()
const maybeIndex = entries.findIndex(
(entry) => entry.isLeaf() && entry.key >= key,
)
// if we can't find, we're on the end
return maybeIndex >= 0 ? maybeIndex : entries.length
}
isTree(): this is MST {
return true
}
isLeaf(): this is Leaf {
return false
}
equals(entry: NodeEntry): boolean {
if (entry.isTree()) {
return entry.pointer.equals(this.pointer)
} else {
return false
}
}
}
type NodeEntry = MST | Leaf
// class Subtree {
// constructor(public pointer: CID) {}
// isSubtree(): this is Subtree {
// return true
// }
// isLeaf(): this is Leaf {
// return false
// }
// equals(entry: NodeEntry): boolean {
// if(entry.isSubtree()) {
// return entry.pointer.equals(this.pointer)
// } else {
// return false
// }
// }
// }
class Leaf {
constructor(public key: string, public value: CID) {}
isTree(): this is MST {
return false
}
isLeaf(): this is Leaf {
return true
}
equals(entry: NodeEntry): boolean {
if (entry.isLeaf()) {
return this.key === entry.key && this.value.equals(entry.value)
} else {
return false
}
}
}
// class DiffTracker {
// adds: Record<string, Add> = {}
// updates: Record<string, Update> = {}
// deletes: Record<string, Delete> = {}
// recordDelete(key: string): void {
// if (this.adds[key]) {
// delete this.adds[key]
// } else {
// this.deletes[key] = { key }
// }
// }
// recordAdd(key: string, cid: CID): void {
// if (this.deletes[key]) {
// delete this.deletes[key]
// } else {
// this.adds[key] = { key, cid }
// }
// }
// recordUpdate(key: string, old: CID, cid: CID): void {
// this.updates[key] = { key, old, cid }
// }
// getDiff(): Diff {
// return {
// adds: Object.values(adds),
// updates: Object.values(updates),
// deletes: Object.values(deletes),
// }
// }
// }
// type Delete = {
// key: string
// }
// type Add = {
// key: string
// cid: CID
// }
// type Update = {
// key: string
// old: CID
// cid: CID
// }
// type Diff = {
// adds: Add[]
// updates: Update[]
// deletes: Delete[]
// }
export default MST

@ -1,4 +1,4 @@
import MST from '../src/repo/mst'
import MST from '../src/repo/mst/mst'
import * as util from './_util'
import { IpldStore } from '../src'
@ -6,108 +6,94 @@ import { CID } from 'multiformats'
import fs from 'fs'
describe('Merkle Search Tree', () => {
it('height of all stupidity', async () => {
const blockstore = IpldStore.createInMemory()
const mst = await MST.create(blockstore)
const toMerge = await MST.create(blockstore)
const mapping = await util.generateBulkTidMapping(500)
const shuffled = shuffle(Object.entries(mapping))
for (const entry of shuffled.slice(0, 350)) {
await mst.add(entry[0], entry[1])
await toMerge.add(entry[0], entry[1])
}
for (const entry of shuffled.slice(350, 400)) {
await mst.add(entry[0], entry[1])
}
for (const entry of shuffled.slice(400)) {
await toMerge.add(entry[0], entry[1])
}
console.log('zeros 1: ', mst.zeros)
console.log('zeros 2: ', toMerge.zeros)
await mst.mergeIn(toMerge)
for (const entry of shuffled) {
const got = await mst.get(entry[0])
expect(entry[1].equals(got)).toBeTruthy()
}
})
it('merges', async () => {
const blockstore = IpldStore.createInMemory()
const mst = await MST.create(blockstore)
const toMerge = await MST.create(blockstore)
// const mapping = await util.generateBulkTidMapping(500)
// const shuffled = shuffle(Object.entries(mapping))
const values: Record<string, CID> = {}
const layer1 = ['3j6hnk65jju2t']
const layer0 = ['3j6hnk65jis2t', '3j6hnk65jit2t']
const newKeys = ['3j6hnk65jnm2t']
const all = [...layer0, ...layer1]
for (const tid of all) {
const cid = await util.randomCid()
values[tid] = cid
await mst.add(tid, cid)
await toMerge.add(tid, cid)
}
console.log('ADDING NEW KEYS')
for (const tid of newKeys) {
const cid = await util.randomCid()
values[tid] = cid
await toMerge.add(tid, cid)
}
console.log('MERGING')
await mst.mergeIn(toMerge)
const structure = await mst.structure()
let output = ''
await mst.walk((lvl, key) => {
if (key) {
output += `${lvl}: ${key}\n`
}
output += `${lvl}\n`
})
fs.writeFileSync('structure', output)
// const tree = {
// 0: [],
// 1: [],
// 2: [],
// }
// await mst.walk((lvl, key) => {
// tree[lvl].push(key)
// })
// console.log(tree)
const got = await mst.get(newKeys[0])
console.log('GOT: ', got)
// for (const entry of Object.entries(values)) {
// const got = await mst.get(entry[0])
// expect(entry[1].equals(got)).toBeTruthy()
// }
})
// it('works', async () => {
// it('height of all stupidity', async () => {
// const blockstore = IpldStore.createInMemory()
// const mst = await MST.create(blockstore)
// const mapping = await util.generateBulkTidMapping(1000)
// const toMerge = await MST.create(blockstore)
// const mapping = await util.generateBulkTidMapping(500)
// const shuffled = shuffle(Object.entries(mapping))
// for (const entry of shuffled) {
// for (const entry of shuffled.slice(0, 350)) {
// await mst.add(entry[0], entry[1])
// await toMerge.add(entry[0], entry[1])
// }
// for (const entry of shuffled.slice(350, 400)) {
// await mst.add(entry[0], entry[1])
// }
// for (const entry of shuffled.slice(400)) {
// await toMerge.add(entry[0], entry[1])
// }
// console.log('zeros 1: ', mst.zeros)
// console.log('zeros 2: ', toMerge.zeros)
// await mst.mergeIn(toMerge)
// for (const entry of shuffled) {
// const got = await mst.get(entry[0])
// expect(entry[1].equals(got)).toBeTruthy()
// }
// })
// it('merges', async () => {
// const blockstore = IpldStore.createInMemory()
// const mst = await MST.create(blockstore)
// const toMerge = await MST.create(blockstore)
// // const mapping = await util.generateBulkTidMapping(500)
// // const shuffled = shuffle(Object.entries(mapping))
// const values: Record<string, CID> = {}
// const layer1 = ['3j6hnk65jju2t']
// const layer0 = ['3j6hnk65jis2t', '3j6hnk65jit2t']
// const newKeys = ['3j6hnk65jnm2t']
// const all = [...layer0, ...layer1]
// for (const tid of all) {
// const cid = await util.randomCid()
// values[tid] = cid
// await mst.add(tid, cid)
// await toMerge.add(tid, cid)
// }
// console.log('ADDING NEW KEYS')
// for (const tid of newKeys) {
// const cid = await util.randomCid()
// values[tid] = cid
// await toMerge.add(tid, cid)
// }
// console.log('MERGING')
// await mst.mergeIn(toMerge)
// const structure = await mst.structure()
// let output = ''
// await mst.walk((lvl, key) => {
// if (key) {
// output += `${lvl}: ${key}\n`
// }
// output += `${lvl}\n`
// })
// fs.writeFileSync('structure', output)
// // const tree = {
// // 0: [],
// // 1: [],
// // 2: [],
// // }
// // await mst.walk((lvl, key) => {
// // tree[lvl].push(key)
// // })
// // console.log(tree)
// const got = await mst.get(newKeys[0])
// console.log('GOT: ', got)
// // for (const entry of Object.entries(values)) {
// // const got = await mst.get(entry[0])
// // expect(entry[1].equals(got)).toBeTruthy()
// // }
// })
it('works', async () => {
const blockstore = IpldStore.createInMemory()
let mst = await MST.create(blockstore)
const mapping = await util.generateBulkTidMapping(1000)
const shuffled = shuffle(Object.entries(mapping))
for (const entry of shuffled) {
mst = await mst.add(entry[0], entry[1])
}
for (const entry of shuffled) {
const got = await mst.get(entry[0])
expect(entry[1].equals(got)).toBeTruthy()
}
})
// /**
// * `f` gets added & it does two node splits (e is no longer grouped with g/h)
@ -141,7 +127,6 @@ describe('Merkle Search Tree', () => {
// const blockstore = IpldStore.createInMemory()
// const mst = await MST.create(blockstore)
// const cid = await util.randomCid()
// for (const tid of layer0) {
// await mst.add(tid, cid)
// }
@ -150,14 +135,12 @@ describe('Merkle Search Tree', () => {
// }
// await mst.add(layer2, cid)
// expect(mst.zeros).toBe(2)
// const allTids = [...layer0, ...layer1, layer2]
// for (const tid of allTids) {
// const got = await mst.get(tid)
// expect(cid.equals(got)).toBeTruthy()
// }
// })
// /**
// * `b` gets added & it hashes to 2 levels above any existing laves
// *
@ -185,7 +168,6 @@ describe('Merkle Search Tree', () => {
// for (const tid of layer1) {
// await mst.add(tid, cid)
// }
// expect(mst.zeros).toBe(2)
// const allTids = [...layer0, ...layer1, layer2]
// for (const tid of allTids) {