@atproto/repo 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/data-diff.d.ts +11 -9
  2. package/dist/index.d.ts +0 -1
  3. package/dist/index.js +404 -1655
  4. package/dist/index.js.map +4 -4
  5. package/dist/mst/mst.d.ts +10 -6
  6. package/dist/readable-repo.d.ts +1 -1
  7. package/dist/repo.d.ts +1 -4
  8. package/dist/storage/index.d.ts +0 -1
  9. package/dist/storage/memory-blockstore.d.ts +7 -12
  10. package/dist/storage/types.d.ts +28 -0
  11. package/dist/sync/consumer.d.ts +13 -17
  12. package/dist/sync/provider.d.ts +1 -5
  13. package/dist/types.d.ts +228 -39
  14. package/dist/util.d.ts +3 -5
  15. package/package.json +2 -2
  16. package/src/data-diff.ts +46 -44
  17. package/src/index.ts +0 -1
  18. package/src/mst/diff.ts +14 -36
  19. package/src/mst/mst.ts +14 -4
  20. package/src/readable-repo.ts +3 -3
  21. package/src/repo.ts +49 -70
  22. package/src/storage/index.ts +0 -1
  23. package/src/storage/memory-blockstore.ts +18 -77
  24. package/src/storage/types.ts +29 -0
  25. package/src/sync/consumer.ts +170 -116
  26. package/src/sync/provider.ts +2 -40
  27. package/src/types.ts +49 -23
  28. package/src/util.ts +24 -79
  29. package/tests/_util.ts +38 -67
  30. package/tests/mst.test.ts +4 -1
  31. package/tests/{sync/narrow.test.ts → proofs.test.ts} +9 -20
  32. package/tests/repo.test.ts +5 -4
  33. package/tests/sync.test.ts +97 -0
  34. package/tsconfig.build.tsbuildinfo +1 -1
  35. package/dist/src/block-map.d.ts +0 -23
  36. package/dist/src/blockstore/index.d.ts +0 -2
  37. package/dist/src/blockstore/ipld-store.d.ts +0 -27
  38. package/dist/src/blockstore/memory-blockstore.d.ts +0 -13
  39. package/dist/src/blockstore/persistent-blockstore.d.ts +0 -12
  40. package/dist/src/cid-set.d.ts +0 -14
  41. package/dist/src/collection.d.ts +0 -22
  42. package/dist/src/data-diff.d.ts +0 -34
  43. package/dist/src/error.d.ts +0 -21
  44. package/dist/src/index.d.ts +0 -7
  45. package/dist/src/logger.d.ts +0 -2
  46. package/dist/src/mst/diff.d.ts +0 -33
  47. package/dist/src/mst/index.d.ts +0 -4
  48. package/dist/src/mst/mst.d.ts +0 -106
  49. package/dist/src/mst/util.d.ts +0 -9
  50. package/dist/src/mst/walker.d.ts +0 -22
  51. package/dist/src/parse.d.ts +0 -11
  52. package/dist/src/readable-repo.d.ts +0 -25
  53. package/dist/src/repo.d.ts +0 -39
  54. package/dist/src/storage/error.d.ts +0 -22
  55. package/dist/src/storage/index.d.ts +0 -1
  56. package/dist/src/storage/memory-blobstore.d.ts +0 -1
  57. package/dist/src/storage/memory-blockstore.d.ts +0 -28
  58. package/dist/src/storage/readable-blockstore.d.ts +0 -21
  59. package/dist/src/storage/repo-storage.d.ts +0 -18
  60. package/dist/src/storage/sync-storage.d.ts +0 -15
  61. package/dist/src/storage/types.d.ts +0 -12
  62. package/dist/src/storage/util.d.ts +0 -17
  63. package/dist/src/structure.d.ts +0 -39
  64. package/dist/src/sync/consumer.d.ts +0 -19
  65. package/dist/src/sync/index.d.ts +0 -2
  66. package/dist/src/sync/producer.d.ts +0 -13
  67. package/dist/src/sync/provider.d.ts +0 -11
  68. package/dist/src/sync.d.ts +0 -9
  69. package/dist/src/types.d.ts +0 -368
  70. package/dist/src/util.d.ts +0 -13
  71. package/dist/src/verify.d.ts +0 -5
  72. package/dist/storage/repo-storage.d.ts +0 -19
  73. package/dist/tsconfig.build.tsbuildinfo +0 -1
  74. package/dist/verify.d.ts +0 -32
  75. package/src/storage/repo-storage.ts +0 -43
  76. package/src/verify.ts +0 -268
  77. package/tests/rebase.test.ts +0 -37
  78. package/tests/sync/checkout.test.ts +0 -75
  79. package/tests/sync/diff.test.ts +0 -92
