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