@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.
- package/CHANGELOG.md +16 -0
- package/package.json +22 -17
- package/jest.config.cjs +0 -24
- package/src/block-map.ts +0 -131
- package/src/car.ts +0 -357
- package/src/cid-set.ts +0 -55
- package/src/data-diff.ts +0 -117
- package/src/error.ts +0 -43
- package/src/index.ts +0 -11
- package/src/logger.ts +0 -7
- package/src/mst/diff.ts +0 -114
- package/src/mst/index.ts +0 -4
- package/src/mst/mst.ts +0 -892
- package/src/mst/util.ts +0 -160
- package/src/mst/walker.ts +0 -118
- package/src/parse.ts +0 -44
- package/src/readable-repo.ts +0 -86
- package/src/repo.ts +0 -236
- package/src/storage/index.ts +0 -4
- package/src/storage/memory-blockstore.ts +0 -76
- package/src/storage/readable-blockstore.ts +0 -55
- package/src/storage/sync-storage.ts +0 -35
- package/src/storage/types.ts +0 -47
- package/src/sync/consumer.ts +0 -207
- package/src/sync/index.ts +0 -2
- package/src/sync/provider.ts +0 -67
- package/src/types.ts +0 -227
- package/src/util.ts +0 -146
- package/tests/_keys.ts +0 -156
- package/tests/_util.ts +0 -265
- package/tests/car-file-fixtures.json +0 -28
- package/tests/car.test.ts +0 -125
- package/tests/commit-data.test.ts +0 -94
- package/tests/commit-proof-fixtures.json +0 -118
- package/tests/commit-proofs.test.ts +0 -63
- package/tests/covering-proofs.test.ts +0 -256
- package/tests/mst.test.ts +0 -450
- package/tests/proofs.test.ts +0 -155
- package/tests/repo.test.ts +0 -106
- package/tests/sync.test.ts +0 -95
- package/tsconfig.build.json +0 -8
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -7
package/tests/mst.test.ts
DELETED
|
@@ -1,450 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
|
-
import { Cid, parseCid } from '@atproto/lex-data'
|
|
3
|
-
import { DataAdd, DataDelete, DataDiff, DataUpdate } from '../src/data-diff.js'
|
|
4
|
-
import { MST } from '../src/mst/index.js'
|
|
5
|
-
import { InvalidMstKeyError, countPrefixLen } from '../src/mst/util.js'
|
|
6
|
-
import { MemoryBlockstore } from '../src/storage/index.js'
|
|
7
|
-
import * as util from './_util.js'
|
|
8
|
-
|
|
9
|
-
describe('Merkle Search Tree', () => {
|
|
10
|
-
let blockstore: MemoryBlockstore
|
|
11
|
-
let mst: MST
|
|
12
|
-
let mapping: Record<string, Cid>
|
|
13
|
-
let shuffled: [string, Cid][]
|
|
14
|
-
|
|
15
|
-
beforeAll(async () => {
|
|
16
|
-
blockstore = new MemoryBlockstore()
|
|
17
|
-
mst = await MST.create(blockstore)
|
|
18
|
-
mapping = await util.generateBulkDataKeys(1000, blockstore)
|
|
19
|
-
shuffled = util.shuffle(Object.entries(mapping))
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('adds records', async () => {
|
|
23
|
-
for (const entry of shuffled) {
|
|
24
|
-
mst = await mst.add(entry[0], entry[1])
|
|
25
|
-
}
|
|
26
|
-
for (const entry of shuffled) {
|
|
27
|
-
const got = await mst.get(entry[0])
|
|
28
|
-
assert(got !== null)
|
|
29
|
-
expect(entry[1].equals(got)).toBe(true)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const totalSize = await mst.leafCount()
|
|
33
|
-
expect(totalSize).toBe(1000)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('edits records', async () => {
|
|
37
|
-
let editedMst = mst
|
|
38
|
-
const toEdit = shuffled.slice(0, 100)
|
|
39
|
-
|
|
40
|
-
const edited: [string, Cid][] = []
|
|
41
|
-
for (const entry of toEdit) {
|
|
42
|
-
const newCid = await util.randomCid()
|
|
43
|
-
editedMst = await editedMst.update(entry[0], newCid)
|
|
44
|
-
edited.push([entry[0], newCid])
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
for (const entry of edited) {
|
|
48
|
-
const got = await editedMst.get(entry[0])
|
|
49
|
-
assert(got !== null)
|
|
50
|
-
expect(entry[1].equals(got)).toBe(true)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const totalSize = await editedMst.leafCount()
|
|
54
|
-
expect(totalSize).toBe(1000)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('deletes records', async () => {
|
|
58
|
-
let deletedMst = mst
|
|
59
|
-
const toDelete = shuffled.slice(0, 100)
|
|
60
|
-
const theRest = shuffled.slice(100)
|
|
61
|
-
for (const entry of toDelete) {
|
|
62
|
-
deletedMst = await deletedMst.delete(entry[0])
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const totalSize = await deletedMst.leafCount()
|
|
66
|
-
expect(totalSize).toBe(900)
|
|
67
|
-
|
|
68
|
-
for (const entry of toDelete) {
|
|
69
|
-
const got = await deletedMst.get(entry[0])
|
|
70
|
-
expect(got).toBe(null)
|
|
71
|
-
}
|
|
72
|
-
for (const entry of theRest) {
|
|
73
|
-
const got = await deletedMst.get(entry[0])
|
|
74
|
-
assert(got !== null)
|
|
75
|
-
expect(entry[1].equals(got)).toBe(true)
|
|
76
|
-
}
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('is order independent', async () => {
|
|
80
|
-
const allNodes = await mst.allNodes()
|
|
81
|
-
|
|
82
|
-
let recreated = await MST.create(blockstore)
|
|
83
|
-
const reshuffled = util.shuffle(Object.entries(mapping))
|
|
84
|
-
for (const entry of reshuffled) {
|
|
85
|
-
recreated = await recreated.add(entry[0], entry[1])
|
|
86
|
-
}
|
|
87
|
-
const allReshuffled = await recreated.allNodes()
|
|
88
|
-
|
|
89
|
-
expect(allNodes.length).toBe(allReshuffled.length)
|
|
90
|
-
for (let i = 0; i < allNodes.length; i++) {
|
|
91
|
-
expect(await allNodes[i].equals(allReshuffled[i])).toBeTruthy()
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('saves and loads from blockstore', async () => {
|
|
96
|
-
const root = await util.saveMst(blockstore, mst)
|
|
97
|
-
const loaded = await MST.load(blockstore, root)
|
|
98
|
-
const origNodes = await mst.allNodes()
|
|
99
|
-
const loadedNodes = await loaded.allNodes()
|
|
100
|
-
expect(origNodes.length).toBe(loadedNodes.length)
|
|
101
|
-
for (let i = 0; i < origNodes.length; i++) {
|
|
102
|
-
expect(await origNodes[i].equals(loadedNodes[i])).toBeTruthy()
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('diffs', async () => {
|
|
107
|
-
let toDiff = mst
|
|
108
|
-
|
|
109
|
-
const toAdd = Object.entries(
|
|
110
|
-
await util.generateBulkDataKeys(100, blockstore),
|
|
111
|
-
)
|
|
112
|
-
const toEdit = shuffled.slice(500, 600)
|
|
113
|
-
const toDel = shuffled.slice(400, 500)
|
|
114
|
-
|
|
115
|
-
const expectedAdds: Record<string, DataAdd> = {}
|
|
116
|
-
const expectedUpdates: Record<string, DataUpdate> = {}
|
|
117
|
-
const expectedDels: Record<string, DataDelete> = {}
|
|
118
|
-
|
|
119
|
-
for (const entry of toAdd) {
|
|
120
|
-
toDiff = await toDiff.add(entry[0], entry[1])
|
|
121
|
-
expectedAdds[entry[0]] = { key: entry[0], cid: entry[1] }
|
|
122
|
-
}
|
|
123
|
-
for (const entry of toEdit) {
|
|
124
|
-
const updated = await util.randomCid()
|
|
125
|
-
toDiff = await toDiff.update(entry[0], updated)
|
|
126
|
-
expectedUpdates[entry[0]] = {
|
|
127
|
-
key: entry[0],
|
|
128
|
-
prev: entry[1],
|
|
129
|
-
cid: updated,
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
for (const entry of toDel) {
|
|
133
|
-
toDiff = await toDiff.delete(entry[0])
|
|
134
|
-
expectedDels[entry[0]] = { key: entry[0], cid: entry[1] }
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const diff = await DataDiff.of(toDiff, mst)
|
|
138
|
-
|
|
139
|
-
expect(diff.addList().length).toBe(100)
|
|
140
|
-
expect(diff.updateList().length).toBe(100)
|
|
141
|
-
expect(diff.deleteList().length).toBe(100)
|
|
142
|
-
|
|
143
|
-
expect(diff.adds).toEqual(expectedAdds)
|
|
144
|
-
expect(diff.updates).toEqual(expectedUpdates)
|
|
145
|
-
expect(diff.deletes).toEqual(expectedDels)
|
|
146
|
-
|
|
147
|
-
// ensure we correctly report all added CIDs
|
|
148
|
-
for await (const entry of toDiff.walk()) {
|
|
149
|
-
const cid = entry.isTree() ? await entry.getPointer() : entry.value
|
|
150
|
-
const found =
|
|
151
|
-
(await blockstore.has(cid)) ||
|
|
152
|
-
diff.newMstBlocks.has(cid) ||
|
|
153
|
-
diff.newLeafCids.has(cid)
|
|
154
|
-
expect(found).toBeTruthy()
|
|
155
|
-
}
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
describe('utils', () => {
|
|
159
|
-
it('counts prefix length', () => {
|
|
160
|
-
expect(countPrefixLen('abc', 'abc')).toBe(3)
|
|
161
|
-
expect(countPrefixLen('', 'abc')).toBe(0)
|
|
162
|
-
expect(countPrefixLen('abc', '')).toBe(0)
|
|
163
|
-
expect(countPrefixLen('ab', 'abc')).toBe(2)
|
|
164
|
-
expect(countPrefixLen('abc', 'ab')).toBe(2)
|
|
165
|
-
expect(countPrefixLen('abcde', 'abc')).toBe(3)
|
|
166
|
-
expect(countPrefixLen('abc', 'abcde')).toBe(3)
|
|
167
|
-
expect(countPrefixLen('abcde', 'abc1')).toBe(3)
|
|
168
|
-
expect(countPrefixLen('abcde', 'abb')).toBe(2)
|
|
169
|
-
expect(countPrefixLen('abcde', 'qbb')).toBe(0)
|
|
170
|
-
expect(countPrefixLen('', 'asdf')).toBe(0)
|
|
171
|
-
expect(countPrefixLen('abc', 'abc\x00')).toBe(3)
|
|
172
|
-
expect(countPrefixLen('abc\x00', 'abc')).toBe(3)
|
|
173
|
-
})
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
describe('MST Interop Allowable Keys', () => {
|
|
177
|
-
let blockstore: MemoryBlockstore
|
|
178
|
-
let mst: MST
|
|
179
|
-
let cid1: Cid
|
|
180
|
-
|
|
181
|
-
beforeAll(async () => {
|
|
182
|
-
blockstore = new MemoryBlockstore()
|
|
183
|
-
mst = await MST.create(blockstore)
|
|
184
|
-
cid1 = parseCid(
|
|
185
|
-
'bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454',
|
|
186
|
-
)
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
const expectReject = async (key: string) => {
|
|
190
|
-
const promise = mst.add(key, cid1)
|
|
191
|
-
await expect(promise).rejects.toThrow(InvalidMstKeyError)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const expectAllow = async (key: string) => {
|
|
195
|
-
await mst.add(key, cid1)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
it('rejects the empty key', async () => {
|
|
199
|
-
await expectReject('')
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
it('rejects a key with no collection', async () => {
|
|
203
|
-
await expectReject('asdf')
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
it('rejects a key with a nested collection', async () => {
|
|
207
|
-
await expectReject('nested/collection/asdf')
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
it('rejects on empty coll or rkey', async () => {
|
|
211
|
-
await expectReject('coll/')
|
|
212
|
-
await expectReject('/rkey')
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it('rejects non-ascii chars', async () => {
|
|
216
|
-
await expectReject('coll/jalapeñoA')
|
|
217
|
-
await expectReject('coll/coöperative')
|
|
218
|
-
await expectReject('coll/abc💩')
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
it('rejects ascii that we dont support', async () => {
|
|
222
|
-
await expectReject('coll/key$')
|
|
223
|
-
await expectReject('coll/key%')
|
|
224
|
-
await expectReject('coll/key(')
|
|
225
|
-
await expectReject('coll/key)')
|
|
226
|
-
await expectReject('coll/key+')
|
|
227
|
-
await expectReject('coll/key=')
|
|
228
|
-
await expectReject('coll/@handle')
|
|
229
|
-
await expectReject('coll/any space')
|
|
230
|
-
await expectReject('coll/#extra')
|
|
231
|
-
await expectReject('coll/any+space')
|
|
232
|
-
await expectReject('coll/number[3]')
|
|
233
|
-
await expectReject('coll/number(3)')
|
|
234
|
-
await expectReject('coll/dHJ1ZQ==')
|
|
235
|
-
await expectReject('coll/"quote"')
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
it('rejects keys over 1024 chars', async () => {
|
|
239
|
-
await expectReject(
|
|
240
|
-
'coll/asdofiupoiwqeurfpaosidfuapsodirupasoirupasoeiruaspeoriuaspeoriu2p3o4iu1pqw3oiuaspdfoiuaspdfoiuasdfpoiasdufpwoieruapsdofiuaspdfoiuasdpfoiausdfpoasidfupasodifuaspdofiuasdpfoiasudfpoasidfuapsodfiuasdpfoiausdfpoasidufpasodifuapsdofiuasdpofiuasdfpoaisdufpaoasdofiupoiwqeurfpaosidfuapsodirupasoirupasoeiruaspeoriuaspeoriu2p3o4iu1pqw3oiuaspdfoiuaspdfoiuasdfpoiasdufpwoieruapsdofiuaspdfoiuasdpfoiausdfpoasidfupasodifuaspdofiuasdpfoiasudfpoasidfuapsodfiuasdpfoiausdfpoasidufpasodifuapsdofiuasdpofiuasdfpoaisdufpaoasdofiupoiwqeurfpaosidfuapsodirupasoirupasoeiruaspeoriuaspeoriu2p3o4iu1pqw3oiuaspdfoiuaspdfoiuasdfpoiasdufpwoieruapsdofiuaspdfoiuasdpfoiausdfpoasidfupasodifuaspdofiuasdpfoiasudfpoasidfuapsodfiuasdpfoiausdfpoasidufpasodifuapsdofiuasdpofiuasdfpoaisdufpaoasdofiupoiwqeurfpaosidfuapsodirupasoirupasoeiruaspeoriuaspeoriu2p3o4iu1pqw3oiuaspdfoiuaspdfoiuasdfpoiasdufpwoieruapsdofiuaspdfoiuasdpfoiausdfpoasidfupasodifuaspdofiuasdpfoiasudfpoasidfuapsodfiuasdpfoiausdfpoasidufpasodifuapsdofiuasdpofiuasdfpoaisdufpaoasdofiupoiwqeurfpaosidfuapsodirupasoirupasoeiruaspeoriuaspeoriu2p3o4iu1pqw3oiuaspdfoiuaspdfoiuasdfpoiasdufpwoieruapsdofiuaspdfoiuasdpfoiausdfpoasidfupasodifuaspdofiuasdpfoiasudfpoasidfuapsodfiuasdpfoiausdfpoasidufpasodifuapsdofiuasdpofiuasdfpoaisdufpao',
|
|
241
|
-
)
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
it('allows valid keys', async () => {
|
|
245
|
-
await expectAllow('coll/3jui7kd54zh2y')
|
|
246
|
-
await expectAllow('coll/self')
|
|
247
|
-
await expectAllow('coll/example.com')
|
|
248
|
-
await expectAllow('com.example/rkey')
|
|
249
|
-
await expectAllow('coll/~1.2-3_')
|
|
250
|
-
await expectAllow('coll/dHJ1ZQ')
|
|
251
|
-
await expectAllow('coll/pre:fix')
|
|
252
|
-
await expectAllow('coll/_')
|
|
253
|
-
})
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
describe('MST Interop Known Maps', () => {
|
|
257
|
-
let blockstore: MemoryBlockstore
|
|
258
|
-
let mst: MST
|
|
259
|
-
let cid1: Cid
|
|
260
|
-
|
|
261
|
-
beforeAll(async () => {
|
|
262
|
-
blockstore = new MemoryBlockstore()
|
|
263
|
-
cid1 = parseCid(
|
|
264
|
-
'bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454',
|
|
265
|
-
)
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
beforeEach(async () => {
|
|
269
|
-
mst = await MST.create(blockstore)
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
it('computes "empty" tree root CID', async () => {
|
|
273
|
-
expect(await mst.leafCount()).toBe(0)
|
|
274
|
-
expect((await mst.getPointer()).toString()).toBe(
|
|
275
|
-
'bafyreie5737gdxlw5i64vzichcalba3z2v5n6icifvx5xytvske7mr3hpm',
|
|
276
|
-
)
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
it('computes "trivial" tree root CID', async () => {
|
|
280
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fo2j', cid1)
|
|
281
|
-
expect(await mst.leafCount()).toBe(1)
|
|
282
|
-
expect((await mst.getPointer()).toString()).toBe(
|
|
283
|
-
'bafyreibj4lsc3aqnrvphp5xmrnfoorvru4wynt6lwidqbm2623a6tatzdu',
|
|
284
|
-
)
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
it('computes "singlelayer2" tree root CID', async () => {
|
|
288
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fx2j', cid1)
|
|
289
|
-
expect(await mst.leafCount()).toBe(1)
|
|
290
|
-
expect(await mst.layer).toBe(2)
|
|
291
|
-
expect((await mst.getPointer()).toString()).toBe(
|
|
292
|
-
'bafyreih7wfei65pxzhauoibu3ls7jgmkju4bspy4t2ha2qdjnzqvoy33ai',
|
|
293
|
-
)
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
it('computes "simple" tree root CID', async () => {
|
|
297
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fp2j', cid1) // level 0
|
|
298
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fr2j', cid1) // level 0
|
|
299
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fs2j', cid1) // level 1
|
|
300
|
-
mst = await mst.add('com.example.record/3jqfcqzm3ft2j', cid1) // level 0
|
|
301
|
-
mst = await mst.add('com.example.record/3jqfcqzm4fc2j', cid1) // level 0
|
|
302
|
-
expect(await mst.leafCount()).toBe(5)
|
|
303
|
-
expect((await mst.getPointer()).toString()).toBe(
|
|
304
|
-
'bafyreicmahysq4n6wfuxo522m6dpiy7z7qzym3dzs756t5n7nfdgccwq7m',
|
|
305
|
-
)
|
|
306
|
-
})
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
describe('MST Interop Edge Cases', () => {
|
|
310
|
-
let blockstore: MemoryBlockstore
|
|
311
|
-
let mst: MST
|
|
312
|
-
let cid1: Cid
|
|
313
|
-
|
|
314
|
-
beforeAll(async () => {
|
|
315
|
-
blockstore = new MemoryBlockstore()
|
|
316
|
-
cid1 = parseCid(
|
|
317
|
-
'bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454',
|
|
318
|
-
)
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
beforeEach(async () => {
|
|
322
|
-
mst = await MST.create(blockstore)
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
it('trims top of tree on delete', async () => {
|
|
326
|
-
const l1root =
|
|
327
|
-
'bafyreifnqrwbk6ffmyaz5qtujqrzf5qmxf7cbxvgzktl4e3gabuxbtatv4'
|
|
328
|
-
const l0root =
|
|
329
|
-
'bafyreie4kjuxbwkhzg2i5dljaswcroeih4dgiqq6pazcmunwt2byd725vi'
|
|
330
|
-
|
|
331
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fn2j', cid1) // level 0
|
|
332
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fo2j', cid1) // level 0
|
|
333
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fp2j', cid1) // level 0
|
|
334
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fs2j', cid1) // level 1
|
|
335
|
-
mst = await mst.add('com.example.record/3jqfcqzm3ft2j', cid1) // level 0
|
|
336
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fu2j', cid1) // level 0
|
|
337
|
-
|
|
338
|
-
expect(await mst.leafCount()).toBe(6)
|
|
339
|
-
expect(await mst.layer).toBe(1)
|
|
340
|
-
expect((await mst.getPointer()).toString()).toBe(l1root)
|
|
341
|
-
|
|
342
|
-
mst = await mst.delete('com.example.record/3jqfcqzm3fs2j') // level 1
|
|
343
|
-
expect(await mst.leafCount()).toBe(5)
|
|
344
|
-
expect(await mst.layer).toBe(0)
|
|
345
|
-
expect((await mst.getPointer()).toString()).toBe(l0root)
|
|
346
|
-
})
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
*
|
|
350
|
-
* * *
|
|
351
|
-
* _________|________ ____|_____
|
|
352
|
-
* | | | | | | | |
|
|
353
|
-
* * d * i * -> * f *
|
|
354
|
-
* __|__ __|__ __|__ __|__ __|___
|
|
355
|
-
* | | | | | | | | | | | | | | |
|
|
356
|
-
* a b c e g h j k l * d * * i *
|
|
357
|
-
* __|__ | _|_ __|__
|
|
358
|
-
* | | | | | | | | |
|
|
359
|
-
* a b c e g h j k l
|
|
360
|
-
*
|
|
361
|
-
*/
|
|
362
|
-
it('handles insertion that splits two layers down', async () => {
|
|
363
|
-
const l1root =
|
|
364
|
-
'bafyreiettyludka6fpgp33stwxfuwhkzlur6chs4d2v4nkmq2j3ogpdjem'
|
|
365
|
-
const l2root =
|
|
366
|
-
'bafyreid2x5eqs4w4qxvc5jiwda4cien3gw2q6cshofxwnvv7iucrmfohpm'
|
|
367
|
-
|
|
368
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fo2j', cid1) // A; level 0
|
|
369
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fp2j', cid1) // B; level 0
|
|
370
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fr2j', cid1) // C; level 0
|
|
371
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fs2j', cid1) // D; level 1
|
|
372
|
-
mst = await mst.add('com.example.record/3jqfcqzm3ft2j', cid1) // E; level 0
|
|
373
|
-
// GAP for F
|
|
374
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fz2j', cid1) // G; level 0
|
|
375
|
-
mst = await mst.add('com.example.record/3jqfcqzm4fc2j', cid1) // H; level 0
|
|
376
|
-
mst = await mst.add('com.example.record/3jqfcqzm4fd2j', cid1) // I; level 1
|
|
377
|
-
mst = await mst.add('com.example.record/3jqfcqzm4ff2j', cid1) // J; level 0
|
|
378
|
-
mst = await mst.add('com.example.record/3jqfcqzm4fg2j', cid1) // K; level 0
|
|
379
|
-
mst = await mst.add('com.example.record/3jqfcqzm4fh2j', cid1) // L; level 0
|
|
380
|
-
|
|
381
|
-
expect(await mst.leafCount()).toBe(11)
|
|
382
|
-
expect(await mst.layer).toBe(1)
|
|
383
|
-
expect((await mst.getPointer()).toString()).toBe(l1root)
|
|
384
|
-
|
|
385
|
-
// insert F, which will push E out of the node with G+H to a new node under D
|
|
386
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fx2j', cid1) // F; level 2
|
|
387
|
-
expect(await mst.leafCount()).toBe(12)
|
|
388
|
-
expect(await mst.layer).toBe(2)
|
|
389
|
-
expect((await mst.getPointer()).toString()).toBe(l2root)
|
|
390
|
-
|
|
391
|
-
// remove F, which should push E back over with G+H
|
|
392
|
-
mst = await mst.delete('com.example.record/3jqfcqzm3fx2j') // F; level 2
|
|
393
|
-
expect(await mst.leafCount()).toBe(11)
|
|
394
|
-
expect(await mst.layer).toBe(1)
|
|
395
|
-
expect((await mst.getPointer()).toString()).toBe(l1root)
|
|
396
|
-
})
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
*
|
|
400
|
-
* * -> *
|
|
401
|
-
* __|__ __|__
|
|
402
|
-
* | | | | |
|
|
403
|
-
* a c * b *
|
|
404
|
-
* | |
|
|
405
|
-
* * *
|
|
406
|
-
* | |
|
|
407
|
-
* a c
|
|
408
|
-
*
|
|
409
|
-
*/
|
|
410
|
-
it('handles new layers that are two higher than existing', async () => {
|
|
411
|
-
const l0root =
|
|
412
|
-
'bafyreidfcktqnfmykz2ps3dbul35pepleq7kvv526g47xahuz3rqtptmky'
|
|
413
|
-
const l2root =
|
|
414
|
-
'bafyreiavxaxdz7o7rbvr3zg2liox2yww46t7g6hkehx4i4h3lwudly7dhy'
|
|
415
|
-
const l2root2 =
|
|
416
|
-
'bafyreig4jv3vuajbsybhyvb7gggvpwh2zszwfyttjrj6qwvcsp24h6popu'
|
|
417
|
-
|
|
418
|
-
mst = await mst.add('com.example.record/3jqfcqzm3ft2j', cid1) // A; level 0
|
|
419
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fz2j', cid1) // C; level 0
|
|
420
|
-
expect(await mst.leafCount()).toBe(2)
|
|
421
|
-
expect(await mst.layer).toBe(0)
|
|
422
|
-
expect((await mst.getPointer()).toString()).toBe(l0root)
|
|
423
|
-
|
|
424
|
-
// insert B, which is two levels above
|
|
425
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fx2j', cid1) // B; level 2
|
|
426
|
-
expect(await mst.leafCount()).toBe(3)
|
|
427
|
-
expect(await mst.layer).toBe(2)
|
|
428
|
-
expect((await mst.getPointer()).toString()).toBe(l2root)
|
|
429
|
-
|
|
430
|
-
// remove B
|
|
431
|
-
mst = await mst.delete('com.example.record/3jqfcqzm3fx2j') // B; level 2
|
|
432
|
-
expect(await mst.leafCount()).toBe(2)
|
|
433
|
-
expect(await mst.layer).toBe(0)
|
|
434
|
-
expect((await mst.getPointer()).toString()).toBe(l0root)
|
|
435
|
-
|
|
436
|
-
// insert B (level=2) and D (level=1)
|
|
437
|
-
mst = await mst.add('com.example.record/3jqfcqzm3fx2j', cid1) // B; level 2
|
|
438
|
-
mst = await mst.add('com.example.record/3jqfcqzm4fd2j', cid1) // D; level 1
|
|
439
|
-
expect(await mst.leafCount()).toBe(4)
|
|
440
|
-
expect(await mst.layer).toBe(2)
|
|
441
|
-
expect((await mst.getPointer()).toString()).toBe(l2root2)
|
|
442
|
-
|
|
443
|
-
// remove D
|
|
444
|
-
mst = await mst.delete('com.example.record/3jqfcqzm4fd2j') // D; level 1
|
|
445
|
-
expect(await mst.leafCount()).toBe(3)
|
|
446
|
-
expect(await mst.layer).toBe(2)
|
|
447
|
-
expect((await mst.getPointer()).toString()).toBe(l2root)
|
|
448
|
-
})
|
|
449
|
-
})
|
|
450
|
-
})
|
package/tests/proofs.test.ts
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { TID } from '@atproto/common-web'
|
|
2
|
-
import * as crypto from '@atproto/crypto'
|
|
3
|
-
import { cidForLex } from '@atproto/lex-cbor'
|
|
4
|
-
import { NsidString } from '@atproto/syntax'
|
|
5
|
-
import { RecordCidClaim, RecordPath, Repo, RepoContents } from '../src/index.js'
|
|
6
|
-
import { MemoryBlockstore } from '../src/storage/index.js'
|
|
7
|
-
import * as sync from '../src/sync/index.js'
|
|
8
|
-
import * as util from './_util.js'
|
|
9
|
-
|
|
10
|
-
describe('Repo Proofs', () => {
|
|
11
|
-
let storage: MemoryBlockstore
|
|
12
|
-
let repo: Repo
|
|
13
|
-
let keypair: crypto.Keypair
|
|
14
|
-
let repoData: RepoContents
|
|
15
|
-
|
|
16
|
-
const repoDid = 'did:example:test'
|
|
17
|
-
|
|
18
|
-
beforeAll(async () => {
|
|
19
|
-
storage = new MemoryBlockstore()
|
|
20
|
-
keypair = await crypto.Secp256k1Keypair.create()
|
|
21
|
-
repo = await Repo.create(storage, repoDid, keypair)
|
|
22
|
-
const filled = await util.fillRepo(repo, keypair, 5)
|
|
23
|
-
repo = filled.repo
|
|
24
|
-
repoData = filled.data
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
const getProofs = async (claims: RecordPath[]) => {
|
|
28
|
-
return util.toBuffer(sync.getRecords(storage, repo.cid, claims))
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const doVerify = (proofs: Uint8Array, claims: RecordCidClaim[]) => {
|
|
32
|
-
return sync.verifyProofs(proofs, claims, repoDid, keypair.did())
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const contentsToClaims = async (
|
|
36
|
-
contents: RepoContents,
|
|
37
|
-
): Promise<RecordCidClaim[]> => {
|
|
38
|
-
const claims: RecordCidClaim[] = []
|
|
39
|
-
for (const coll of Object.keys(contents) as NsidString[]) {
|
|
40
|
-
for (const rkey of Object.keys(contents[coll])) {
|
|
41
|
-
claims.push({
|
|
42
|
-
collection: coll,
|
|
43
|
-
rkey: rkey,
|
|
44
|
-
cid: await cidForLex(contents[coll][rkey]),
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return claims
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
it('verifies valid records', async () => {
|
|
52
|
-
const claims = await contentsToClaims(repoData)
|
|
53
|
-
const proofs = await getProofs(claims)
|
|
54
|
-
const results = await doVerify(proofs, claims)
|
|
55
|
-
expect(results.verified.length).toBeGreaterThan(0)
|
|
56
|
-
expect(results.verified).toEqual(claims)
|
|
57
|
-
expect(results.unverified.length).toBe(0)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('verifies record nonexistence', async () => {
|
|
61
|
-
const claims: RecordCidClaim[] = [
|
|
62
|
-
{
|
|
63
|
-
collection: util.testCollections[0],
|
|
64
|
-
rkey: TID.nextStr(), // does not exist
|
|
65
|
-
cid: null,
|
|
66
|
-
},
|
|
67
|
-
]
|
|
68
|
-
const proofs = await getProofs(claims)
|
|
69
|
-
const results = await doVerify(proofs, claims)
|
|
70
|
-
expect(results.verified.length).toBeGreaterThan(0)
|
|
71
|
-
expect(results.verified).toEqual(claims)
|
|
72
|
-
expect(results.unverified.length).toBe(0)
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('does not verify a record that doesnt exist', async () => {
|
|
76
|
-
const realClaims = await contentsToClaims(repoData)
|
|
77
|
-
const claims: RecordCidClaim[] = [
|
|
78
|
-
{
|
|
79
|
-
...realClaims[0],
|
|
80
|
-
rkey: TID.nextStr(),
|
|
81
|
-
},
|
|
82
|
-
]
|
|
83
|
-
const proofs = await getProofs(claims)
|
|
84
|
-
const results = await doVerify(proofs, claims)
|
|
85
|
-
expect(results.verified.length).toBe(0)
|
|
86
|
-
expect(results.unverified.length).toBeGreaterThan(0)
|
|
87
|
-
expect(results.unverified).toEqual(claims)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('does not verify an invalid record at a real path', async () => {
|
|
91
|
-
const realClaims = await contentsToClaims(repoData)
|
|
92
|
-
const claims: RecordCidClaim[] = [
|
|
93
|
-
{
|
|
94
|
-
...realClaims[0],
|
|
95
|
-
cid: await util.randomCid(),
|
|
96
|
-
},
|
|
97
|
-
]
|
|
98
|
-
const proofs = await getProofs(claims)
|
|
99
|
-
const results = await doVerify(proofs, claims)
|
|
100
|
-
expect(results.verified.length).toBe(0)
|
|
101
|
-
expect(results.unverified.length).toBeGreaterThan(0)
|
|
102
|
-
expect(results.unverified).toEqual(claims)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it('does not verify a delete where the record does exist', async () => {
|
|
106
|
-
const realClaims = await contentsToClaims(repoData)
|
|
107
|
-
const claims: RecordCidClaim[] = [
|
|
108
|
-
{
|
|
109
|
-
collection: realClaims[0].collection,
|
|
110
|
-
rkey: realClaims[0].rkey,
|
|
111
|
-
cid: null,
|
|
112
|
-
},
|
|
113
|
-
]
|
|
114
|
-
const proofs = await getProofs(claims)
|
|
115
|
-
const results = await doVerify(proofs, claims)
|
|
116
|
-
expect(results.verified.length).toBe(0)
|
|
117
|
-
expect(results.unverified.length).toBeGreaterThan(0)
|
|
118
|
-
expect(results.unverified).toEqual(claims)
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
it('can determine record proofs from car file', async () => {
|
|
122
|
-
const possible = await contentsToClaims(repoData)
|
|
123
|
-
const claims = [
|
|
124
|
-
//random sampling of records
|
|
125
|
-
possible[0],
|
|
126
|
-
possible[4],
|
|
127
|
-
possible[5],
|
|
128
|
-
possible[8],
|
|
129
|
-
]
|
|
130
|
-
const proofs = await getProofs(claims)
|
|
131
|
-
const records = await sync.verifyRecords(proofs, repoDid, keypair.did())
|
|
132
|
-
for (const record of records) {
|
|
133
|
-
const foundClaim = claims.find(
|
|
134
|
-
(claim) =>
|
|
135
|
-
claim.collection === record.collection && claim.rkey === record.rkey,
|
|
136
|
-
)
|
|
137
|
-
if (!foundClaim) {
|
|
138
|
-
throw new Error('Could not find record for claim')
|
|
139
|
-
}
|
|
140
|
-
expect(foundClaim.cid).toEqual(
|
|
141
|
-
await cidForLex(repoData[record.collection][record.rkey]),
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('verifyProofs throws on a bad signature', async () => {
|
|
147
|
-
const badRepo = await util.addBadCommit(repo, keypair)
|
|
148
|
-
const claims = await contentsToClaims(repoData)
|
|
149
|
-
const proofs = await util.toBuffer(
|
|
150
|
-
sync.getRecords(storage, badRepo.cid, claims),
|
|
151
|
-
)
|
|
152
|
-
const fn = sync.verifyProofs(proofs, claims, repoDid, keypair.did())
|
|
153
|
-
await expect(fn).rejects.toThrow(sync.RepoVerificationError)
|
|
154
|
-
})
|
|
155
|
-
})
|