package/src/verify.ts DELETED
@@ -1,268 +0,0 @@
1
- import { CID } from 'multiformats/cid'
2
- import { MemoryBlockstore, ReadableBlockstore, RepoStorage } from './storage'
3
- import DataDiff from './data-diff'
4
- import SyncStorage from './storage/sync-storage'
5
- import ReadableRepo from './readable-repo'
6
- import Repo from './repo'
7
- import CidSet from './cid-set'
8
- import * as util from './util'
9
- import { RecordClaim, RepoContents, RepoContentsWithCids } from './types'
10
- import { def } from './types'
11
- import { MST } from './mst'
12
- import { cidForCbor } from '@atproto/common'
13
-
14
- export type VerifiedCheckout = {
15
- contents: RepoContents
16
- newCids: CidSet
17
- }
18
-
19
- export type VerifiedCheckoutWithCids = {
20
- commit: CID
21
- contents: RepoContentsWithCids
22
- }
23
-
24
- export const verifyCheckout = async (
25
- storage: ReadableBlockstore,
26
- head: CID,
27
- did: string,
28
- signingKey: string,
29
- ): Promise<VerifiedCheckout> => {
30
- const repo = await verifyRepoRoot(storage, head, did, signingKey)
31
- const diff = await DataDiff.of(repo.data, null)
32
- const newCids = new CidSet([repo.cid]).addSet(diff.newCids)
33
-
34
- const contents: RepoContents = {}
35
- for (const add of diff.addList()) {
36
- const { collection, rkey } = util.parseDataKey(add.key)
37
- if (!contents[collection]) {
38
- contents[collection] = {}
39
- }
40
- const record = await storage.readRecord(add.cid)
41
- contents[collection][rkey] = record
42
- }
43
-
44
- return {
45
- contents,
46
- newCids,
47
- }
48
- }
49
-
50
- export const verifyCheckoutWithCids = async (
51
- storage: ReadableBlockstore,
52
- head: CID,
53
- did: string,
54
- signingKey: string,
55
- ): Promise<VerifiedCheckoutWithCids> => {
56
- const repo = await verifyRepoRoot(storage, head, did, signingKey)
57
- const diff = await DataDiff.of(repo.data, null)
58
-
59
- const contents: RepoContentsWithCids = {}
60
- for (const add of diff.addList()) {
61
- const { collection, rkey } = util.parseDataKey(add.key)
62
- contents[collection] ??= {}
63
- contents[collection][rkey] = {
64
- cid: add.cid,
65
- value: await storage.readRecord(add.cid),
66
- }
67
- }
68
-
69
- return {
70
- commit: repo.cid,
71
- contents,
72
- }
73
- }
74
-
75
- // @NOTE only verifies the root, not the repo contents
76
- const verifyRepoRoot = async (
77
- storage: ReadableBlockstore,
78
- head: CID,
79
- did: string,
80
- signingKey: string,
81
- ): Promise<ReadableRepo> => {
82
- const repo = await ReadableRepo.load(storage, head)
83
- if (repo.did !== did) {
84
- throw new RepoVerificationError(`Invalid repo did: ${repo.did}`)
85
- }
86
- const validSig = await util.verifyCommitSig(repo.commit, signingKey)
87
- if (!validSig) {
88
- throw new RepoVerificationError(
89
- `Invalid signature on commit: ${repo.cid.toString()}`,
90
- )
91
- }
92
- return repo
93
- }
94
-
95
- export type VerifiedUpdate = {
96
- commit: CID
97
- prev: CID | null
98
- diff: DataDiff
99
- newCids: CidSet
100
- }
101
-
102
- export const verifyFullHistory = async (
103
- storage: RepoStorage,
104
- head: CID,
105
- did: string,
106
- signingKey: string,
107
- ): Promise<VerifiedUpdate[]> => {
108
- const commitPath = await storage.getCommitPath(head, null)
109
- if (commitPath === null) {
110
- throw new RepoVerificationError('Could not find shared history')
111
- } else if (commitPath.length < 1) {
112
- throw new RepoVerificationError('Expected at least one commit')
113
- }
114
- const baseRepo = await Repo.load(storage, commitPath[0])
115
- const baseDiff = await DataDiff.of(baseRepo.data, null)
116
- const baseRepoCids = new CidSet([baseRepo.cid]).addSet(baseDiff.newCids)
117
- const init: VerifiedUpdate = {
118
- commit: baseRepo.cid,
119
- prev: null,
120
- diff: baseDiff,
121
- newCids: baseRepoCids,
122
- }
123
- const updates = await verifyCommitPath(
124
- baseRepo,
125
- storage,
126
- commitPath.slice(1),
127
- did,
128
- signingKey,
129
- )
130
- return [init, ...updates]
131
- }
132
-
133
- export const verifyUpdates = async (
134
- repo: ReadableRepo,
135
- updateStorage: RepoStorage,
136
- updateRoot: CID,
137
- did: string,
138
- signingKey: string,
139
- ): Promise<VerifiedUpdate[]> => {
140
- const commitPath = await updateStorage.getCommitPath(updateRoot, repo.cid)
141
- if (commitPath === null) {
142
- throw new RepoVerificationError('Could not find shared history')
143
- }
144
- const syncStorage = new SyncStorage(updateStorage, repo.storage)
145
- return verifyCommitPath(repo, syncStorage, commitPath, did, signingKey)
146
- }
147
-
148
- export const verifyCommitPath = async (
149
- baseRepo: ReadableRepo,
150
- storage: ReadableBlockstore,
151
- commitPath: CID[],
152
- did: string,
153
- signingKey: string,
154
- ): Promise<VerifiedUpdate[]> => {
155
- const updates: VerifiedUpdate[] = []
156
- if (commitPath.length === 0) return updates
157
- let prevRepo = baseRepo
158
- for (const commit of commitPath) {
159
- const nextRepo = await ReadableRepo.load(storage, commit)
160
- const diff = await DataDiff.of(nextRepo.data, prevRepo.data)
161
-
162
- if (nextRepo.did !== did) {
163
- throw new RepoVerificationError(`Invalid repo did: ${nextRepo.did}`)
164
- }
165
- if (!util.metaEqual(nextRepo.commit, prevRepo.commit)) {
166
- throw new RepoVerificationError('Not supported: repo metadata updated')
167
- }
168
-
169
- const validSig = await util.verifyCommitSig(nextRepo.commit, signingKey)
170
- if (!validSig) {
171
- throw new RepoVerificationError(
172
- `Invalid signature on commit: ${nextRepo.cid.toString()}`,
173
- )
174
- }
175
-
176
- const newCids = new CidSet([nextRepo.cid]).addSet(diff.newCids)
177
-
178
- updates.push({
179
- commit: nextRepo.cid,
180
- prev: prevRepo.cid,
181
- diff,
182
- newCids,
183
- })
184
- prevRepo = nextRepo
185
- }
186
- return updates
187
- }
188
-
189
- export const verifyProofs = async (
190
- proofs: Uint8Array,
191
- claims: RecordClaim[],
192
- did: string,
193
- didKey: string,
194
- ): Promise<{ verified: RecordClaim[]; unverified: RecordClaim[] }> => {
195
- const car = await util.readCarWithRoot(proofs)
196
- const blockstore = new MemoryBlockstore(car.blocks)
197
- const commit = await blockstore.readObj(car.root, def.commit)
198
- if (commit.did !== did) {
199
- throw new RepoVerificationError(`Invalid repo did: ${commit.did}`)
200
- }
201
- const validSig = await util.verifyCommitSig(commit, didKey)
202
- if (!validSig) {
203
- throw new RepoVerificationError(
204
- `Invalid signature on commit: ${car.root.toString()}`,
205
- )
206
- }
207
- const mst = MST.load(blockstore, commit.data)
208
- const verified: RecordClaim[] = []
209
- const unverified: RecordClaim[] = []
210
- for (const claim of claims) {
211
- const found = await mst.get(
212
- util.formatDataKey(claim.collection, claim.rkey),
213
- )
214
- const record = found ? await blockstore.readObj(found, def.map) : null
215
- if (claim.record === null) {
216
- if (record === null) {
217
- verified.push(claim)
218
- } else {
219
- unverified.push(claim)
220
- }
221
- } else {
222
- const expected = await cidForCbor(claim.record)
223
- if (expected.equals(found)) {
224
- verified.push(claim)
225
- } else {
226
- unverified.push(claim)
227
- }
228
- }
229
- }
230
- return { verified, unverified }
231
- }
232
-
233
- export const verifyRecords = async (
234
- proofs: Uint8Array,
235
- did: string,
236
- signingKey: string,
237
- ): Promise<RecordClaim[]> => {
238
- const car = await util.readCarWithRoot(proofs)
239
- const blockstore = new MemoryBlockstore(car.blocks)
240
- const commit = await blockstore.readObj(car.root, def.commit)
241
- if (commit.did !== did) {
242
- throw new RepoVerificationError(`Invalid repo did: ${commit.did}`)
243
- }
244
- const validSig = await util.verifyCommitSig(commit, signingKey)
245
- if (!validSig) {
246
- throw new RepoVerificationError(
247
- `Invalid signature on commit: ${car.root.toString()}`,
248
- )
249
- }
250
- const mst = MST.load(blockstore, commit.data)
251
-
252
- const records: RecordClaim[] = []
253
- const leaves = await mst.reachableLeaves()
254
- for (const leaf of leaves) {
255
- const { collection, rkey } = util.parseDataKey(leaf.key)
256
- const record = await blockstore.attemptReadRecord(leaf.value)
257
- if (record) {
258
- records.push({
259
- collection,
260
- rkey,
261
- record,
262
- })
263
- }
264
- }
265
- return records
266
- }
267
-
268
- export class RepoVerificationError extends Error {}
@@ -1,37 +0,0 @@
1
- import * as crypto from '@atproto/crypto'
2
- import { Repo } from '../src/repo'
3
- import { MemoryBlockstore } from '../src/storage'
4
- import * as util from './_util'
5
- import { Secp256k1Keypair } from '@atproto/crypto'
6
-
7
- describe('Rebases', () => {
8
- let storage: MemoryBlockstore
9
- let keypair: crypto.Keypair
10
- let repo: Repo
11
-
12
- it('fills a repo with data', async () => {
13
- storage = new MemoryBlockstore()
14
- keypair = await Secp256k1Keypair.create()
15
- repo = await Repo.create(storage, keypair.did(), keypair)
16
- const filled = await util.fillRepo(repo, keypair, 100)
17
- repo = filled.repo
18
- })
19
-
20
- it('rebases the repo & preserves contents', async () => {
21
- const dataCidBefore = await repo.data.getPointer()
22
- const contents = await repo.getContents()
23
- repo = await repo.rebase(keypair)
24
- const rebasedContents = await repo.getContents()
25
- expect(rebasedContents).toEqual(contents)
26
- const dataCidAfter = await repo.data.getPointer()
27
- expect(dataCidAfter.equals(dataCidBefore)).toBeTruthy()
28
- })
29
-
30
- it('only keeps around relevant cids', async () => {
31
- const allCids = await repo.data.allCids()
32
- allCids.add(repo.cid)
33
- for (const cid of storage.blocks.cids()) {
34
- expect(allCids.has(cid)).toBeTruthy()
35
- }
36
- })
37
- })
@@ -1,75 +0,0 @@
1
- import * as crypto from '@atproto/crypto'
2
- import { CidSet, Repo, RepoContents, RepoVerificationError } from '../../src'
3
- import { MemoryBlockstore } from '../../src/storage'
4
- import * as sync from '../../src/sync'
5
-
6
- import * as util from '../_util'
7
- import { streamToBuffer } from '@atproto/common'
8
- import { CarReader } from '@ipld/car/reader'
9
-
10
- describe('Checkout Sync', () => {
11
- let storage: MemoryBlockstore
12
- let syncStorage: MemoryBlockstore
13
- let repo: Repo
14
- let keypair: crypto.Keypair
15
- let repoData: RepoContents
16
-
17
- const repoDid = 'did:example:test'
18
-
19
- beforeAll(async () => {
20
- storage = new MemoryBlockstore()
21
- keypair = await crypto.Secp256k1Keypair.create()
22
- repo = await Repo.create(storage, repoDid, keypair)
23
- syncStorage = new MemoryBlockstore()
24
- const filled = await util.fillRepo(repo, keypair, 20)
25
- repo = filled.repo
26
- repoData = filled.data
27
- })
28
-
29
- it('sync a non-historical repo checkout', async () => {
30
- const checkoutCar = await streamToBuffer(
31
- sync.getCheckout(storage, repo.cid),
32
- )
33
- const checkout = await sync.loadCheckout(
34
- syncStorage,
35
- checkoutCar,
36
- repoDid,
37
- keypair.did(),
38
- )
39
- const checkoutRepo = await Repo.load(syncStorage, checkout.root)
40
- const contents = await checkoutRepo.getContents()
41
- expect(contents).toEqual(repoData)
42
- expect(checkout.contents).toEqual(repoData)
43
- })
44
-
45
- it('does not sync unneeded blocks during checkout', async () => {
46
- const commitPath = await storage.getCommitPath(repo.cid, null)
47
- if (!commitPath) {
48
- throw new Error('Could not get commitPath')
49
- }
50
- const hasGenesisCommit = await syncStorage.has(commitPath[0])
51
- expect(hasGenesisCommit).toBeFalsy()
52
- })
53
-
54
- it('does not sync duplicate blocks', async () => {
55
- const carBytes = await streamToBuffer(sync.getCheckout(storage, repo.cid))
56
- const car = await CarReader.fromBytes(carBytes)
57
- const cids = new CidSet()
58
- for await (const block of car.blocks()) {
59
- if (cids.has(block.cid)) {
60
- throw new Error(`duplicate block: :${block.cid.toString()}`)
61
- }
62
- cids.add(block.cid)
63
- }
64
- })
65
-
66
- it('throws on a bad signature', async () => {
67
- const badRepo = await util.addBadCommit(repo, keypair)
68
- const checkoutCar = await streamToBuffer(
69
- sync.getCheckout(storage, badRepo.cid),
70
- )
71
- await expect(
72
- sync.loadCheckout(syncStorage, checkoutCar, repoDid, keypair.did()),
73
- ).rejects.toThrow(RepoVerificationError)
74
- })
75
- })
@@ -1,92 +0,0 @@
1
- import * as crypto from '@atproto/crypto'
2
- import { Repo, RepoContents } from '../../src'
3
- import { MemoryBlockstore } from '../../src/storage'
4
- import * as sync from '../../src/sync'
5
-
6
- import * as util from '../_util'
7
- import { streamToBuffer } from '@atproto/common'
8
-
9
- describe('Diff Sync', () => {
10
- let storage: MemoryBlockstore
11
- let syncStorage: 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
- syncStorage = new MemoryBlockstore()
23
- })
24
-
25
- let syncRepo: Repo
26
-
27
- it('syncs an empty repo', async () => {
28
- const car = await streamToBuffer(sync.getFullRepo(storage, repo.cid))
29
- const loaded = await sync.loadFullRepo(
30
- syncStorage,
31
- car,
32
- repoDid,
33
- keypair.did(),
34
- )
35
- syncRepo = await Repo.load(syncStorage, loaded.root)
36
- const data = await syncRepo.data.list(10)
37
- expect(data.length).toBe(0)
38
- })
39
-
40
- it('syncs a repo that is starting from scratch', async () => {
41
- const filled = await util.fillRepo(repo, keypair, 100)
42
- repo = filled.repo
43
- repoData = filled.data
44
-
45
- const car = await streamToBuffer(sync.getFullRepo(storage, repo.cid))
46
- const loaded = await sync.loadFullRepo(
47
- syncStorage,
48
- car,
49
- repoDid,
50
- keypair.did(),
51
- )
52
- syncRepo = await Repo.load(syncStorage, loaded.root)
53
- const contents = await syncRepo.getContents()
54
- expect(contents).toEqual(repoData)
55
- await util.verifyRepoDiff(loaded.writeLog, {}, repoData)
56
- })
57
-
58
- it('syncs a repo that is behind', async () => {
59
- // add more to providers's repo & have consumer catch up
60
- const beforeData = structuredClone(repoData)
61
- const edited = await util.editRepo(repo, repoData, keypair, {
62
- adds: 20,
63
- updates: 20,
64
- deletes: 20,
65
- })
66
- repo = edited.repo
67
- repoData = edited.data
68
- const diffCar = await streamToBuffer(
69
- sync.getCommits(storage, repo.cid, syncRepo.cid),
70
- )
71
- const loaded = await sync.loadDiff(
72
- syncRepo,
73
- diffCar,
74
- repoDid,
75
- keypair.did(),
76
- )
77
- syncRepo = await Repo.load(syncStorage, loaded.root)
78
- const contents = await syncRepo.getContents()
79
- expect(contents).toEqual(repoData)
80
- await util.verifyRepoDiff(loaded.writeLog, beforeData, repoData)
81
- })
82
-
83
- it('throws on a bad signature', async () => {
84
- const badRepo = await util.addBadCommit(repo, keypair)
85
- const diffCar = await streamToBuffer(
86
- sync.getCommits(storage, badRepo.cid, syncRepo.cid),
87
- )
88
- await expect(
89
- sync.loadDiff(syncRepo, diffCar, repoDid, keypair.did()),
90
- ).rejects.toThrow('Invalid signature on commit')
91
- })
92
- })