@filoz/repair-cli 0.4.0 → 1.1.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/package.json +4 -4
- package/dist/src/api.d.ts +35 -0
- package/dist/src/api.d.ts.map +1 -0
- package/dist/src/api.js +118 -0
- package/dist/src/api.js.map +1 -0
- package/dist/src/commands/datasets.d.ts +5 -7
- package/dist/src/commands/datasets.d.ts.map +1 -1
- package/dist/src/commands/datasets.js +33 -35
- package/dist/src/commands/datasets.js.map +1 -1
- package/dist/src/commands/providers.d.ts +5 -7
- package/dist/src/commands/providers.d.ts.map +1 -1
- package/dist/src/commands/providers.js +32 -33
- package/dist/src/commands/providers.js.map +1 -1
- package/dist/src/commands/repair.d.ts +5 -7
- package/dist/src/commands/repair.d.ts.map +1 -1
- package/dist/src/commands/repair.js +5 -1
- package/dist/src/commands/repair.js.map +1 -1
- package/dist/src/commands/replicate.d.ts +5 -7
- package/dist/src/commands/replicate.d.ts.map +1 -1
- package/dist/src/commands/replicate.js +5 -1
- package/dist/src/commands/replicate.js.map +1 -1
- package/dist/src/commands/second-copy.d.ts +5 -7
- package/dist/src/commands/second-copy.d.ts.map +1 -1
- package/dist/src/commands/second-copy.js +5 -1
- package/dist/src/commands/second-copy.js.map +1 -1
- package/dist/src/commands/session-key.d.ts +5 -7
- package/dist/src/commands/session-key.d.ts.map +1 -1
- package/dist/src/commands/setup.d.ts.map +1 -1
- package/dist/src/commands/setup.js +11 -32
- package/dist/src/commands/setup.js.map +1 -1
- package/dist/src/commands/wallet.d.ts +5 -7
- package/dist/src/commands/wallet.d.ts.map +1 -1
- package/dist/src/db/dedupe-cids.d.ts +6 -7
- package/dist/src/db/dedupe-cids.d.ts.map +1 -1
- package/dist/src/db/dedupe-cids.js +2 -14
- package/dist/src/db/dedupe-cids.js.map +1 -1
- package/dist/src/db/find-repair-dataset.d.ts +3 -3
- package/dist/src/db/find-repair-dataset.d.ts.map +1 -1
- package/dist/src/db/find-repair-dataset.js +16 -14
- package/dist/src/db/find-repair-dataset.js.map +1 -1
- package/dist/src/db/get-active-data-set-ids.d.ts +9 -0
- package/dist/src/db/get-active-data-set-ids.d.ts.map +1 -0
- package/dist/src/db/get-active-data-set-ids.js +9 -0
- package/dist/src/db/get-active-data-set-ids.js.map +1 -0
- package/dist/src/db/get-alternate-provider-urls.d.ts +6 -0
- package/dist/src/db/get-alternate-provider-urls.d.ts.map +1 -0
- package/dist/src/db/get-alternate-provider-urls.js +24 -0
- package/dist/src/db/get-alternate-provider-urls.js.map +1 -0
- package/dist/src/db/get-pieces.d.ts +8 -27
- package/dist/src/db/get-pieces.d.ts.map +1 -1
- package/dist/src/db/get-pieces.js +44 -97
- package/dist/src/db/get-pieces.js.map +1 -1
- package/dist/src/db/get-repair-provider.d.ts +2 -2
- package/dist/src/db/get-repair-provider.d.ts.map +1 -1
- package/dist/src/db/get-repair-provider.js +8 -20
- package/dist/src/db/get-repair-provider.js.map +1 -1
- package/dist/src/db/repair-create.js +3 -3
- package/dist/src/db/repair-create.js.map +1 -1
- package/dist/src/db/replicate-create.d.ts.map +1 -1
- package/dist/src/db/replicate-create.js +13 -21
- package/dist/src/db/replicate-create.js.map +1 -1
- package/dist/src/db/second-copy-create.js +2 -2
- package/dist/src/db/second-copy-create.js.map +1 -1
- package/dist/src/local-schema.d.ts +1 -1
- package/dist/src/middleware.d.ts +12 -16
- package/dist/src/middleware.d.ts.map +1 -1
- package/dist/src/middleware.js +6 -10
- package/dist/src/middleware.js.map +1 -1
- package/dist/src/pipeline/add-pieces.d.ts +4 -4
- package/dist/src/pipeline/add-pieces.d.ts.map +1 -1
- package/dist/src/pipeline/add-pieces.js +32 -19
- package/dist/src/pipeline/add-pieces.js.map +1 -1
- package/dist/src/pipeline/create-datasets.d.ts +4 -4
- package/dist/src/pipeline/create-datasets.d.ts.map +1 -1
- package/dist/src/pipeline/create-datasets.js +7 -14
- package/dist/src/pipeline/create-datasets.js.map +1 -1
- package/dist/src/types.d.ts +8 -9
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils.d.ts +45 -37
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +47 -2
- package/dist/src/utils.js.map +1 -1
- package/package.json +4 -4
- package/readme.md +22 -19
- package/src/api.ts +220 -0
- package/src/commands/datasets.ts +33 -38
- package/src/commands/providers.ts +34 -42
- package/src/commands/repair.ts +5 -1
- package/src/commands/replicate.ts +5 -1
- package/src/commands/second-copy.ts +5 -1
- package/src/commands/setup.ts +12 -35
- package/src/db/dedupe-cids.ts +7 -24
- package/src/db/find-repair-dataset.ts +17 -28
- package/src/db/get-active-data-set-ids.ts +29 -0
- package/src/db/get-alternate-provider-urls.ts +43 -0
- package/src/db/get-pieces.ts +82 -214
- package/src/db/get-repair-provider.ts +10 -30
- package/src/db/repair-create.ts +3 -3
- package/src/db/replicate-create.ts +13 -21
- package/src/db/second-copy-create.ts +2 -2
- package/src/middleware.ts +10 -14
- package/src/pipeline/add-pieces.ts +52 -24
- package/src/pipeline/create-datasets.ts +9 -29
- package/src/types.ts +11 -9
- package/src/utils.ts +71 -7
- package/dist/src/db/find-providers-by-cid.d.ts +0 -9
- package/dist/src/db/find-providers-by-cid.d.ts.map +0 -1
- package/dist/src/db/find-providers-by-cid.js +0 -43
- package/dist/src/db/find-providers-by-cid.js.map +0 -1
- package/dist/src/indexer-schema.d.ts +0 -549
- package/dist/src/indexer-schema.d.ts.map +0 -1
- package/dist/src/indexer-schema.js +0 -56
- package/dist/src/indexer-schema.js.map +0 -1
- package/src/db/find-providers-by-cid.ts +0 -70
- package/src/indexer-schema.ts +0 -77
|
@@ -1,47 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Address } from 'viem'
|
|
2
|
+
import { getProvider } from '../api.ts'
|
|
2
3
|
import type { Context, RepairProvider } from '../types.ts'
|
|
3
4
|
|
|
4
|
-
export interface GetRepairProviderOptions extends Pick<Context, '
|
|
5
|
+
export interface GetRepairProviderOptions extends Pick<Context, 'api'> {
|
|
5
6
|
providerId: bigint
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Load an active provider by ID for use as a repair target.
|
|
10
11
|
*/
|
|
11
|
-
export async function getRepairProvider({
|
|
12
|
-
|
|
13
|
-
providerId,
|
|
14
|
-
}: GetRepairProviderOptions): Promise<RepairProvider | null> {
|
|
15
|
-
const schema = indexerDb._.fullSchema
|
|
16
|
-
const [provider] = await indexerDb
|
|
17
|
-
.select({
|
|
18
|
-
providerId: schema.providers.providerId,
|
|
19
|
-
providerAddress: schema.providers.providerAddress,
|
|
20
|
-
name: schema.providers.name,
|
|
21
|
-
serviceUrl: schema.providers.serviceUrl,
|
|
22
|
-
approved: schema.providers.approved,
|
|
23
|
-
endorsed: schema.providers.endorsed,
|
|
24
|
-
})
|
|
25
|
-
.from(schema.providers)
|
|
26
|
-
.where(
|
|
27
|
-
and(
|
|
28
|
-
eq(schema.providers.providerId, providerId),
|
|
29
|
-
eq(schema.providers.providerActive, true),
|
|
30
|
-
eq(schema.providers.pdpProductActive, true)
|
|
31
|
-
)
|
|
32
|
-
)
|
|
33
|
-
.limit(1)
|
|
12
|
+
export async function getRepairProvider({ api, providerId }: GetRepairProviderOptions): Promise<RepairProvider | null> {
|
|
13
|
+
const provider = await getProvider(api, providerId)
|
|
34
14
|
|
|
35
|
-
if (!provider?.
|
|
15
|
+
if (!provider?.active || !provider.pdpProductActive || !provider.serviceUrl || !provider.name) {
|
|
36
16
|
return null
|
|
37
17
|
}
|
|
38
18
|
|
|
39
19
|
return {
|
|
40
|
-
providerId: provider.providerId,
|
|
41
|
-
providerAddress: provider.
|
|
20
|
+
providerId: BigInt(provider.providerId),
|
|
21
|
+
providerAddress: provider.address as Address,
|
|
42
22
|
name: provider.name,
|
|
43
23
|
serviceUrl: provider.serviceUrl,
|
|
44
|
-
approved: provider.approved,
|
|
45
|
-
endorsed: provider.endorsed,
|
|
24
|
+
approved: provider.approved ?? false,
|
|
25
|
+
endorsed: provider.endorsed ?? false,
|
|
46
26
|
}
|
|
47
27
|
}
|
package/src/db/repair-create.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface RepairCreateOptions extends Context {
|
|
|
18
18
|
* @returns {Promise<number>} The ID of the created repair.
|
|
19
19
|
*/
|
|
20
20
|
export async function repairCreate(options: RepairCreateOptions): Promise<number> {
|
|
21
|
-
const {
|
|
21
|
+
const { api, localDb, repairProviderId, targetProviderId, client } = options
|
|
22
22
|
const localSchema = localDb._.fullSchema
|
|
23
23
|
const now = Date.now()
|
|
24
24
|
const blockNumber = await getBlockNumber(client)
|
|
@@ -34,7 +34,7 @@ export async function repairCreate(options: RepairCreateOptions): Promise<number
|
|
|
34
34
|
throw new RepairCreationError('Target provider must differ from the provider being repaired')
|
|
35
35
|
}
|
|
36
36
|
const targetProvider = await getRepairProvider({
|
|
37
|
-
|
|
37
|
+
api,
|
|
38
38
|
providerId: targetProviderId,
|
|
39
39
|
})
|
|
40
40
|
|
|
@@ -68,7 +68,7 @@ export async function repairCreate(options: RepairCreateOptions): Promise<number
|
|
|
68
68
|
const seenCids = new Set<string>()
|
|
69
69
|
await forEachPiecesPage(
|
|
70
70
|
{
|
|
71
|
-
|
|
71
|
+
api,
|
|
72
72
|
providerId: repairProviderId,
|
|
73
73
|
repairId: repair.id,
|
|
74
74
|
blockNumber,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { taskLog } from '@clack/prompts'
|
|
2
|
-
import { eq } from 'drizzle-orm'
|
|
3
2
|
import { getBlockNumber } from 'viem/actions'
|
|
3
|
+
import { getDataSet } from '../api.ts'
|
|
4
4
|
import { NoAlternateProviderError, RepairCreationError } from '../error.ts'
|
|
5
5
|
import type { Context } from '../types.ts'
|
|
6
6
|
import { forEachDataSetPiecesPage } from './get-pieces.ts'
|
|
@@ -18,8 +18,7 @@ export interface ReplicateCreateOptions extends Context {
|
|
|
18
18
|
* @returns The ID of the created local job.
|
|
19
19
|
*/
|
|
20
20
|
export async function replicateCreate(options: ReplicateCreateOptions): Promise<number> {
|
|
21
|
-
const {
|
|
22
|
-
const indexerSchema = indexerDb._.fullSchema
|
|
21
|
+
const { api, localDb, dataSetId, targetProviderId, client } = options
|
|
23
22
|
const localSchema = localDb._.fullSchema
|
|
24
23
|
const now = Date.now()
|
|
25
24
|
const blockNumber = await getBlockNumber(client)
|
|
@@ -30,35 +29,28 @@ export async function replicateCreate(options: ReplicateCreateOptions): Promise<
|
|
|
30
29
|
retainLog: true,
|
|
31
30
|
})
|
|
32
31
|
|
|
33
|
-
const
|
|
34
|
-
.select({
|
|
35
|
-
dataSetId: indexerSchema.dataSets.dataSetId,
|
|
36
|
-
providerId: indexerSchema.dataSets.providerId,
|
|
37
|
-
deleted: indexerSchema.dataSets.deleted,
|
|
38
|
-
})
|
|
39
|
-
.from(indexerSchema.dataSets)
|
|
40
|
-
.where(eq(indexerSchema.dataSets.dataSetId, dataSetId))
|
|
41
|
-
.limit(1)
|
|
32
|
+
const sourceDataSet = await getDataSet(api, dataSetId)
|
|
42
33
|
|
|
43
|
-
if (!sourceDataSet || sourceDataSet.deleted) {
|
|
34
|
+
if (!sourceDataSet || sourceDataSet.deleted || sourceDataSet.providerId == null) {
|
|
44
35
|
throw new RepairCreationError(`Source dataset ${dataSetId} not found or deleted`)
|
|
45
36
|
}
|
|
37
|
+
const sourceProviderId = BigInt(sourceDataSet.providerId)
|
|
46
38
|
|
|
47
|
-
if (targetProviderId ===
|
|
39
|
+
if (targetProviderId === sourceProviderId) {
|
|
48
40
|
throw new RepairCreationError('Target provider must differ from the source dataset provider')
|
|
49
41
|
}
|
|
50
42
|
|
|
51
43
|
const sourceProvider = await getRepairProvider({
|
|
52
|
-
|
|
53
|
-
providerId:
|
|
44
|
+
api,
|
|
45
|
+
providerId: sourceProviderId,
|
|
54
46
|
})
|
|
55
47
|
|
|
56
48
|
if (!sourceProvider) {
|
|
57
|
-
throw new RepairCreationError(`Source provider ${
|
|
49
|
+
throw new RepairCreationError(`Source provider ${sourceProviderId} not found or inactive`)
|
|
58
50
|
}
|
|
59
51
|
|
|
60
52
|
const targetProvider = await getRepairProvider({
|
|
61
|
-
|
|
53
|
+
api,
|
|
62
54
|
providerId: targetProviderId,
|
|
63
55
|
})
|
|
64
56
|
|
|
@@ -71,7 +63,7 @@ export async function replicateCreate(options: ReplicateCreateOptions): Promise<
|
|
|
71
63
|
.values({
|
|
72
64
|
kind: 'replicate',
|
|
73
65
|
repairProviderId: sourceProvider.providerId,
|
|
74
|
-
repairDataSetId:
|
|
66
|
+
repairDataSetId: dataSetId,
|
|
75
67
|
sourceProviderUrl: sourceProvider.serviceUrl,
|
|
76
68
|
targetProviderId: targetProvider.providerId,
|
|
77
69
|
targetProviderUrl: targetProvider.serviceUrl,
|
|
@@ -87,8 +79,8 @@ export async function replicateCreate(options: ReplicateCreateOptions): Promise<
|
|
|
87
79
|
let totalOperations = 0
|
|
88
80
|
await forEachDataSetPiecesPage(
|
|
89
81
|
{
|
|
90
|
-
|
|
91
|
-
dataSetId
|
|
82
|
+
api,
|
|
83
|
+
dataSetId,
|
|
92
84
|
repairId: repair.id,
|
|
93
85
|
sourceProviderUrl: sourceProvider.serviceUrl,
|
|
94
86
|
},
|
|
@@ -76,7 +76,7 @@ export async function parsePieceCidsFromFile(piecesFile: string): Promise<string
|
|
|
76
76
|
* @returns The ID of the created local job.
|
|
77
77
|
*/
|
|
78
78
|
export async function secondCopyCreate(options: SecondCopyCreateOptions): Promise<number> {
|
|
79
|
-
const {
|
|
79
|
+
const { api, localDb, piecesFile, sourceProviderUrl, targetProviderId, client } = options
|
|
80
80
|
const localSchema = localDb._.fullSchema
|
|
81
81
|
const now = Date.now()
|
|
82
82
|
const blockNumber = await getBlockNumber(client)
|
|
@@ -89,7 +89,7 @@ export async function secondCopyCreate(options: SecondCopyCreateOptions): Promis
|
|
|
89
89
|
})
|
|
90
90
|
|
|
91
91
|
const targetProvider = await getRepairProvider({
|
|
92
|
-
|
|
92
|
+
api,
|
|
93
93
|
providerId: targetProviderId,
|
|
94
94
|
})
|
|
95
95
|
|
package/src/middleware.ts
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { drizzle as drizzlePostgres } from 'drizzle-orm/node-postgres'
|
|
1
|
+
import type { FilecoinChain } from '@filoz/synapse-core/chains'
|
|
3
2
|
import { middleware, z } from 'incur'
|
|
4
3
|
import type { Account, Client, Transport } from 'viem'
|
|
5
|
-
import
|
|
6
|
-
import type {
|
|
4
|
+
import { createIndexerApi } from './api.ts'
|
|
5
|
+
import type { IndexerApi, LocalDatabase } from './types.ts'
|
|
7
6
|
import { config, createLocalDatabase, getClient } from './utils.ts'
|
|
8
7
|
|
|
9
8
|
export const contextSchema = z.object({
|
|
10
|
-
|
|
9
|
+
api: z.custom<IndexerApi>(),
|
|
11
10
|
localDb: z.custom<LocalDatabase>(),
|
|
12
11
|
config: z.custom<typeof config>(),
|
|
13
|
-
client: z.custom<Client<Transport,
|
|
14
|
-
chain: z.custom<
|
|
12
|
+
client: z.custom<Client<Transport, FilecoinChain, Account>>(),
|
|
13
|
+
chain: z.custom<FilecoinChain>(),
|
|
15
14
|
source: z.string(),
|
|
16
15
|
isInteractive: z.boolean(),
|
|
17
16
|
})
|
|
18
17
|
|
|
19
18
|
export const contextMiddleware = middleware<typeof contextSchema>(async (c, next) => {
|
|
20
|
-
const { dbPath, chainId,
|
|
19
|
+
const { dbPath, chainId, apiUrl, source } = config.store
|
|
21
20
|
|
|
22
|
-
if (!dbPath || !chainId || !
|
|
21
|
+
if (!dbPath || !chainId || !source) {
|
|
23
22
|
return c.error({
|
|
24
23
|
code: 'CONFIG_NOT_SET',
|
|
25
24
|
message: 'Config not set. Please run `repair setup` first.',
|
|
@@ -28,13 +27,11 @@ export const contextMiddleware = middleware<typeof contextSchema>(async (c, next
|
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
const localDb = await createLocalDatabase(dbPath)
|
|
31
|
-
const
|
|
32
|
-
schema: indexerSchema,
|
|
33
|
-
})
|
|
30
|
+
const api = createIndexerApi({ baseUrl: apiUrl, chainId })
|
|
34
31
|
|
|
35
32
|
const { client, chain } = getClient(chainId)
|
|
36
33
|
c.set('localDb', localDb)
|
|
37
|
-
c.set('
|
|
34
|
+
c.set('api', api)
|
|
38
35
|
c.set('config', config)
|
|
39
36
|
c.set('client', client)
|
|
40
37
|
c.set('chain', chain)
|
|
@@ -43,5 +40,4 @@ export const contextMiddleware = middleware<typeof contextSchema>(async (c, next
|
|
|
43
40
|
await next()
|
|
44
41
|
|
|
45
42
|
localDb.$client.close()
|
|
46
|
-
await indexerDb.$client.end()
|
|
47
43
|
})
|
|
@@ -1,32 +1,43 @@
|
|
|
1
1
|
import { taskLog } from '@clack/prompts'
|
|
2
|
-
import * as Piece from '@filoz/synapse-core/piece'
|
|
3
2
|
import * as SP from '@filoz/synapse-core/sp'
|
|
4
3
|
import { and, asc, eq, gt, inArray } from 'drizzle-orm'
|
|
5
4
|
import PQueue from 'p-queue'
|
|
5
|
+
import { listDataSetPieces } from '../api.ts'
|
|
6
6
|
import { dedupeCids } from '../db/dedupe-cids.ts'
|
|
7
7
|
import { getTargetDataset } from '../db/get-target-dataset.ts'
|
|
8
8
|
import { repairUpdate } from '../db/repair-update.ts'
|
|
9
9
|
import { upsertOperations } from '../db/upsert-operations.ts'
|
|
10
10
|
import type { OperationSelect, RepairSelect } from '../local-schema.ts'
|
|
11
|
-
import type {
|
|
12
|
-
import {
|
|
11
|
+
import type { IndexerApi, LocalDatabase, WalletClient } from '../types.ts'
|
|
12
|
+
import {
|
|
13
|
+
completeConfirmedOperations,
|
|
14
|
+
excludeOperationsByCid,
|
|
15
|
+
fitAddPiecesBatch,
|
|
16
|
+
hashLink,
|
|
17
|
+
operationsToAddPieces,
|
|
18
|
+
operationsToPullPieces,
|
|
19
|
+
} from '../utils.ts'
|
|
20
|
+
|
|
21
|
+
/** Operations read per batch before fitting; above the ~400 metadata-free pieces one addPieces message holds. */
|
|
22
|
+
const MAX_BATCH_CANDIDATES = 500
|
|
13
23
|
|
|
14
24
|
export type RunPullPiecesPhaseOptions = {
|
|
15
25
|
localDb: LocalDatabase
|
|
16
|
-
|
|
26
|
+
api: IndexerApi
|
|
17
27
|
repair: RepairSelect
|
|
18
28
|
concurrency: number
|
|
19
|
-
|
|
29
|
+
/** Max pieces per batch. Defaults to as many as fit in one addPieces message. */
|
|
30
|
+
batchSize?: number
|
|
20
31
|
client: WalletClient
|
|
21
32
|
}
|
|
22
33
|
|
|
23
34
|
type CreateAddPiecesWorkerOptions = {
|
|
24
35
|
localDb: LocalDatabase
|
|
25
|
-
|
|
36
|
+
/** CIDs already in the target dataset; repairs only. Updated as batches are added. */
|
|
37
|
+
targetCids?: Set<string>
|
|
26
38
|
repair: RepairSelect
|
|
27
39
|
client: WalletClient
|
|
28
40
|
state: {
|
|
29
|
-
totalBatches: number
|
|
30
41
|
totalOperations: number
|
|
31
42
|
completedOperations: number
|
|
32
43
|
failedOperations: number
|
|
@@ -42,21 +53,20 @@ type AddPiecesJobOptions = {
|
|
|
42
53
|
/**
|
|
43
54
|
* Create a worker function that adds pieces to the target repair dataset.
|
|
44
55
|
*/
|
|
45
|
-
function createAddPiecesWorker({ localDb,
|
|
56
|
+
function createAddPiecesWorker({ localDb, targetCids, repair, client, state, log }: CreateAddPiecesWorkerOptions) {
|
|
46
57
|
return async (options: AddPiecesJobOptions) => {
|
|
47
58
|
let completedOps = 0
|
|
48
59
|
let failedOps = 0
|
|
49
60
|
let operations: OperationSelect[] = options.operations
|
|
50
|
-
const isRepair = repair.repairDataSetId == null
|
|
51
61
|
|
|
52
|
-
const group = log.group(`Batch ${options.batchNumber}
|
|
62
|
+
const group = log.group(`Batch ${options.batchNumber} (${options.operations.length} pieces)`)
|
|
53
63
|
|
|
54
64
|
try {
|
|
55
65
|
const dataset = await getTargetDataset({ localDb, repairId: repair.id, client })
|
|
56
66
|
|
|
57
67
|
// dedupe operations by CID on the target dataset for repairs jobs
|
|
58
|
-
if (
|
|
59
|
-
operations = await dedupeCids({
|
|
68
|
+
if (targetCids) {
|
|
69
|
+
operations = await dedupeCids({ localDb, existingCids: targetCids, operations })
|
|
60
70
|
}
|
|
61
71
|
// A previous run may have submitted a transaction and crashed before marking rows completed.
|
|
62
72
|
const operationsBeforeConfirmationCheck = operations.length
|
|
@@ -75,7 +85,6 @@ function createAddPiecesWorker({ localDb, indexerDb, repair, client, state, log
|
|
|
75
85
|
dataSetId: dataset.dataSetId,
|
|
76
86
|
clientDataSetId: dataset.clientDataSetId,
|
|
77
87
|
pieces: operationsToPullPieces(operations),
|
|
78
|
-
timeout: 1000 * 60 * 30, // 30 minutes
|
|
79
88
|
onStatus: (status) => {
|
|
80
89
|
const completed = status.pieces.filter((piece) => piece.status === 'complete').length
|
|
81
90
|
const failed = status.pieces.filter((piece) => piece.status === 'failed').length
|
|
@@ -109,10 +118,7 @@ function createAddPiecesWorker({ localDb, indexerDb, repair, client, state, log
|
|
|
109
118
|
serviceURL: repair.targetProviderUrl,
|
|
110
119
|
dataSetId: dataset.dataSetId,
|
|
111
120
|
clientDataSetId: dataset.clientDataSetId,
|
|
112
|
-
pieces: operations
|
|
113
|
-
pieceCid: Piece.from(operation.cid),
|
|
114
|
-
metadata: isRepair ? undefined : operation.metadata,
|
|
115
|
-
})),
|
|
121
|
+
pieces: operationsToAddPieces(operations, !targetCids),
|
|
116
122
|
})
|
|
117
123
|
await upsertOperations({
|
|
118
124
|
localDb,
|
|
@@ -124,15 +130,18 @@ function createAddPiecesWorker({ localDb, indexerDb, repair, client, state, log
|
|
|
124
130
|
|
|
125
131
|
group.message(`Waiting for add pieces ${hashLink(addPiecesResult.txHash, client.chain)}...`)
|
|
126
132
|
const waitForAddPieces = await SP.waitForAddPieces(addPiecesResult)
|
|
133
|
+
// Curio may replace the message by fee; record the hash that landed on chain.
|
|
134
|
+
const confirmedTxHash = waitForAddPieces.confirmedTxHash ?? waitForAddPieces.txHash
|
|
127
135
|
state.completedOperations += operations.length
|
|
128
136
|
completedOps += operations.length
|
|
137
|
+
for (const operation of operations) targetCids?.add(operation.cid)
|
|
129
138
|
await upsertOperations({
|
|
130
139
|
localDb,
|
|
131
140
|
operations: operations.map((operation) => ({
|
|
132
141
|
...operation,
|
|
133
142
|
status: 'completed',
|
|
134
143
|
error: null,
|
|
135
|
-
txHash:
|
|
144
|
+
txHash: confirmedTxHash,
|
|
136
145
|
})),
|
|
137
146
|
})
|
|
138
147
|
}
|
|
@@ -158,7 +167,7 @@ function createAddPiecesWorker({ localDb, indexerDb, repair, client, state, log
|
|
|
158
167
|
*/
|
|
159
168
|
export async function runAddPieces({
|
|
160
169
|
localDb,
|
|
161
|
-
|
|
170
|
+
api,
|
|
162
171
|
repair,
|
|
163
172
|
concurrency,
|
|
164
173
|
batchSize,
|
|
@@ -177,7 +186,6 @@ export async function runAddPieces({
|
|
|
177
186
|
)
|
|
178
187
|
let batchNumber = 0
|
|
179
188
|
const state = {
|
|
180
|
-
totalBatches: Math.ceil(totalOperations / batchSize),
|
|
181
189
|
totalOperations,
|
|
182
190
|
completedOperations: 0,
|
|
183
191
|
failedOperations: 0,
|
|
@@ -188,8 +196,20 @@ export async function runAddPieces({
|
|
|
188
196
|
limit: 1,
|
|
189
197
|
})
|
|
190
198
|
|
|
199
|
+
const dataset = totalOperations > 0 ? await getTargetDataset({ localDb, repairId: repair.id, client }) : undefined
|
|
200
|
+
|
|
201
|
+
// Repairs and second copies dedupe into one shared dataset; read its CIDs once instead of per batch.
|
|
202
|
+
let targetCids: Set<string> | undefined
|
|
203
|
+
if (repair.repairDataSetId == null && dataset) {
|
|
204
|
+
const pieces = await listDataSetPieces(api, dataset.dataSetId, { removed: false })
|
|
205
|
+
targetCids = new Set(pieces.flatMap((piece) => (piece.cid == null ? [] : [piece.cid])))
|
|
206
|
+
}
|
|
207
|
+
|
|
191
208
|
async function getNextBatch(): Promise<OperationSelect[] | null> {
|
|
192
|
-
|
|
209
|
+
if (!dataset) {
|
|
210
|
+
return null
|
|
211
|
+
}
|
|
212
|
+
const candidates = await localDb.query.operations.findMany({
|
|
193
213
|
where: and(
|
|
194
214
|
eq(localSchema.operations.repairId, repair.id),
|
|
195
215
|
eq(localSchema.operations.type, 'add_piece'),
|
|
@@ -197,17 +217,25 @@ export async function runAddPieces({
|
|
|
197
217
|
gt(localSchema.operations.id, cursor)
|
|
198
218
|
),
|
|
199
219
|
orderBy: [asc(localSchema.operations.id)],
|
|
200
|
-
limit: batchSize,
|
|
220
|
+
limit: batchSize ?? MAX_BATCH_CANDIDATES,
|
|
201
221
|
})
|
|
202
|
-
if (
|
|
222
|
+
if (candidates.length === 0) {
|
|
203
223
|
return null
|
|
204
224
|
}
|
|
205
225
|
|
|
226
|
+
// Size the batch by what fits in one addPieces message rather than a fixed piece count.
|
|
227
|
+
const operations = fitAddPiecesBatch({
|
|
228
|
+
operations: candidates,
|
|
229
|
+
dataSetId: dataset.dataSetId,
|
|
230
|
+
chain: client.chain,
|
|
231
|
+
withMetadata: !targetCids,
|
|
232
|
+
})
|
|
233
|
+
|
|
206
234
|
cursor = operations.at(-1)?.id ?? cursor
|
|
207
235
|
return operations
|
|
208
236
|
}
|
|
209
237
|
|
|
210
|
-
const addPiecesJob = createAddPiecesWorker({ localDb,
|
|
238
|
+
const addPiecesJob = createAddPiecesWorker({ localDb, targetCids, repair, client, state, log })
|
|
211
239
|
const queue = new PQueue({ concurrency })
|
|
212
240
|
|
|
213
241
|
while (true) {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import * as p from '@clack/prompts'
|
|
2
2
|
import * as SP from '@filoz/synapse-core/sp'
|
|
3
3
|
import { getPDPProvider } from '@filoz/synapse-core/sp-registry'
|
|
4
|
-
import { eq } from 'drizzle-orm'
|
|
5
4
|
import type { Address } from 'viem'
|
|
5
|
+
import { getDataSet, toStringMetadata } from '../api.ts'
|
|
6
6
|
import { findRepairDataset } from '../db/find-repair-dataset.ts'
|
|
7
7
|
import { repairUpdate } from '../db/repair-update.ts'
|
|
8
8
|
import type { RepairSelect } from '../local-schema.ts'
|
|
9
|
-
import type {
|
|
9
|
+
import type { IndexerApi, LocalDatabase, WalletClient } from '../types.ts'
|
|
10
10
|
import { hashLink } from '../utils.ts'
|
|
11
11
|
|
|
12
12
|
export type EnsureRepairDatasetOptions = {
|
|
13
13
|
source: string
|
|
14
14
|
localDb: LocalDatabase
|
|
15
|
-
|
|
15
|
+
api: IndexerApi
|
|
16
16
|
client: WalletClient
|
|
17
17
|
repair: RepairSelect
|
|
18
18
|
payer: Address
|
|
@@ -23,14 +23,7 @@ export type EnsureRepairDatasetOptions = {
|
|
|
23
23
|
*
|
|
24
24
|
* @param options - The options for ensuring the repair dataset.
|
|
25
25
|
*/
|
|
26
|
-
export async function ensureRepairDataset({
|
|
27
|
-
source,
|
|
28
|
-
localDb,
|
|
29
|
-
indexerDb,
|
|
30
|
-
client,
|
|
31
|
-
repair,
|
|
32
|
-
payer,
|
|
33
|
-
}: EnsureRepairDatasetOptions) {
|
|
26
|
+
export async function ensureRepairDataset({ source, localDb, api, client, repair, payer }: EnsureRepairDatasetOptions) {
|
|
34
27
|
const log = p.taskLog({
|
|
35
28
|
title: 'Ensuring repair dataset',
|
|
36
29
|
})
|
|
@@ -43,7 +36,7 @@ export async function ensureRepairDataset({
|
|
|
43
36
|
let datasetId: bigint | null = null
|
|
44
37
|
// check if dataset already exists
|
|
45
38
|
const existingDatasetId = await findRepairDataset({
|
|
46
|
-
|
|
39
|
+
api,
|
|
47
40
|
providerId: repair.targetProviderId,
|
|
48
41
|
payer,
|
|
49
42
|
source,
|
|
@@ -83,13 +76,7 @@ export async function ensureRepairDataset({
|
|
|
83
76
|
*
|
|
84
77
|
* @param options - The options for ensuring the replication dataset.
|
|
85
78
|
*/
|
|
86
|
-
export async function ensureReplicateDataset({
|
|
87
|
-
localDb,
|
|
88
|
-
indexerDb,
|
|
89
|
-
client,
|
|
90
|
-
repair,
|
|
91
|
-
payer,
|
|
92
|
-
}: EnsureRepairDatasetOptions) {
|
|
79
|
+
export async function ensureReplicateDataset({ localDb, api, client, repair, payer }: EnsureRepairDatasetOptions) {
|
|
93
80
|
const log = p.taskLog({
|
|
94
81
|
title: 'Ensuring replication dataset',
|
|
95
82
|
})
|
|
@@ -109,14 +96,7 @@ export async function ensureReplicateDataset({
|
|
|
109
96
|
return repair.targetDataSetId
|
|
110
97
|
}
|
|
111
98
|
|
|
112
|
-
const
|
|
113
|
-
const sourceDataSet = await indexerDb.query.dataSets.findFirst({
|
|
114
|
-
where: eq(schema.dataSets.dataSetId, repair.repairDataSetId),
|
|
115
|
-
columns: {
|
|
116
|
-
metadata: true,
|
|
117
|
-
withCdn: true,
|
|
118
|
-
},
|
|
119
|
-
})
|
|
99
|
+
const sourceDataSet = await getDataSet(api, repair.repairDataSetId)
|
|
120
100
|
|
|
121
101
|
if (!sourceDataSet) {
|
|
122
102
|
throw new Error(`Source dataset ${repair.repairDataSetId} not found`)
|
|
@@ -126,8 +106,8 @@ export async function ensureReplicateDataset({
|
|
|
126
106
|
payee: provider.payee,
|
|
127
107
|
serviceURL: provider.pdp.serviceURL,
|
|
128
108
|
payer,
|
|
129
|
-
cdn: sourceDataSet.withCdn,
|
|
130
|
-
metadata: sourceDataSet.metadata
|
|
109
|
+
cdn: sourceDataSet.withCdn ?? false,
|
|
110
|
+
metadata: sourceDataSet.metadata ? toStringMetadata(sourceDataSet.metadata) : undefined,
|
|
131
111
|
})
|
|
132
112
|
log.message(`Waiting for data to be created at ${provider.pdp.serviceURL} ${hashLink(txHash, client.chain)}...`)
|
|
133
113
|
const waitForResult = await SP.waitForCreateDataSet({
|
package/src/types.ts
CHANGED
|
@@ -1,30 +1,32 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApiClient } from '@filecoin-project/api-client'
|
|
2
|
+
import type { FilecoinChain } from '@filoz/synapse-core/chains'
|
|
2
3
|
import type { Client } from '@libsql/client'
|
|
3
4
|
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
|
|
4
|
-
import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
|
|
5
5
|
import type { z } from 'incur'
|
|
6
|
-
import type { Pool } from 'pg'
|
|
7
6
|
import type { Account, Address, Hex, Transport, Client as ViemClient } from 'viem'
|
|
8
|
-
import type
|
|
7
|
+
import type { Network } from './api.ts'
|
|
9
8
|
import type * as localSchema from './local-schema.ts'
|
|
10
9
|
import type { contextSchema } from './middleware.ts'
|
|
11
10
|
export type LocalDatabase = LibSQLDatabase<typeof localSchema> & {
|
|
12
11
|
$client: Client
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
/**
|
|
15
|
+
* filecoin-api client bound to the configured network.
|
|
16
|
+
*/
|
|
17
|
+
export type IndexerApi = {
|
|
18
|
+
client: ApiClient
|
|
19
|
+
network: Network
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export interface Config {
|
|
20
23
|
privateKey: Hex
|
|
21
|
-
|
|
22
|
-
indexerCalibrationUrl: string
|
|
24
|
+
apiUrl: string
|
|
23
25
|
chainId: number
|
|
24
26
|
dbPath: string
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
export type WalletClient = ViemClient<Transport,
|
|
29
|
+
export type WalletClient = ViemClient<Transport, FilecoinChain, Account>
|
|
28
30
|
|
|
29
31
|
export type Context = z.infer<typeof contextSchema>
|
|
30
32
|
|
package/src/utils.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type FilecoinChain, getChain } from '@filoz/synapse-core/chains'
|
|
2
2
|
import * as Piece from '@filoz/synapse-core/piece'
|
|
3
|
-
import
|
|
3
|
+
import * as SP from '@filoz/synapse-core/sp'
|
|
4
4
|
import { getTableColumns, inArray, type SQL, sql } from 'drizzle-orm'
|
|
5
5
|
import { drizzle } from 'drizzle-orm/libsql'
|
|
6
|
-
import type { PgTable } from 'drizzle-orm/pg-core'
|
|
7
6
|
import type { SQLiteTable } from 'drizzle-orm/sqlite-core'
|
|
8
7
|
import { z } from 'incur'
|
|
9
8
|
import { Conf } from 'iso-conf'
|
|
@@ -20,8 +19,7 @@ import type { LocalDatabase, WalletClient } from './types.ts'
|
|
|
20
19
|
|
|
21
20
|
export const configSchema = z.object({
|
|
22
21
|
privateKey: z.string().optional(),
|
|
23
|
-
|
|
24
|
-
indexerCalibrationUrl: z.url().optional(),
|
|
22
|
+
apiUrl: z.url().optional(),
|
|
25
23
|
chainId: z.number().optional(),
|
|
26
24
|
dbPath: z.string().optional(),
|
|
27
25
|
source: z.string().optional(),
|
|
@@ -168,7 +166,7 @@ export async function migrateLocalDatabase(db: LocalDatabase) {
|
|
|
168
166
|
* @param chain - The chain to use
|
|
169
167
|
* @returns The link
|
|
170
168
|
*/
|
|
171
|
-
export function hashLink(hash: string, chain:
|
|
169
|
+
export function hashLink(hash: string, chain: FilecoinChain) {
|
|
172
170
|
const link = terminalLink(hash, `${chain.blockExplorers?.default?.url}/tx/${hash}`)
|
|
173
171
|
return link
|
|
174
172
|
}
|
|
@@ -273,7 +271,7 @@ export async function findPieceOnProviders(providers: string[], pieceCid: string
|
|
|
273
271
|
return result
|
|
274
272
|
}
|
|
275
273
|
|
|
276
|
-
export const buildConflictUpdateColumns = <T extends
|
|
274
|
+
export const buildConflictUpdateColumns = <T extends SQLiteTable, Q extends keyof T['_']['columns']>(
|
|
277
275
|
table: T,
|
|
278
276
|
columns?: Q[]
|
|
279
277
|
) => {
|
|
@@ -329,3 +327,69 @@ export function operationsToPullPieces(operations: OperationSelect[]) {
|
|
|
329
327
|
}
|
|
330
328
|
return pieces
|
|
331
329
|
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Convert operations to addPieces inputs. Repairs and second copies add pieces without metadata.
|
|
333
|
+
*/
|
|
334
|
+
export function operationsToAddPieces(operations: OperationSelect[], withMetadata: boolean) {
|
|
335
|
+
return operations.map((operation) => ({
|
|
336
|
+
pieceCid: Piece.from(operation.cid),
|
|
337
|
+
metadata: withMetadata ? operation.metadata : undefined,
|
|
338
|
+
}))
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Take the longest prefix of operations that fits in one addPieces message.
|
|
343
|
+
* Always returns at least one operation so an oversized or invalid piece fails in the worker instead of stalling the queue.
|
|
344
|
+
*/
|
|
345
|
+
export function fitAddPiecesBatch({
|
|
346
|
+
operations,
|
|
347
|
+
dataSetId,
|
|
348
|
+
chain,
|
|
349
|
+
withMetadata,
|
|
350
|
+
}: {
|
|
351
|
+
operations: OperationSelect[]
|
|
352
|
+
dataSetId: bigint
|
|
353
|
+
chain: FilecoinChain
|
|
354
|
+
withMetadata: boolean
|
|
355
|
+
}): OperationSelect[] {
|
|
356
|
+
// Malformed CIDs throw; stop before the first one so it fails alone in the worker.
|
|
357
|
+
const pieces: SP.LimiterPiece[] = []
|
|
358
|
+
for (const operation of operations) {
|
|
359
|
+
try {
|
|
360
|
+
pieces.push(...operationsToAddPieces([operation], withMetadata))
|
|
361
|
+
} catch {
|
|
362
|
+
break
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (pieces.length === 0) {
|
|
366
|
+
return operations.slice(0, 1)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const fits = (count: number) => {
|
|
370
|
+
try {
|
|
371
|
+
return SP.addPiecesFits({
|
|
372
|
+
kind: 'addPieces',
|
|
373
|
+
dataSetId,
|
|
374
|
+
legacyPieceStorageIdLimit: chain.legacyPieceStorageIdLimit,
|
|
375
|
+
pieces: pieces.slice(0, count),
|
|
376
|
+
})
|
|
377
|
+
} catch {
|
|
378
|
+
// Invalid metadata throws; isolate that piece so addPieces fails it in the worker.
|
|
379
|
+
return false
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Calldata only grows as pieces are added, so binary search the largest prefix that fits.
|
|
384
|
+
let low = 1
|
|
385
|
+
let high = pieces.length
|
|
386
|
+
while (low < high) {
|
|
387
|
+
const mid = Math.ceil((low + high) / 2)
|
|
388
|
+
if (fits(mid)) {
|
|
389
|
+
low = mid
|
|
390
|
+
} else {
|
|
391
|
+
high = mid - 1
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return operations.slice(0, low)
|
|
395
|
+
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { IndexerDatabase, RepairProvider } from '../types.ts';
|
|
2
|
-
export type FindProvidersByCidOptions = {
|
|
3
|
-
indexerDb: IndexerDatabase;
|
|
4
|
-
cids: readonly string[];
|
|
5
|
-
blockNumber: bigint;
|
|
6
|
-
};
|
|
7
|
-
export type ProvidersByCid = Record<string, RepairProvider[]>;
|
|
8
|
-
export declare function findProvidersByCid({ indexerDb, cids, blockNumber, }: FindProvidersByCidOptions): Promise<ProvidersByCid>;
|
|
9
|
-
//# sourceMappingURL=find-providers-by-cid.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"find-providers-by-cid.d.ts","sourceRoot":"","sources":["../../../src/db/find-providers-by-cid.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAElE,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,EAAE,eAAe,CAAA;IAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAKD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,CAAA;AAS7D,wBAAsB,kBAAkB,CAAC,EACvC,SAAS,EACT,IAAI,EACJ,WAAW,GACZ,EAAE,yBAAyB,GAAG,OAAO,CAAC,cAAc,CAAC,CA4CrD"}
|