@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
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
})
|
|
@@ -0,0 +1,92 @@
|
|
|
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
|
+
})
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { TID, streamToBuffer } from '@atproto/common'
|
|
2
|
+
import * as crypto from '@atproto/crypto'
|
|
3
|
+
import { RecordClaim, Repo, RepoContents } from '../../src'
|
|
4
|
+
import { MemoryBlockstore } from '../../src/storage'
|
|
5
|
+
import * as verify from '../../src/verify'
|
|
6
|
+
import * as sync from '../../src/sync'
|
|
7
|
+
|
|
8
|
+
import * as util from '../_util'
|
|
9
|
+
|
|
10
|
+
describe('Narrow Sync', () => {
|
|
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: RecordClaim[]) => {
|
|
28
|
+
return streamToBuffer(sync.getRecords(storage, repo.cid, claims))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const doVerify = (proofs: Uint8Array, claims: RecordClaim[]) => {
|
|
32
|
+
return verify.verifyProofs(proofs, claims, repoDid, keypair.did())
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
it('verifies valid records', async () => {
|
|
36
|
+
const claims = util.contentsToClaims(repoData)
|
|
37
|
+
const proofs = await getProofs(claims)
|
|
38
|
+
const results = await doVerify(proofs, claims)
|
|
39
|
+
expect(results.verified.length).toBeGreaterThan(0)
|
|
40
|
+
expect(results.verified).toEqual(claims)
|
|
41
|
+
expect(results.unverified.length).toBe(0)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('verifies record nonexistence', async () => {
|
|
45
|
+
const claims: RecordClaim[] = [
|
|
46
|
+
{
|
|
47
|
+
collection: util.testCollections[0],
|
|
48
|
+
rkey: TID.nextStr(), // does not exist
|
|
49
|
+
record: null,
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
const proofs = await getProofs(claims)
|
|
53
|
+
const results = await doVerify(proofs, claims)
|
|
54
|
+
expect(results.verified.length).toBeGreaterThan(0)
|
|
55
|
+
expect(results.verified).toEqual(claims)
|
|
56
|
+
expect(results.unverified.length).toBe(0)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('does not verify a record that doesnt exist', async () => {
|
|
60
|
+
const realClaims = util.contentsToClaims(repoData)
|
|
61
|
+
const claims: RecordClaim[] = [
|
|
62
|
+
{
|
|
63
|
+
...realClaims[0],
|
|
64
|
+
rkey: TID.nextStr(),
|
|
65
|
+
},
|
|
66
|
+
]
|
|
67
|
+
const proofs = await getProofs(claims)
|
|
68
|
+
const results = await doVerify(proofs, claims)
|
|
69
|
+
expect(results.verified.length).toBe(0)
|
|
70
|
+
expect(results.unverified.length).toBeGreaterThan(0)
|
|
71
|
+
expect(results.unverified).toEqual(claims)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('does not verify an invalid record at a real path', async () => {
|
|
75
|
+
const realClaims = util.contentsToClaims(repoData)
|
|
76
|
+
const claims: RecordClaim[] = [
|
|
77
|
+
{
|
|
78
|
+
...realClaims[0],
|
|
79
|
+
record: util.generateObject(),
|
|
80
|
+
},
|
|
81
|
+
]
|
|
82
|
+
const proofs = await getProofs(claims)
|
|
83
|
+
const results = await doVerify(proofs, claims)
|
|
84
|
+
expect(results.verified.length).toBe(0)
|
|
85
|
+
expect(results.unverified.length).toBeGreaterThan(0)
|
|
86
|
+
expect(results.unverified).toEqual(claims)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('does not verify a delete where the record does exist', async () => {
|
|
90
|
+
const realClaims = util.contentsToClaims(repoData)
|
|
91
|
+
const claims: RecordClaim[] = [
|
|
92
|
+
{
|
|
93
|
+
collection: realClaims[0].collection,
|
|
94
|
+
rkey: realClaims[0].rkey,
|
|
95
|
+
record: null,
|
|
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('can determine record proofs from car file', async () => {
|
|
106
|
+
const possible = util.contentsToClaims(repoData)
|
|
107
|
+
const claims = [
|
|
108
|
+
//random sampling of records
|
|
109
|
+
possible[0],
|
|
110
|
+
possible[4],
|
|
111
|
+
possible[5],
|
|
112
|
+
possible[8],
|
|
113
|
+
]
|
|
114
|
+
const proofs = await getProofs(claims)
|
|
115
|
+
const records = await verify.verifyRecords(proofs, repoDid, keypair.did())
|
|
116
|
+
for (const record of records) {
|
|
117
|
+
const foundClaim = claims.find(
|
|
118
|
+
(claim) =>
|
|
119
|
+
claim.collection === record.collection && claim.rkey === record.rkey,
|
|
120
|
+
)
|
|
121
|
+
if (!foundClaim) {
|
|
122
|
+
throw new Error('Could not find record for claim')
|
|
123
|
+
}
|
|
124
|
+
expect(foundClaim.record).toEqual(
|
|
125
|
+
repoData[record.collection][record.rkey],
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('verifyRecords throws on a bad signature', async () => {
|
|
131
|
+
const badRepo = await util.addBadCommit(repo, keypair)
|
|
132
|
+
const claims = util.contentsToClaims(repoData)
|
|
133
|
+
const proofs = await streamToBuffer(
|
|
134
|
+
sync.getRecords(storage, badRepo.cid, claims),
|
|
135
|
+
)
|
|
136
|
+
const fn = verify.verifyRecords(proofs, repoDid, keypair.did())
|
|
137
|
+
await expect(fn).rejects.toThrow(verify.RepoVerificationError)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('verifyProofs throws on a bad signature', async () => {
|
|
141
|
+
const badRepo = await util.addBadCommit(repo, keypair)
|
|
142
|
+
const claims = util.contentsToClaims(repoData)
|
|
143
|
+
const proofs = await streamToBuffer(
|
|
144
|
+
sync.getRecords(storage, badRepo.cid, claims),
|
|
145
|
+
)
|
|
146
|
+
const fn = verify.verifyProofs(proofs, claims, repoDid, keypair.did())
|
|
147
|
+
await expect(fn).rejects.toThrow(verify.RepoVerificationError)
|
|
148
|
+
})
|
|
149
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { dataToCborBlock, wait } from '@atproto/common'
|
|
2
|
+
import { writeCar } from '../src'
|
|
3
|
+
|
|
4
|
+
describe('Utils', () => {
|
|
5
|
+
describe('writeCar()', () => {
|
|
6
|
+
it('propagates errors', async () => {
|
|
7
|
+
const iterate = async () => {
|
|
8
|
+
const iter = writeCar(null, async (car) => {
|
|
9
|
+
await wait(1)
|
|
10
|
+
const block = await dataToCborBlock({ test: 1 })
|
|
11
|
+
await car.put(block)
|
|
12
|
+
throw new Error('Oops!')
|
|
13
|
+
})
|
|
14
|
+
for await (const bytes of iter) {
|
|
15
|
+
// no-op
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
await expect(iterate).rejects.toThrow('Oops!')
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
})
|