@atproto/repo 0.0.1 → 0.2.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 (104) hide show
  1. package/bench/mst.bench.ts +7 -4
  2. package/bench/repo.bench.ts +25 -16
  3. package/dist/block-map.d.ts +27 -0
  4. package/dist/data-diff.d.ts +36 -0
  5. package/dist/error.d.ts +20 -0
  6. package/dist/index.d.ts +4 -1
  7. package/dist/index.js +22870 -12456
  8. package/dist/index.js.map +4 -4
  9. package/dist/mst/diff.d.ts +4 -33
  10. package/dist/mst/mst.d.ts +73 -31
  11. package/dist/mst/util.d.ts +13 -5
  12. package/dist/parse.d.ts +16 -0
  13. package/dist/readable-repo.d.ts +23 -0
  14. package/dist/repo.d.ts +19 -31
  15. package/dist/src/block-map.d.ts +23 -0
  16. package/dist/src/blockstore/persistent-blockstore.d.ts +12 -0
  17. package/dist/src/cid-set.d.ts +14 -0
  18. package/dist/src/collection.d.ts +22 -0
  19. package/dist/src/data-diff.d.ts +34 -0
  20. package/dist/src/error.d.ts +21 -0
  21. package/dist/src/index.d.ts +7 -0
  22. package/dist/src/logger.d.ts +2 -0
  23. package/dist/src/mst/diff.d.ts +33 -0
  24. package/dist/src/mst/index.d.ts +4 -0
  25. package/dist/src/mst/mst.d.ts +106 -0
  26. package/dist/src/mst/util.d.ts +9 -0
  27. package/dist/src/mst/walker.d.ts +22 -0
  28. package/dist/src/parse.d.ts +11 -0
  29. package/dist/src/readable-repo.d.ts +25 -0
  30. package/dist/src/repo.d.ts +39 -0
  31. package/dist/src/storage/error.d.ts +22 -0
  32. package/dist/src/storage/index.d.ts +1 -0
  33. package/dist/src/storage/memory-blobstore.d.ts +1 -0
  34. package/dist/src/storage/memory-blockstore.d.ts +28 -0
  35. package/dist/src/storage/readable-blockstore.d.ts +21 -0
  36. package/dist/src/storage/repo-storage.d.ts +18 -0
  37. package/dist/src/storage/sync-storage.d.ts +15 -0
  38. package/dist/src/storage/types.d.ts +12 -0
  39. package/dist/src/storage/util.d.ts +17 -0
  40. package/dist/src/structure.d.ts +39 -0
  41. package/dist/src/sync/consumer.d.ts +19 -0
  42. package/dist/src/sync/index.d.ts +2 -0
  43. package/dist/src/sync/producer.d.ts +13 -0
  44. package/dist/src/sync/provider.d.ts +11 -0
  45. package/dist/src/types.d.ts +368 -0
  46. package/dist/src/util.d.ts +13 -0
  47. package/dist/src/verify.d.ts +5 -0
  48. package/dist/storage/index.d.ts +4 -0
  49. package/dist/storage/memory-blockstore.d.ts +29 -0
  50. package/dist/storage/readable-blockstore.d.ts +24 -0
  51. package/dist/storage/repo-storage.d.ts +19 -0
  52. package/dist/storage/sync-storage.d.ts +15 -0
  53. package/dist/storage/types.d.ts +4 -0
  54. package/dist/sync/consumer.d.ts +19 -0
  55. package/dist/sync/index.d.ts +2 -0
  56. package/dist/sync/provider.d.ts +9 -0
  57. package/dist/tsconfig.build.tsbuildinfo +1 -0
  58. package/dist/types.d.ts +137 -331
  59. package/dist/util.d.ts +35 -12
  60. package/dist/verify.d.ts +31 -4
  61. package/jest.bench.config.js +2 -1
  62. package/package.json +13 -6
  63. package/src/block-map.ts +103 -0
  64. package/src/cid-set.ts +1 -2
  65. package/src/data-diff.ts +117 -0
  66. package/src/error.ts +31 -0
  67. package/src/index.ts +4 -1
  68. package/src/mst/diff.ts +120 -90
  69. package/src/mst/mst.ts +179 -187
  70. package/src/mst/util.ts +54 -31
  71. package/src/parse.ts +44 -0
  72. package/src/readable-repo.ts +75 -0
  73. package/src/repo.ts +145 -244
  74. package/src/storage/index.ts +4 -0
  75. package/src/storage/memory-blockstore.ts +133 -0
  76. package/src/storage/readable-blockstore.ts +56 -0
  77. package/src/storage/repo-storage.ts +43 -0
  78. package/src/storage/sync-storage.ts +35 -0
  79. package/src/storage/types.ts +4 -0
  80. package/src/sync/consumer.ts +140 -0
  81. package/src/sync/index.ts +2 -0
  82. package/src/sync/provider.ts +91 -0
  83. package/src/types.ts +110 -73
  84. package/src/util.ts +258 -56
  85. package/src/verify.ts +248 -42
  86. package/tests/_util.ts +132 -97
  87. package/tests/mst.test.ts +269 -122
  88. package/tests/rebase.test.ts +37 -0
  89. package/tests/repo.test.ts +48 -50
  90. package/tests/sync/checkout.test.ts +75 -0
  91. package/tests/sync/diff.test.ts +92 -0
  92. package/tests/sync/narrow.test.ts +149 -0
  93. package/tests/util.test.ts +21 -0
  94. package/tsconfig.build.tsbuildinfo +1 -1
  95. package/tsconfig.json +2 -1
  96. package/src/blockstore/index.ts +0 -2
  97. package/src/blockstore/ipld-store.ts +0 -103
  98. package/src/blockstore/memory-blockstore.ts +0 -49
  99. package/src/sync.ts +0 -38
  100. package/tests/sync.test.ts +0 -129
  101. /package/dist/{blockstore → src/blockstore}/index.d.ts +0 -0
  102. /package/dist/{blockstore → src/blockstore}/ipld-store.d.ts +0 -0
  103. /package/dist/{blockstore → src/blockstore}/memory-blockstore.d.ts +0 -0
  104. /package/dist/{sync.d.ts → src/sync.d.ts} +0 -0
