@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.
- package/bench/mst.bench.ts +7 -4
- package/bench/repo.bench.ts +25 -16
- package/dist/block-map.d.ts +27 -0
- package/dist/data-diff.d.ts +36 -0
- package/dist/error.d.ts +20 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +22870 -12456
- package/dist/index.js.map +4 -4
- package/dist/mst/diff.d.ts +4 -33
- package/dist/mst/mst.d.ts +73 -31
- package/dist/mst/util.d.ts +13 -5
- package/dist/parse.d.ts +16 -0
- package/dist/readable-repo.d.ts +23 -0
- package/dist/repo.d.ts +19 -31
- package/dist/src/block-map.d.ts +23 -0
- package/dist/src/blockstore/persistent-blockstore.d.ts +12 -0
- package/dist/src/cid-set.d.ts +14 -0
- package/dist/src/collection.d.ts +22 -0
- package/dist/src/data-diff.d.ts +34 -0
- package/dist/src/error.d.ts +21 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/logger.d.ts +2 -0
- package/dist/src/mst/diff.d.ts +33 -0
- package/dist/src/mst/index.d.ts +4 -0
- package/dist/src/mst/mst.d.ts +106 -0
- package/dist/src/mst/util.d.ts +9 -0
- package/dist/src/mst/walker.d.ts +22 -0
- package/dist/src/parse.d.ts +11 -0
- package/dist/src/readable-repo.d.ts +25 -0
- package/dist/src/repo.d.ts +39 -0
- package/dist/src/storage/error.d.ts +22 -0
- package/dist/src/storage/index.d.ts +1 -0
- package/dist/src/storage/memory-blobstore.d.ts +1 -0
- package/dist/src/storage/memory-blockstore.d.ts +28 -0
- package/dist/src/storage/readable-blockstore.d.ts +21 -0
- package/dist/src/storage/repo-storage.d.ts +18 -0
- package/dist/src/storage/sync-storage.d.ts +15 -0
- package/dist/src/storage/types.d.ts +12 -0
- package/dist/src/storage/util.d.ts +17 -0
- package/dist/src/structure.d.ts +39 -0
- package/dist/src/sync/consumer.d.ts +19 -0
- package/dist/src/sync/index.d.ts +2 -0
- package/dist/src/sync/producer.d.ts +13 -0
- package/dist/src/sync/provider.d.ts +11 -0
- package/dist/src/types.d.ts +368 -0
- package/dist/src/util.d.ts +13 -0
- package/dist/src/verify.d.ts +5 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/memory-blockstore.d.ts +29 -0
- package/dist/storage/readable-blockstore.d.ts +24 -0
- package/dist/storage/repo-storage.d.ts +19 -0
- package/dist/storage/sync-storage.d.ts +15 -0
- package/dist/storage/types.d.ts +4 -0
- package/dist/sync/consumer.d.ts +19 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/provider.d.ts +9 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/types.d.ts +137 -331
- package/dist/util.d.ts +35 -12
- package/dist/verify.d.ts +31 -4
- package/jest.bench.config.js +2 -1
- package/package.json +13 -6
- package/src/block-map.ts +103 -0
- package/src/cid-set.ts +1 -2
- package/src/data-diff.ts +117 -0
- package/src/error.ts +31 -0
- package/src/index.ts +4 -1
- package/src/mst/diff.ts +120 -90
- package/src/mst/mst.ts +179 -187
- package/src/mst/util.ts +54 -31
- package/src/parse.ts +44 -0
- package/src/readable-repo.ts +75 -0
- package/src/repo.ts +145 -244
- package/src/storage/index.ts +4 -0
- package/src/storage/memory-blockstore.ts +133 -0
- package/src/storage/readable-blockstore.ts +56 -0
- package/src/storage/repo-storage.ts +43 -0
- package/src/storage/sync-storage.ts +35 -0
- package/src/storage/types.ts +4 -0
- package/src/sync/consumer.ts +140 -0
- package/src/sync/index.ts +2 -0
- package/src/sync/provider.ts +91 -0
- package/src/types.ts +110 -73
- package/src/util.ts +258 -56
- package/src/verify.ts +248 -42
- package/tests/_util.ts +132 -97
- package/tests/mst.test.ts +269 -122
- package/tests/rebase.test.ts +37 -0
- package/tests/repo.test.ts +48 -50
- package/tests/sync/checkout.test.ts +75 -0
- package/tests/sync/diff.test.ts +92 -0
- package/tests/sync/narrow.test.ts +149 -0
- package/tests/util.test.ts +21 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.json +2 -1
- package/src/blockstore/index.ts +0 -2
- package/src/blockstore/ipld-store.ts +0 -103
- package/src/blockstore/memory-blockstore.ts +0 -49
- package/src/sync.ts +0 -38
- package/tests/sync.test.ts +0 -129
- /package/dist/{blockstore → src/blockstore}/index.d.ts +0 -0
- /package/dist/{blockstore → src/blockstore}/ipld-store.d.ts +0 -0
- /package/dist/{blockstore → src/blockstore}/memory-blockstore.d.ts +0 -0
- /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
|
|
3
|
-
import
|
|
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
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
142
|
+
throw new RepoVerificationError('Could not find shared history')
|
|
18
143
|
}
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 (
|
|
27
|
-
throw new
|
|
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
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
249
|
+
}
|
|
250
|
+
const mst = MST.load(blockstore, commit.data)
|
|
57
251
|
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
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
|
|
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 {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
29
|
+
return block.cid
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
export const
|
|
32
|
+
export const generateBulkDataKeys = async (
|
|
29
33
|
count: number,
|
|
30
|
-
blockstore
|
|
34
|
+
blockstore?: RepoStorage,
|
|
31
35
|
): Promise<IdMapping> => {
|
|
32
|
-
const ids = generateBulkTids(count)
|
|
33
36
|
const obj: IdMapping = {}
|
|
34
|
-
for (
|
|
35
|
-
|
|
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
|
-
|
|
85
|
+
keypair: crypto.Keypair,
|
|
85
86
|
itemsPerCollection: number,
|
|
86
|
-
): Promise<{ repo: Repo; data:
|
|
87
|
-
const 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:
|
|
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:
|
|
97
|
+
action: WriteOpAction.Create,
|
|
97
98
|
collection: collName,
|
|
98
99
|
rkey,
|
|
99
|
-
|
|
100
|
+
record: object,
|
|
100
101
|
})
|
|
101
102
|
}
|
|
102
103
|
repoData[collName] = collData
|
|
103
104
|
}
|
|
104
|
-
const updated = await repo.
|
|
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:
|
|
114
|
-
|
|
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:
|
|
121
|
+
): Promise<{ repo: Repo; data: RepoContents }> => {
|
|
121
122
|
const { adds = 0, updates = 0, deletes = 0 } = params
|
|
122
|
-
const 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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
|
175
|
+
repo,
|
|
168
176
|
data: repoData,
|
|
169
177
|
}
|
|
170
178
|
}
|
|
171
179
|
|
|
172
|
-
export const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
188
|
-
|
|
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
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
218
|
-
|
|
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
|