@atproto/repo 0.1.0 → 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/dist/block-map.d.ts +2 -0
- package/dist/data-diff.d.ts +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +12090 -2882
- package/dist/index.js.map +4 -4
- package/dist/mst/mst.d.ts +13 -14
- package/dist/readable-repo.d.ts +4 -3
- package/dist/repo.d.ts +6 -2
- 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/memory-blockstore.d.ts +2 -1
- package/dist/storage/repo-storage.d.ts +2 -1
- package/dist/storage/types.d.ts +1 -0
- package/dist/sync/consumer.d.ts +1 -0
- package/dist/sync/provider.d.ts +4 -4
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/types.d.ts +30 -31
- package/dist/util.d.ts +6 -2
- package/dist/verify.d.ts +6 -1
- package/jest.bench.config.js +2 -1
- package/package.json +11 -6
- package/src/block-map.ts +8 -0
- package/src/data-diff.ts +2 -6
- package/src/index.ts +1 -0
- package/src/mst/mst.ts +1 -10
- package/src/readable-repo.ts +3 -3
- package/src/repo.ts +33 -2
- package/src/storage/memory-blockstore.ts +20 -1
- package/src/storage/repo-storage.ts +2 -1
- package/src/storage/types.ts +1 -0
- package/src/sync/consumer.ts +4 -1
- package/src/sync/provider.ts +11 -11
- package/src/types.ts +19 -21
- package/src/util.ts +34 -13
- package/src/verify.ts +52 -11
- package/tests/rebase.test.ts +37 -0
- package/tests/sync/checkout.test.ts +21 -3
- package/tests/sync/diff.test.ts +9 -4
- package/tests/sync/narrow.test.ts +8 -4
- package/tests/util.test.ts +21 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.json +1 -1
- /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/sync/provider.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { def, RecordPath } from '../types'
|
|
2
2
|
import { BlockWriter } from '@ipld/car/writer'
|
|
3
3
|
import { CID } from 'multiformats/cid'
|
|
4
4
|
import CidSet from '../cid-set'
|
|
@@ -10,10 +10,10 @@ import { MST } from '../mst'
|
|
|
10
10
|
// Checkouts
|
|
11
11
|
// -------------
|
|
12
12
|
|
|
13
|
-
export const getCheckout =
|
|
13
|
+
export const getCheckout = (
|
|
14
14
|
storage: RepoStorage,
|
|
15
15
|
commitCid: CID,
|
|
16
|
-
):
|
|
16
|
+
): AsyncIterable<Uint8Array> => {
|
|
17
17
|
return util.writeCar(commitCid, async (car: BlockWriter) => {
|
|
18
18
|
const commit = await storage.readObjAndBytes(commitCid, def.commit)
|
|
19
19
|
await car.put({ cid: commitCid, bytes: commit.bytes })
|
|
@@ -22,24 +22,24 @@ export const getCheckout = async (
|
|
|
22
22
|
})
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
//
|
|
25
|
+
// Commits
|
|
26
26
|
// -------------
|
|
27
27
|
|
|
28
|
-
export const
|
|
28
|
+
export const getCommits = (
|
|
29
29
|
storage: RepoStorage,
|
|
30
30
|
latest: CID,
|
|
31
31
|
earliest: CID | null,
|
|
32
|
-
):
|
|
32
|
+
): AsyncIterable<Uint8Array> => {
|
|
33
33
|
return util.writeCar(latest, (car: BlockWriter) => {
|
|
34
34
|
return writeCommitsToCarStream(storage, car, latest, earliest)
|
|
35
35
|
})
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export const getFullRepo =
|
|
38
|
+
export const getFullRepo = (
|
|
39
39
|
storage: RepoStorage,
|
|
40
40
|
cid: CID,
|
|
41
|
-
):
|
|
42
|
-
return
|
|
41
|
+
): AsyncIterable<Uint8Array> => {
|
|
42
|
+
return getCommits(storage, cid, null)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export const writeCommitsToCarStream = async (
|
|
@@ -63,11 +63,11 @@ export const writeCommitsToCarStream = async (
|
|
|
63
63
|
// Narrow slices
|
|
64
64
|
// -------------
|
|
65
65
|
|
|
66
|
-
export const getRecords =
|
|
66
|
+
export const getRecords = (
|
|
67
67
|
storage: RepoStorage,
|
|
68
68
|
commitCid: CID,
|
|
69
69
|
paths: RecordPath[],
|
|
70
|
-
):
|
|
70
|
+
): AsyncIterable<Uint8Array> => {
|
|
71
71
|
return util.writeCar(commitCid, async (car: BlockWriter) => {
|
|
72
72
|
const commit = await storage.readObjAndBytes(commitCid, def.commit)
|
|
73
73
|
await car.put({ cid: commitCid, bytes: commit.bytes })
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import { BlockWriter } from '@ipld/car/writer'
|
|
3
2
|
import { schema as common, def as commonDef } from '@atproto/common'
|
|
4
3
|
import { CID } from 'multiformats'
|
|
5
4
|
import BlockMap from './block-map'
|
|
@@ -101,6 +100,19 @@ export type CommitData = CommitBlockData & {
|
|
|
101
100
|
prev: CID | null
|
|
102
101
|
}
|
|
103
102
|
|
|
103
|
+
export type RebaseData = {
|
|
104
|
+
commit: CID
|
|
105
|
+
rebased: CID
|
|
106
|
+
blocks: BlockMap
|
|
107
|
+
preservedCids: CID[]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type CommitCidData = {
|
|
111
|
+
commit: CID
|
|
112
|
+
prev: CID | null
|
|
113
|
+
cids: CID[]
|
|
114
|
+
}
|
|
115
|
+
|
|
104
116
|
export type RepoUpdate = CommitData & {
|
|
105
117
|
ops: RecordWriteOp[]
|
|
106
118
|
}
|
|
@@ -108,6 +120,12 @@ export type RepoUpdate = CommitData & {
|
|
|
108
120
|
export type CollectionContents = Record<string, RepoRecord>
|
|
109
121
|
export type RepoContents = Record<string, CollectionContents>
|
|
110
122
|
|
|
123
|
+
export type RepoRecordWithCid = { cid: CID; value: RepoRecord }
|
|
124
|
+
export type CollectionContentsWithCids = Record<string, RepoRecordWithCid>
|
|
125
|
+
export type RepoContentsWithCids = Record<string, CollectionContentsWithCids>
|
|
126
|
+
|
|
127
|
+
export type DatastoreContents = Record<string, CID>
|
|
128
|
+
|
|
111
129
|
export type RecordPath = {
|
|
112
130
|
collection: string
|
|
113
131
|
rkey: string
|
|
@@ -118,23 +136,3 @@ export type RecordClaim = {
|
|
|
118
136
|
rkey: string
|
|
119
137
|
record: RepoRecord | null
|
|
120
138
|
}
|
|
121
|
-
|
|
122
|
-
// DataStores
|
|
123
|
-
// ---------------
|
|
124
|
-
|
|
125
|
-
export type DataValue = {
|
|
126
|
-
key: string
|
|
127
|
-
value: CID
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export interface DataStore {
|
|
131
|
-
add(key: string, value: CID): Promise<DataStore>
|
|
132
|
-
update(key: string, value: CID): Promise<DataStore>
|
|
133
|
-
delete(key: string): Promise<DataStore>
|
|
134
|
-
get(key: string): Promise<CID | null>
|
|
135
|
-
list(count?: number, after?: string, before?: string): Promise<DataValue[]>
|
|
136
|
-
listWithPrefix(prefix: string, count?: number): Promise<DataValue[]>
|
|
137
|
-
getUnstoredBlocks(): Promise<{ root: CID; blocks: BlockMap }>
|
|
138
|
-
writeToCarStream(car: BlockWriter): Promise<void>
|
|
139
|
-
cidsForPath(key: string): Promise<CID[]>
|
|
140
|
-
}
|
package/src/util.ts
CHANGED
|
@@ -4,12 +4,13 @@ import { CarReader } from '@ipld/car/reader'
|
|
|
4
4
|
import { BlockWriter, CarWriter } from '@ipld/car/writer'
|
|
5
5
|
import { Block as CarBlock } from '@ipld/car/api'
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
streamToBuffer,
|
|
8
8
|
verifyCidForBytes,
|
|
9
9
|
cborDecode,
|
|
10
10
|
check,
|
|
11
11
|
schema,
|
|
12
12
|
cidForCbor,
|
|
13
|
+
byteIterableToStream,
|
|
13
14
|
} from '@atproto/common'
|
|
14
15
|
import { ipldToLex, lexToIpld, LexValue, RepoRecord } from '@atproto/lexicon'
|
|
15
16
|
|
|
@@ -20,7 +21,6 @@ import DataDiff from './data-diff'
|
|
|
20
21
|
import { RepoStorage } from './storage'
|
|
21
22
|
import {
|
|
22
23
|
Commit,
|
|
23
|
-
DataStore,
|
|
24
24
|
RecordCreateDescript,
|
|
25
25
|
RecordDeleteDescript,
|
|
26
26
|
RecordPath,
|
|
@@ -34,6 +34,7 @@ import BlockMap from './block-map'
|
|
|
34
34
|
import { MissingBlocksError } from './error'
|
|
35
35
|
import * as parse from './parse'
|
|
36
36
|
import { Keypair } from '@atproto/crypto'
|
|
37
|
+
import { Readable } from 'stream'
|
|
37
38
|
|
|
38
39
|
export async function* verifyIncomingCarBlocks(
|
|
39
40
|
car: AsyncIterable<CarBlock>,
|
|
@@ -44,25 +45,37 @@ export async function* verifyIncomingCarBlocks(
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
// we have to turn the car writer output into a stream in order to properly handle errors
|
|
49
|
+
export function writeCarStream(
|
|
48
50
|
root: CID | null,
|
|
49
51
|
fn: (car: BlockWriter) => Promise<void>,
|
|
50
|
-
):
|
|
52
|
+
): Readable {
|
|
51
53
|
const { writer, out } =
|
|
52
54
|
root !== null ? CarWriter.create(root) : CarWriter.create()
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
|
|
56
|
+
const stream = byteIterableToStream(out)
|
|
57
|
+
fn(writer)
|
|
58
|
+
.catch((err) => {
|
|
59
|
+
stream.destroy(err)
|
|
60
|
+
})
|
|
61
|
+
.finally(() => writer.close())
|
|
62
|
+
return stream
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function* writeCar(
|
|
66
|
+
root: CID | null,
|
|
67
|
+
fn: (car: BlockWriter) => Promise<void>,
|
|
68
|
+
): AsyncIterable<Uint8Array> {
|
|
69
|
+
const stream = writeCarStream(root, fn)
|
|
70
|
+
for await (const chunk of stream) {
|
|
71
|
+
yield chunk
|
|
58
72
|
}
|
|
59
|
-
return bytes
|
|
60
73
|
}
|
|
61
74
|
|
|
62
|
-
export const
|
|
75
|
+
export const blocksToCarStream = (
|
|
63
76
|
root: CID | null,
|
|
64
77
|
blocks: BlockMap,
|
|
65
|
-
):
|
|
78
|
+
): AsyncIterable<Uint8Array> => {
|
|
66
79
|
return writeCar(root, async (writer) => {
|
|
67
80
|
for (const entry of blocks.entries()) {
|
|
68
81
|
await writer.put(entry)
|
|
@@ -70,6 +83,14 @@ export const blocksToCar = async (
|
|
|
70
83
|
})
|
|
71
84
|
}
|
|
72
85
|
|
|
86
|
+
export const blocksToCarFile = (
|
|
87
|
+
root: CID | null,
|
|
88
|
+
blocks: BlockMap,
|
|
89
|
+
): Promise<Uint8Array> => {
|
|
90
|
+
const carStream = blocksToCarStream(root, blocks)
|
|
91
|
+
return streamToBuffer(carStream)
|
|
92
|
+
}
|
|
93
|
+
|
|
73
94
|
export const readCar = async (
|
|
74
95
|
bytes: Uint8Array,
|
|
75
96
|
): Promise<{ roots: CID[]; blocks: BlockMap }> => {
|
|
@@ -108,7 +129,7 @@ export const getWriteLog = async (
|
|
|
108
129
|
if (!commits) throw new Error('Could not find shared history')
|
|
109
130
|
const heads = await Promise.all(commits.map((c) => Repo.load(storage, c)))
|
|
110
131
|
// Turn commit path into list of diffs
|
|
111
|
-
let prev
|
|
132
|
+
let prev = await MST.create(storage) // Empty
|
|
112
133
|
const msts = heads.map((h) => h.data)
|
|
113
134
|
const diffs: DataDiff[] = []
|
|
114
135
|
for (const mst of msts) {
|
package/src/verify.ts
CHANGED
|
@@ -6,7 +6,7 @@ import ReadableRepo from './readable-repo'
|
|
|
6
6
|
import Repo from './repo'
|
|
7
7
|
import CidSet from './cid-set'
|
|
8
8
|
import * as util from './util'
|
|
9
|
-
import { RecordClaim, RepoContents } from './types'
|
|
9
|
+
import { RecordClaim, RepoContents, RepoContentsWithCids } from './types'
|
|
10
10
|
import { def } from './types'
|
|
11
11
|
import { MST } from './mst'
|
|
12
12
|
import { cidForCbor } from '@atproto/common'
|
|
@@ -16,22 +16,18 @@ export type VerifiedCheckout = {
|
|
|
16
16
|
newCids: CidSet
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export type VerifiedCheckoutWithCids = {
|
|
20
|
+
commit: CID
|
|
21
|
+
contents: RepoContentsWithCids
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
export const verifyCheckout = async (
|
|
20
25
|
storage: ReadableBlockstore,
|
|
21
26
|
head: CID,
|
|
22
27
|
did: string,
|
|
23
28
|
signingKey: string,
|
|
24
29
|
): Promise<VerifiedCheckout> => {
|
|
25
|
-
const repo = await
|
|
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
|
-
}
|
|
30
|
+
const repo = await verifyRepoRoot(storage, head, did, signingKey)
|
|
35
31
|
const diff = await DataDiff.of(repo.data, null)
|
|
36
32
|
const newCids = new CidSet([repo.cid]).addSet(diff.newCids)
|
|
37
33
|
|
|
@@ -51,6 +47,51 @@ export const verifyCheckout = async (
|
|
|
51
47
|
}
|
|
52
48
|
}
|
|
53
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
|
+
|
|
54
95
|
export type VerifiedUpdate = {
|
|
55
96
|
commit: CID
|
|
56
97
|
prev: CID | null
|
|
@@ -0,0 +1,37 @@
|
|
|
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,9 +1,11 @@
|
|
|
1
1
|
import * as crypto from '@atproto/crypto'
|
|
2
|
-
import { Repo, RepoContents, RepoVerificationError } from '../../src'
|
|
2
|
+
import { CidSet, Repo, RepoContents, RepoVerificationError } from '../../src'
|
|
3
3
|
import { MemoryBlockstore } from '../../src/storage'
|
|
4
4
|
import * as sync from '../../src/sync'
|
|
5
5
|
|
|
6
6
|
import * as util from '../_util'
|
|
7
|
+
import { streamToBuffer } from '@atproto/common'
|
|
8
|
+
import { CarReader } from '@ipld/car/reader'
|
|
7
9
|
|
|
8
10
|
describe('Checkout Sync', () => {
|
|
9
11
|
let storage: MemoryBlockstore
|
|
@@ -25,7 +27,9 @@ describe('Checkout Sync', () => {
|
|
|
25
27
|
})
|
|
26
28
|
|
|
27
29
|
it('sync a non-historical repo checkout', async () => {
|
|
28
|
-
const checkoutCar = await
|
|
30
|
+
const checkoutCar = await streamToBuffer(
|
|
31
|
+
sync.getCheckout(storage, repo.cid),
|
|
32
|
+
)
|
|
29
33
|
const checkout = await sync.loadCheckout(
|
|
30
34
|
syncStorage,
|
|
31
35
|
checkoutCar,
|
|
@@ -47,9 +51,23 @@ describe('Checkout Sync', () => {
|
|
|
47
51
|
expect(hasGenesisCommit).toBeFalsy()
|
|
48
52
|
})
|
|
49
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
|
+
|
|
50
66
|
it('throws on a bad signature', async () => {
|
|
51
67
|
const badRepo = await util.addBadCommit(repo, keypair)
|
|
52
|
-
const checkoutCar = await
|
|
68
|
+
const checkoutCar = await streamToBuffer(
|
|
69
|
+
sync.getCheckout(storage, badRepo.cid),
|
|
70
|
+
)
|
|
53
71
|
await expect(
|
|
54
72
|
sync.loadCheckout(syncStorage, checkoutCar, repoDid, keypair.did()),
|
|
55
73
|
).rejects.toThrow(RepoVerificationError)
|
package/tests/sync/diff.test.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { MemoryBlockstore } from '../../src/storage'
|
|
|
4
4
|
import * as sync from '../../src/sync'
|
|
5
5
|
|
|
6
6
|
import * as util from '../_util'
|
|
7
|
+
import { streamToBuffer } from '@atproto/common'
|
|
7
8
|
|
|
8
9
|
describe('Diff Sync', () => {
|
|
9
10
|
let storage: MemoryBlockstore
|
|
@@ -24,7 +25,7 @@ describe('Diff Sync', () => {
|
|
|
24
25
|
let syncRepo: Repo
|
|
25
26
|
|
|
26
27
|
it('syncs an empty repo', async () => {
|
|
27
|
-
const car = await sync.getFullRepo(storage, repo.cid)
|
|
28
|
+
const car = await streamToBuffer(sync.getFullRepo(storage, repo.cid))
|
|
28
29
|
const loaded = await sync.loadFullRepo(
|
|
29
30
|
syncStorage,
|
|
30
31
|
car,
|
|
@@ -41,7 +42,7 @@ describe('Diff Sync', () => {
|
|
|
41
42
|
repo = filled.repo
|
|
42
43
|
repoData = filled.data
|
|
43
44
|
|
|
44
|
-
const car = await sync.getFullRepo(storage, repo.cid)
|
|
45
|
+
const car = await streamToBuffer(sync.getFullRepo(storage, repo.cid))
|
|
45
46
|
const loaded = await sync.loadFullRepo(
|
|
46
47
|
syncStorage,
|
|
47
48
|
car,
|
|
@@ -64,7 +65,9 @@ describe('Diff Sync', () => {
|
|
|
64
65
|
})
|
|
65
66
|
repo = edited.repo
|
|
66
67
|
repoData = edited.data
|
|
67
|
-
const diffCar = await
|
|
68
|
+
const diffCar = await streamToBuffer(
|
|
69
|
+
sync.getCommits(storage, repo.cid, syncRepo.cid),
|
|
70
|
+
)
|
|
68
71
|
const loaded = await sync.loadDiff(
|
|
69
72
|
syncRepo,
|
|
70
73
|
diffCar,
|
|
@@ -79,7 +82,9 @@ describe('Diff Sync', () => {
|
|
|
79
82
|
|
|
80
83
|
it('throws on a bad signature', async () => {
|
|
81
84
|
const badRepo = await util.addBadCommit(repo, keypair)
|
|
82
|
-
const diffCar = await
|
|
85
|
+
const diffCar = await streamToBuffer(
|
|
86
|
+
sync.getCommits(storage, badRepo.cid, syncRepo.cid),
|
|
87
|
+
)
|
|
83
88
|
await expect(
|
|
84
89
|
sync.loadDiff(syncRepo, diffCar, repoDid, keypair.did()),
|
|
85
90
|
).rejects.toThrow('Invalid signature on commit')
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TID } from '@atproto/common'
|
|
1
|
+
import { TID, streamToBuffer } from '@atproto/common'
|
|
2
2
|
import * as crypto from '@atproto/crypto'
|
|
3
3
|
import { RecordClaim, Repo, RepoContents } from '../../src'
|
|
4
4
|
import { MemoryBlockstore } from '../../src/storage'
|
|
@@ -25,7 +25,7 @@ describe('Narrow Sync', () => {
|
|
|
25
25
|
})
|
|
26
26
|
|
|
27
27
|
const getProofs = async (claims: RecordClaim[]) => {
|
|
28
|
-
return sync.getRecords(storage, repo.cid, claims)
|
|
28
|
+
return streamToBuffer(sync.getRecords(storage, repo.cid, claims))
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const doVerify = (proofs: Uint8Array, claims: RecordClaim[]) => {
|
|
@@ -130,7 +130,9 @@ describe('Narrow Sync', () => {
|
|
|
130
130
|
it('verifyRecords throws on a bad signature', async () => {
|
|
131
131
|
const badRepo = await util.addBadCommit(repo, keypair)
|
|
132
132
|
const claims = util.contentsToClaims(repoData)
|
|
133
|
-
const proofs = await
|
|
133
|
+
const proofs = await streamToBuffer(
|
|
134
|
+
sync.getRecords(storage, badRepo.cid, claims),
|
|
135
|
+
)
|
|
134
136
|
const fn = verify.verifyRecords(proofs, repoDid, keypair.did())
|
|
135
137
|
await expect(fn).rejects.toThrow(verify.RepoVerificationError)
|
|
136
138
|
})
|
|
@@ -138,7 +140,9 @@ describe('Narrow Sync', () => {
|
|
|
138
140
|
it('verifyProofs throws on a bad signature', async () => {
|
|
139
141
|
const badRepo = await util.addBadCommit(repo, keypair)
|
|
140
142
|
const claims = util.contentsToClaims(repoData)
|
|
141
|
-
const proofs = await
|
|
143
|
+
const proofs = await streamToBuffer(
|
|
144
|
+
sync.getRecords(storage, badRepo.cid, claims),
|
|
145
|
+
)
|
|
142
146
|
const fn = verify.verifyProofs(proofs, claims, repoDid, keypair.did())
|
|
143
147
|
await expect(fn).rejects.toThrow(verify.RepoVerificationError)
|
|
144
148
|
})
|
|
@@ -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
|
+
})
|