package/src/verify.ts CHANGED
@@ -1,62 +1,268 @@
1
1
  import { CID } from 'multiformats/cid'
2
- import * as auth from '@atproto/auth'
3
- import { IpldStore } from './blockstore'
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'
4
6
  import Repo from './repo'
5
- import { DataDiff } from './mst'
7
+ import CidSet from './cid-set'
6
8
  import * as util from './util'
9
+ import { RecordClaim, RepoContents, RepoContentsWithCids } from './types'
7
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
+ }
8
132
 
9
133
  export const verifyUpdates = async (
10
- blockstore: IpldStore,
11
- earliest: CID | null,
12
- latest: CID,
13
- verifier: auth.Verifier,
14
- ): Promise<DataDiff> => {
15
- const commitPath = await util.getCommitPath(blockstore, earliest, latest)
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)
16
141
  if (commitPath === null) {
17
- throw new Error('Could not find shared history')
142
+ throw new RepoVerificationError('Could not find shared history')
18
143
  }
19
- const fullDiff = new DataDiff()
20
- if (commitPath.length === 0) return fullDiff
21
- let prevRepo = await Repo.load(blockstore, commitPath[0])
22
- for (const commit of commitPath.slice(1)) {
23
- const nextRepo = await Repo.load(blockstore, commit)
24
- const diff = await prevRepo.data.diff(nextRepo.data)
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)
25
161
 
26
- if (!nextRepo.root.meta.equals(prevRepo.root.meta)) {
27
- throw new Error('Not supported: repo metadata updated')
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')
28
167
  }
29
168
 
30
- let didForSignature: string
31
- if (nextRepo.root.auth_token) {
32
- // verify auth token covers all necessary writes
33
- const encodedToken = await blockstore.get(
34
- nextRepo.root.auth_token,
35
- def.string,
169
+ const validSig = await util.verifyCommitSig(nextRepo.commit, signingKey)
170
+ if (!validSig) {
171
+ throw new RepoVerificationError(
172
+ `Invalid signature on commit: ${nextRepo.cid.toString()}`,
36
173
  )
37
- const token = await verifier.validateUcan(encodedToken)
38
- const neededCaps = diff.neededCapabilities(prevRepo.did)
39
- for (const cap of neededCaps) {
40
- await verifier.verifyAtpUcan(token, prevRepo.did, cap)
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)
41
220
  }
42
- didForSignature = token.payload.iss
43
221
  } else {
44
- didForSignature = prevRepo.did
222
+ const expected = await cidForCbor(claim.record)
223
+ if (expected.equals(found)) {
224
+ verified.push(claim)
225
+ } else {
226
+ unverified.push(claim)
227
+ }
45
228
  }
229
+ }
230
+ return { verified, unverified }
231
+ }
46
232
 
47
- // verify signature matches repo root + auth token
48
- // const commit = await toRepo.getCommit()
49
- const validSig = await verifier.verifySignature(
50
- didForSignature,
51
- nextRepo.commit.root.bytes,
52
- nextRepo.commit.sig,
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()}`,
53
248
  )
54
- if (!validSig) {
55
- throw new Error(`Invalid signature on commit: ${nextRepo.cid.toString()}`)
56
- }
249
+ }
250
+ const mst = MST.load(blockstore, commit.data)
57
251
 
58
- fullDiff.addDiff(diff)
59
- prevRepo = nextRepo
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
+ }
60
264
  }
61
- return fullDiff
265
+ return records
62
266
  }
267
+
268
+ export class RepoVerificationError extends Error {}
package/tests/_util.ts CHANGED
@@ -1,38 +1,42 @@
1
+ import fs from 'fs'
1
2
  import { CID } from 'multiformats'
2
- import { TID } from '@atproto/common'
3
- import * as auth from '@atproto/auth'
4
- import IpldStore from '../src/blockstore/ipld-store'
3
+ import { TID, dataToCborBlock } from '@atproto/common'
4
+ import * as crypto from '@atproto/crypto'
5
5
  import { Repo } from '../src/repo'
6
- import { MemoryBlockstore } from '../src/blockstore'
7
- import { DataDiff, MST } from '../src/mst'
8
- import fs from 'fs'
9
- import { RecordWriteOp } from '../src'
6
+ import { RepoStorage } from '../src/storage'
7
+ import { MST } from '../src/mst'
8
+ import {
9
+ BlockMap,
10
+ collapseWriteLog,
11
+ CollectionContents,
12
+ RecordWriteOp,
13
+ RepoContents,
14
+ RecordPath,
15
+ WriteLog,
16
+ WriteOpAction,
17
+ RecordClaim,
18
+ Commit,
19
+ } from '../src'
20
+ import { Keypair, randomBytes } from '@atproto/crypto'
10
21
 
11
22
  type IdMapping = Record<string, CID>
12
23
 
13
- const fakeStore = new MemoryBlockstore()
14
-
15
- export const randomCid = async (store: IpldStore = fakeStore): Promise<CID> => {
16
- const str = randomStr(50)
17
- return store.stage({ test: str })
18
- }
19
-
20
- export const generateBulkTids = (count: number): TID[] => {
21
- const ids: TID[] = []
22
- for (let i = 0; i < count; i++) {
23
- ids.push(TID.next())
24
+ export const randomCid = async (storage?: RepoStorage): Promise<CID> => {
25
+ const block = await dataToCborBlock({ test: randomStr(50) })
26
+ if (storage) {
27
+ await storage.putBlock(block.cid, block.bytes)
24
28
  }
25
- return ids
29
+ return block.cid
26
30
  }
27
31
 
28
- export const generateBulkTidMapping = async (
32
+ export const generateBulkDataKeys = async (
29
33
  count: number,
30
- blockstore: IpldStore = fakeStore,
34
+ blockstore?: RepoStorage,
31
35
  ): Promise<IdMapping> => {
32
- const ids = generateBulkTids(count)
33
36
  const obj: IdMapping = {}
34
- for (const id of ids) {
35
- obj[id.toString()] = await randomCid(blockstore)
37
+ for (let i = 0; i < count; i++) {
38
+ const key = `com.example.record/${TID.nextStr()}`
39
+ obj[key] = await randomCid(blockstore)
36
40
  }
37
41
  return obj
38
42
  }
@@ -76,32 +80,29 @@ export const generateObject = (): Record<string, string> => {
76
80
 
77
81
  export const testCollections = ['com.example.posts', 'com.example.likes']
78
82
 
79
- export type CollectionData = Record<string, unknown>
80
- export type RepoData = Record<string, CollectionData>
81
-
82
83
  export const fillRepo = async (
83
84
  repo: Repo,
84
- authStore: auth.AuthStore,
85
+ keypair: crypto.Keypair,
85
86
  itemsPerCollection: number,
86
- ): Promise<{ repo: Repo; data: RepoData }> => {
87
- const repoData: RepoData = {}
87
+ ): Promise<{ repo: Repo; data: RepoContents }> => {
88
+ const repoData: RepoContents = {}
88
89
  const writes: RecordWriteOp[] = []
89
90
  for (const collName of testCollections) {
90
- const collData: CollectionData = {}
91
+ const collData: CollectionContents = {}
91
92
  for (let i = 0; i < itemsPerCollection; i++) {
92
93
  const object = generateObject()
93
94
  const rkey = TID.nextStr()
94
95
  collData[rkey] = object
95
96
  writes.push({
96
- action: 'create',
97
+ action: WriteOpAction.Create,
97
98
  collection: collName,
98
99
  rkey,
99
- value: object,
100
+ record: object,
100
101
  })
101
102
  }
102
103
  repoData[collName] = collData
103
104
  }
104
- const updated = await repo.stageUpdate(writes).createCommit(authStore)
105
+ const updated = await repo.applyWrites(writes, keypair)
105
106
  return {
106
107
  repo: updated,
107
108
  data: repoData,
@@ -110,17 +111,16 @@ export const fillRepo = async (
110
111
 
111
112
  export const editRepo = async (
112
113
  repo: Repo,
113
- prevData: RepoData,
114
- authStore: auth.AuthStore,
114
+ prevData: RepoContents,
115
+ keypair: crypto.Keypair,
115
116
  params: {
116
117
  adds?: number
117
118
  updates?: number
118
119
  deletes?: number
119
120
  },
120
- ): Promise<{ repo: Repo; data: RepoData }> => {
121
+ ): Promise<{ repo: Repo; data: RepoContents }> => {
121
122
  const { adds = 0, updates = 0, deletes = 0 } = params
122
- const repoData: RepoData = {}
123
- const writes: RecordWriteOp[] = []
123
+ const repoData: RepoContents = {}
124
124
  for (const collName of testCollections) {
125
125
  const collData = prevData[collName]
126
126
  const shuffled = shuffle(Object.entries(collData))
@@ -129,94 +129,129 @@ export const editRepo = async (
129
129
  const object = generateObject()
130
130
  const rkey = TID.nextStr()
131
131
  collData[rkey] = object
132
- writes.push({
133
- action: 'create',
134
- collection: collName,
135
- rkey,
136
- value: object,
137
- })
132
+ repo = await repo.applyWrites(
133
+ {
134
+ action: WriteOpAction.Create,
135
+ collection: collName,
136
+ rkey,
137
+ record: object,
138
+ },
139
+ keypair,
140
+ )
138
141
  }
139
142
 
140
143
  const toUpdate = shuffled.slice(0, updates)
141
144
  for (let i = 0; i < toUpdate.length; i++) {
142
145
  const object = generateObject()
143
146
  const rkey = toUpdate[i][0]
144
- writes.push({
145
- action: 'update',
146
- collection: collName,
147
- rkey,
148
- value: object,
149
- })
147
+ repo = await repo.applyWrites(
148
+ {
149
+ action: WriteOpAction.Update,
150
+ collection: collName,
151
+ rkey,
152
+ record: object,
153
+ },
154
+ keypair,
155
+ )
150
156
  collData[rkey] = object
151
157
  }
152
158
 
153
159
  const toDelete = shuffled.slice(updates, deletes)
154
160
  for (let i = 0; i < toDelete.length; i++) {
155
161
  const rkey = toDelete[i][0]
156
- writes.push({
157
- action: 'delete',
158
- collection: collName,
159
- rkey,
160
- })
162
+ repo = await repo.applyWrites(
163
+ {
164
+ action: WriteOpAction.Delete,
165
+ collection: collName,
166
+ rkey,
167
+ },
168
+ keypair,
169
+ )
161
170
  delete collData[rkey]
162
171
  }
163
172
  repoData[collName] = collData
164
173
  }
165
- const updated = await repo.stageUpdate(writes).createCommit(authStore)
166
174
  return {
167
- repo: updated,
175
+ repo,
168
176
  data: repoData,
169
177
  }
170
178
  }
171
179
 
172
- export const checkRepo = async (repo: Repo, data: RepoData): Promise<void> => {
173
- for (const collName of Object.keys(data)) {
174
- const collData = data[collName]
175
- for (const rkey of Object.keys(collData)) {
176
- const record = await repo.getRecord(collName, rkey)
177
- expect(record).toEqual(collData[rkey])
178
- }
179
- }
180
- }
181
-
182
- export const checkRepoDiff = async (
183
- diff: DataDiff,
184
- before: RepoData,
185
- after: RepoData,
180
+ export const verifyRepoDiff = async (
181
+ writeLog: WriteLog,
182
+ before: RepoContents,
183
+ after: RepoContents,
186
184
  ): Promise<void> => {
187
- const getObjectCid = async (
188
- key: string,
189
- data: RepoData,
190
- ): Promise<CID | undefined> => {
191
- const parts = key.split('/')
192
- const collection = parts[0]
193
- const obj = (data[collection] || {})[parts[1]]
194
- return obj === undefined ? undefined : fakeStore.stage(obj)
185
+ const getVal = (op: RecordWriteOp, data: RepoContents) => {
186
+ return (data[op.collection] || {})[op.rkey]
195
187
  }
188
+ const ops = await collapseWriteLog(writeLog)
196
189
 
197
- for (const add of diff.addList()) {
198
- const beforeCid = await getObjectCid(add.key, before)
199
- const afterCid = await getObjectCid(add.key, after)
200
-
201
- expect(beforeCid).toBeUndefined()
202
- expect(afterCid).toEqual(add.cid)
190
+ for (const op of ops) {
191
+ if (op.action === WriteOpAction.Create) {
192
+ expect(getVal(op, before)).toBeUndefined()
193
+ expect(getVal(op, after)).toEqual(op.record)
194
+ } else if (op.action === WriteOpAction.Update) {
195
+ expect(getVal(op, before)).toBeDefined()
196
+ expect(getVal(op, after)).toEqual(op.record)
197
+ } else if (op.action === WriteOpAction.Delete) {
198
+ expect(getVal(op, before)).toBeDefined()
199
+ expect(getVal(op, after)).toBeUndefined()
200
+ } else {
201
+ throw new Error('unexpected op type')
202
+ }
203
203
  }
204
+ }
204
205
 
205
- for (const update of diff.updateList()) {
206
- const beforeCid = await getObjectCid(update.key, before)
207
- const afterCid = await getObjectCid(update.key, after)
208
-
209
- expect(beforeCid).toEqual(update.prev)
210
- expect(afterCid).toEqual(update.cid)
206
+ export const contentsToClaims = (contents: RepoContents): RecordClaim[] => {
207
+ const claims: RecordClaim[] = []
208
+ for (const coll of Object.keys(contents)) {
209
+ for (const rkey of Object.keys(contents[coll])) {
210
+ claims.push({
211
+ collection: coll,
212
+ rkey: rkey,
213
+ record: contents[coll][rkey],
214
+ })
215
+ }
211
216
  }
217
+ return claims
218
+ }
219
+
220
+ export const pathsForOps = (ops: RecordWriteOp[]): RecordPath[] =>
221
+ ops.map((op) => ({ collection: op.collection, rkey: op.rkey }))
212
222
 
213
- for (const del of diff.deleteList()) {
214
- const beforeCid = await getObjectCid(del.key, before)
215
- const afterCid = await getObjectCid(del.key, after)
223
+ export const saveMst = async (storage: RepoStorage, mst: MST): Promise<CID> => {
224
+ const diff = await mst.getUnstoredBlocks()
225
+ await storage.putMany(diff.blocks)
226
+ return diff.root
227
+ }
216
228
 
217
- expect(beforeCid).toEqual(del.cid)
218
- expect(afterCid).toBeUndefined()
229
+ // Creating repo
230
+ // -------------------
231
+ export const addBadCommit = async (
232
+ repo: Repo,
233
+ keypair: Keypair,
234
+ ): Promise<Repo> => {
235
+ const obj = generateObject()
236
+ const blocks = new BlockMap()
237
+ const cid = await blocks.add(obj)
238
+ const updatedData = await repo.data.add(`com.example.test/${TID.next()}`, cid)
239
+ const unstoredData = await updatedData.getUnstoredBlocks()
240
+ blocks.addMap(unstoredData.blocks)
241
+ // we generate a bad sig by signing some other data
242
+ const commit: Commit = {
243
+ ...repo.commit,
244
+ prev: repo.cid,
245
+ data: unstoredData.root,
246
+ sig: await keypair.sign(randomBytes(256)),
219
247
  }
248
+ const commitCid = await blocks.add(commit)
249
+ await repo.storage.applyCommit({
250
+ commit: commitCid,
251
+ prev: repo.cid,
252
+ blocks: blocks,
253
+ })
254
+ return await Repo.load(repo.storage, commitCid)
220
255
  }
221
256
 
222
257
  // Logging