@filoz/repair-cli 0.0.1
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/src/cli.d.ts +3 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +19 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/commands/datasets.d.ts +9 -0
- package/dist/src/commands/datasets.d.ts.map +1 -0
- package/dist/src/commands/datasets.js +59 -0
- package/dist/src/commands/datasets.js.map +1 -0
- package/dist/src/commands/providers.d.ts +9 -0
- package/dist/src/commands/providers.d.ts.map +1 -0
- package/dist/src/commands/providers.js +84 -0
- package/dist/src/commands/providers.js.map +1 -0
- package/dist/src/commands/repair.d.ts +9 -0
- package/dist/src/commands/repair.d.ts.map +1 -0
- package/dist/src/commands/repair.js +170 -0
- package/dist/src/commands/repair.js.map +1 -0
- package/dist/src/commands/setup.d.ts +11 -0
- package/dist/src/commands/setup.d.ts.map +1 -0
- package/dist/src/commands/setup.js +127 -0
- package/dist/src/commands/setup.js.map +1 -0
- package/dist/src/commands/wallet.d.ts +9 -0
- package/dist/src/commands/wallet.d.ts.map +1 -0
- package/dist/src/commands/wallet.js +150 -0
- package/dist/src/commands/wallet.js.map +1 -0
- package/dist/src/db/get-pieces.d.ts +23 -0
- package/dist/src/db/get-pieces.d.ts.map +1 -0
- package/dist/src/db/get-pieces.js +84 -0
- package/dist/src/db/get-pieces.js.map +1 -0
- package/dist/src/db/get-providers-by-cid.d.ts +10 -0
- package/dist/src/db/get-providers-by-cid.d.ts.map +1 -0
- package/dist/src/db/get-providers-by-cid.js +45 -0
- package/dist/src/db/get-providers-by-cid.js.map +1 -0
- package/dist/src/db/get-repair-dataset.d.ts +9 -0
- package/dist/src/db/get-repair-dataset.d.ts.map +1 -0
- package/dist/src/db/get-repair-dataset.js +15 -0
- package/dist/src/db/get-repair-dataset.js.map +1 -0
- package/dist/src/db/get-repair-provider.d.ts +6 -0
- package/dist/src/db/get-repair-provider.d.ts.map +1 -0
- package/dist/src/db/get-repair-provider.js +28 -0
- package/dist/src/db/get-repair-provider.js.map +1 -0
- package/dist/src/db/get-target-dataset.d.ts +7 -0
- package/dist/src/db/get-target-dataset.d.ts.map +1 -0
- package/dist/src/db/get-target-dataset.js +27 -0
- package/dist/src/db/get-target-dataset.js.map +1 -0
- package/dist/src/db/repair-create.d.ts +7 -0
- package/dist/src/db/repair-create.d.ts.map +1 -0
- package/dist/src/db/repair-create.js +69 -0
- package/dist/src/db/repair-create.js.map +1 -0
- package/dist/src/db/repair-delete.d.ts +11 -0
- package/dist/src/db/repair-delete.d.ts.map +1 -0
- package/dist/src/db/repair-delete.js +22 -0
- package/dist/src/db/repair-delete.js.map +1 -0
- package/dist/src/db/repair-update.d.ts +10 -0
- package/dist/src/db/repair-update.d.ts.map +1 -0
- package/dist/src/db/repair-update.js +13 -0
- package/dist/src/db/repair-update.js.map +1 -0
- package/dist/src/db/sync-pieces-onchain.d.ts +10 -0
- package/dist/src/db/sync-pieces-onchain.d.ts.map +1 -0
- package/dist/src/db/sync-pieces-onchain.js +35 -0
- package/dist/src/db/sync-pieces-onchain.js.map +1 -0
- package/dist/src/db/update-operation.d.ts +11 -0
- package/dist/src/db/update-operation.d.ts.map +1 -0
- package/dist/src/db/update-operation.js +14 -0
- package/dist/src/db/update-operation.js.map +1 -0
- package/dist/src/db/upsert-operations.d.ts +8 -0
- package/dist/src/db/upsert-operations.d.ts.map +1 -0
- package/dist/src/db/upsert-operations.js +13 -0
- package/dist/src/db/upsert-operations.js.map +1 -0
- package/dist/src/error.d.ts +14 -0
- package/dist/src/error.d.ts.map +1 -0
- package/dist/src/error.js +27 -0
- package/dist/src/error.js.map +1 -0
- package/dist/src/indexer-schema.d.ts +549 -0
- package/dist/src/indexer-schema.d.ts.map +1 -0
- package/dist/src/indexer-schema.js +56 -0
- package/dist/src/indexer-schema.js.map +1 -0
- package/dist/src/local-schema.d.ts +456 -0
- package/dist/src/local-schema.d.ts.map +1 -0
- package/dist/src/local-schema.js +63 -0
- package/dist/src/local-schema.js.map +1 -0
- package/dist/src/middleware.d.ts +19 -0
- package/dist/src/middleware.d.ts.map +1 -0
- package/dist/src/middleware.js +28 -0
- package/dist/src/middleware.js.map +1 -0
- package/dist/src/pipeline/create-datasets.d.ts +10 -0
- package/dist/src/pipeline/create-datasets.d.ts.map +1 -0
- package/dist/src/pipeline/create-datasets.js +48 -0
- package/dist/src/pipeline/create-datasets.js.map +1 -0
- package/dist/src/pipeline/pull.d.ts +30 -0
- package/dist/src/pipeline/pull.d.ts.map +1 -0
- package/dist/src/pipeline/pull.js +169 -0
- package/dist/src/pipeline/pull.js.map +1 -0
- package/dist/src/types.d.ts +34 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils.d.ts +11945 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +121 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +135 -0
- package/readme.md +250 -0
- package/src/cli.ts +20 -0
- package/src/commands/datasets.ts +62 -0
- package/src/commands/providers.ts +99 -0
- package/src/commands/repair.ts +177 -0
- package/src/commands/setup.ts +142 -0
- package/src/commands/wallet.ts +159 -0
- package/src/db/get-pieces.ts +189 -0
- package/src/db/get-providers-by-cid.ts +75 -0
- package/src/db/get-repair-dataset.ts +44 -0
- package/src/db/get-repair-provider.ts +47 -0
- package/src/db/get-target-dataset.ts +47 -0
- package/src/db/repair-create.ts +101 -0
- package/src/db/repair-delete.ts +39 -0
- package/src/db/repair-update.ts +20 -0
- package/src/db/sync-pieces-onchain.ts +53 -0
- package/src/db/update-operation.ts +26 -0
- package/src/db/upsert-operations.ts +23 -0
- package/src/error.ts +33 -0
- package/src/indexer-schema.ts +77 -0
- package/src/local-schema.ts +91 -0
- package/src/middleware.ts +34 -0
- package/src/pipeline/create-datasets.ts +70 -0
- package/src/pipeline/pull.ts +255 -0
- package/src/types.ts +41 -0
- package/src/utils.ts +190 -0
package/src/error.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export class NoAlternateProviderError extends Error {
|
|
2
|
+
readonly providerId?: bigint
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param providerId - When set, the explicit target provider was not found or inactive.
|
|
6
|
+
*/
|
|
7
|
+
constructor(providerId?: bigint) {
|
|
8
|
+
super(providerId == null ? 'No alternate provider found' : `Target provider ${providerId} not found or inactive`)
|
|
9
|
+
this.name = 'NoAlternateProviderError'
|
|
10
|
+
this.providerId = providerId
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class RepairCreationError extends Error {
|
|
15
|
+
constructor(message = 'Failed to create repair row') {
|
|
16
|
+
super(message)
|
|
17
|
+
this.name = 'RepairCreationError'
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class RepairNotFoundError extends Error {
|
|
22
|
+
constructor(repairId: number) {
|
|
23
|
+
super(`Repair ${repairId} not found`)
|
|
24
|
+
this.name = 'RepairNotFoundError'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class MissingRepairDataSetError extends Error {
|
|
29
|
+
constructor() {
|
|
30
|
+
super('Missing repair dataset ID')
|
|
31
|
+
this.name = 'MissingRepairDataSetError'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { relations } from 'drizzle-orm'
|
|
2
|
+
import { bigint, boolean, index, jsonb, pgSchema, primaryKey, text } from 'drizzle-orm/pg-core'
|
|
3
|
+
import type { Address } from 'viem'
|
|
4
|
+
|
|
5
|
+
export type JsonRecord = Record<string, string>
|
|
6
|
+
|
|
7
|
+
const schema = pgSchema('early-repair')
|
|
8
|
+
|
|
9
|
+
export const providers = schema.table(
|
|
10
|
+
'providers',
|
|
11
|
+
{
|
|
12
|
+
providerId: bigint('provider_id', { mode: 'bigint' }).primaryKey(),
|
|
13
|
+
providerAddress: text('provider_address').$type<Address>(),
|
|
14
|
+
name: text('name'),
|
|
15
|
+
serviceUrl: text('service_url'),
|
|
16
|
+
providerActive: boolean('provider_active').notNull(),
|
|
17
|
+
pdpProductActive: boolean('pdp_product_active').notNull(),
|
|
18
|
+
approved: boolean('approved').notNull().default(false),
|
|
19
|
+
endorsed: boolean('endorsed').notNull().default(false),
|
|
20
|
+
createdAtBlock: bigint('created_at_block', { mode: 'bigint' }),
|
|
21
|
+
updatedAtBlock: bigint('updated_at_block', { mode: 'bigint' }).notNull(),
|
|
22
|
+
},
|
|
23
|
+
(table) => [index('providers_provider_address_idx').on(table.providerAddress)]
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
export const providersRelations = relations(providers, ({ many }) => ({
|
|
27
|
+
dataSets: many(dataSets),
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
export const dataSets = schema.table(
|
|
31
|
+
'data_sets',
|
|
32
|
+
{
|
|
33
|
+
dataSetId: bigint('data_set_id', { mode: 'bigint' }).primaryKey(),
|
|
34
|
+
providerId: bigint('provider_id', { mode: 'bigint' }).notNull(),
|
|
35
|
+
metadata: jsonb('metadata').$type<JsonRecord | null>(),
|
|
36
|
+
payer: text('payer').notNull(),
|
|
37
|
+
source: text('source'),
|
|
38
|
+
withCdn: boolean('with_cdn').notNull(),
|
|
39
|
+
withIpfsIndexing: boolean('with_ipfs_indexing').notNull(),
|
|
40
|
+
pdpEndEpoch: bigint('pdp_end_epoch', { mode: 'bigint' }).notNull(),
|
|
41
|
+
deleted: boolean('deleted').notNull(),
|
|
42
|
+
createdAtBlock: bigint('created_at_block', { mode: 'bigint' }).notNull(),
|
|
43
|
+
updatedAtBlock: bigint('updated_at_block', { mode: 'bigint' }).notNull(),
|
|
44
|
+
},
|
|
45
|
+
(table) => [index('data_sets_provider_id_idx').on(table.providerId)]
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
export const dataSetsRelations = relations(dataSets, ({ one, many }) => ({
|
|
49
|
+
provider: one(providers, {
|
|
50
|
+
fields: [dataSets.providerId],
|
|
51
|
+
references: [providers.providerId],
|
|
52
|
+
}),
|
|
53
|
+
pieces: many(pieces),
|
|
54
|
+
}))
|
|
55
|
+
|
|
56
|
+
export const pieces = schema.table(
|
|
57
|
+
'pieces',
|
|
58
|
+
{
|
|
59
|
+
dataSetId: bigint('data_set_id', { mode: 'bigint' }).notNull(),
|
|
60
|
+
pieceId: bigint('piece_id', { mode: 'bigint' }).notNull(),
|
|
61
|
+
cid: text('cid').notNull(),
|
|
62
|
+
rawSize: bigint('raw_size', { mode: 'bigint' }).notNull(),
|
|
63
|
+
metadata: jsonb('metadata').$type<JsonRecord | null>(),
|
|
64
|
+
removed: boolean('removed').notNull(),
|
|
65
|
+
addedAtBlock: bigint('added_at_block', { mode: 'bigint' }).notNull(),
|
|
66
|
+
removedAtBlock: bigint('removed_at_block', { mode: 'bigint' }),
|
|
67
|
+
updatedAtBlock: bigint('updated_at_block', { mode: 'bigint' }).notNull(),
|
|
68
|
+
},
|
|
69
|
+
(table) => [primaryKey({ columns: [table.dataSetId, table.pieceId] }), index('pieces_cid_idx').on(table.cid)]
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
export const piecesRelations = relations(pieces, ({ one }) => ({
|
|
73
|
+
dataSet: one(dataSets, {
|
|
74
|
+
fields: [pieces.dataSetId],
|
|
75
|
+
references: [dataSets.dataSetId],
|
|
76
|
+
}),
|
|
77
|
+
}))
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { MetadataObject } from '@filoz/synapse-core'
|
|
2
|
+
import type * as SP from '@filoz/synapse-core/sp'
|
|
3
|
+
import { relations } from 'drizzle-orm'
|
|
4
|
+
import type { AnySQLiteColumn } from 'drizzle-orm/sqlite-core'
|
|
5
|
+
import * as t from 'drizzle-orm/sqlite-core'
|
|
6
|
+
import { customType, sqliteTable as table } from 'drizzle-orm/sqlite-core'
|
|
7
|
+
import * as Json from 'iso-base/json'
|
|
8
|
+
|
|
9
|
+
export type RepairStatus = 'pending' | 'completed' | 'failed'
|
|
10
|
+
export type OperationStatus = 'pending' | 'completed' | 'failed' | 'skipped'
|
|
11
|
+
export type OperationType = 'create_dataset' | 'add_piece'
|
|
12
|
+
|
|
13
|
+
export type OperationResult = Omit<
|
|
14
|
+
SP.AddPiecesSuccess,
|
|
15
|
+
'txStatus' | 'addMessageOk' | 'piecesAdded' | 'pieceCount' | 'confirmedPieceIds'
|
|
16
|
+
>
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Custom type for JSON
|
|
20
|
+
* It will be used to store JSON data in the database
|
|
21
|
+
*/
|
|
22
|
+
export const jsonType = customType<{ data: unknown }>({
|
|
23
|
+
dataType() {
|
|
24
|
+
return 'text'
|
|
25
|
+
},
|
|
26
|
+
toDriver(value) {
|
|
27
|
+
return Json.stringify(value)
|
|
28
|
+
},
|
|
29
|
+
fromDriver(value) {
|
|
30
|
+
return Json.parse(value as string)
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export const bigintType = customType<{ data: bigint }>({
|
|
35
|
+
dataType() {
|
|
36
|
+
return 'text'
|
|
37
|
+
},
|
|
38
|
+
toDriver(value) {
|
|
39
|
+
return value.toString()
|
|
40
|
+
},
|
|
41
|
+
fromDriver(value) {
|
|
42
|
+
return BigInt(value as string)
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
export type RepairInsert = typeof repairs.$inferInsert
|
|
47
|
+
export type RepairSelect = typeof repairs.$inferSelect
|
|
48
|
+
export type RepairUpdate = Partial<RepairInsert>
|
|
49
|
+
|
|
50
|
+
export const repairs = table('repairs', {
|
|
51
|
+
id: t.int().primaryKey({ autoIncrement: true }),
|
|
52
|
+
status: t.text().$type<RepairStatus>().notNull().default('pending'),
|
|
53
|
+
repairProviderId: bigintType('repair_provider_id').notNull(),
|
|
54
|
+
targetProviderId: bigintType('target_provider_id').notNull(),
|
|
55
|
+
targetProviderUrl: t.text('target_provider_url').notNull(),
|
|
56
|
+
targetDataSetId: bigintType('target_data_set_id'),
|
|
57
|
+
blockNumber: bigintType('block_number').notNull(),
|
|
58
|
+
createdAt: t.integer('created_at').notNull(),
|
|
59
|
+
updatedAt: t.integer('updated_at').notNull(),
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
export type OperationInsert = typeof operations.$inferInsert
|
|
63
|
+
export type OperationSelect = typeof operations.$inferSelect
|
|
64
|
+
|
|
65
|
+
export const operations = table('operations', {
|
|
66
|
+
id: t.int().primaryKey({ autoIncrement: true }),
|
|
67
|
+
repairId: t
|
|
68
|
+
.int('repair_id')
|
|
69
|
+
.references((): AnySQLiteColumn => repairs.id)
|
|
70
|
+
.notNull(),
|
|
71
|
+
type: t.text().$type<OperationType>().notNull(),
|
|
72
|
+
status: t.text().$type<OperationStatus>().notNull().default('pending'),
|
|
73
|
+
cid: t.text().notNull(),
|
|
74
|
+
metadata: jsonType().$type<MetadataObject>().notNull(),
|
|
75
|
+
alternateProvider: t.text('alternate_provider').notNull(),
|
|
76
|
+
result: jsonType().$type<OperationResult>(),
|
|
77
|
+
error: t.text(),
|
|
78
|
+
createdAt: t.integer('created_at').notNull(),
|
|
79
|
+
updatedAt: t.integer('updated_at').notNull(),
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
export const repairRelations = relations(repairs, ({ many }) => ({
|
|
83
|
+
operations: many(operations),
|
|
84
|
+
}))
|
|
85
|
+
|
|
86
|
+
export const operationRelations = relations(operations, ({ one }) => ({
|
|
87
|
+
repair: one(repairs, {
|
|
88
|
+
fields: [operations.repairId],
|
|
89
|
+
references: [repairs.id],
|
|
90
|
+
}),
|
|
91
|
+
}))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Chain } from '@filoz/synapse-core/chains'
|
|
2
|
+
import { drizzle as drizzlePostgres } from 'drizzle-orm/node-postgres'
|
|
3
|
+
import { middleware, z } from 'incur'
|
|
4
|
+
import type { Account, Client, Transport } from 'viem'
|
|
5
|
+
import * as indexerSchema from './indexer-schema.ts'
|
|
6
|
+
import type { IndexerDatabase, LocalDatabase } from './types.ts'
|
|
7
|
+
import { config, createLocalDatabase, getClient } from './utils.ts'
|
|
8
|
+
|
|
9
|
+
export const contextSchema = z.object({
|
|
10
|
+
indexerDb: z.custom<IndexerDatabase>(),
|
|
11
|
+
localDb: z.custom<LocalDatabase>(),
|
|
12
|
+
config: z.custom<typeof config>(),
|
|
13
|
+
client: z.custom<Client<Transport, Chain, Account>>(),
|
|
14
|
+
chain: z.custom<Chain>(),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export const contextMiddleware = middleware<typeof contextSchema>(async (c, next) => {
|
|
18
|
+
const { dbPath, chainId, indexerMainnetUrl, indexerCalibrationUrl } = config.store
|
|
19
|
+
const localDb = await createLocalDatabase(dbPath)
|
|
20
|
+
const indexerDb = drizzlePostgres(chainId === 314 ? indexerMainnetUrl : indexerCalibrationUrl, {
|
|
21
|
+
schema: indexerSchema,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const { client, chain } = getClient(chainId)
|
|
25
|
+
c.set('localDb', localDb)
|
|
26
|
+
c.set('indexerDb', indexerDb)
|
|
27
|
+
c.set('config', config)
|
|
28
|
+
c.set('client', client)
|
|
29
|
+
c.set('chain', chain)
|
|
30
|
+
await next()
|
|
31
|
+
|
|
32
|
+
localDb.$client.close()
|
|
33
|
+
await indexerDb.$client.end()
|
|
34
|
+
})
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as p from '@clack/prompts'
|
|
2
|
+
import * as SP from '@filoz/synapse-core/sp'
|
|
3
|
+
import { getPDPProvider } from '@filoz/synapse-core/sp-registry'
|
|
4
|
+
import { getRepairDataset } from '../db/get-repair-dataset.ts'
|
|
5
|
+
import { repairUpdate } from '../db/repair-update.ts'
|
|
6
|
+
import type { RepairSelect } from '../local-schema.ts'
|
|
7
|
+
import type { IndexerDatabase, LocalDatabase, WalletClient } from '../types.ts'
|
|
8
|
+
import { getRepairDatasetMetadata, hashLink } from '../utils.ts'
|
|
9
|
+
|
|
10
|
+
export type EnsureRepairDatasetOptions = {
|
|
11
|
+
localDb: LocalDatabase
|
|
12
|
+
indexerDb: IndexerDatabase
|
|
13
|
+
client: WalletClient
|
|
14
|
+
repair: RepairSelect
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Ensure the repair dataset exists by creating it if it doesn't.
|
|
19
|
+
*
|
|
20
|
+
* @param options - The options for ensuring the repair dataset.
|
|
21
|
+
* @returns {Promise<bigint>} - The ID of the created dataset.
|
|
22
|
+
*/
|
|
23
|
+
export async function ensureRepairDataset({
|
|
24
|
+
localDb,
|
|
25
|
+
indexerDb,
|
|
26
|
+
client,
|
|
27
|
+
repair,
|
|
28
|
+
}: EnsureRepairDatasetOptions): Promise<bigint> {
|
|
29
|
+
const log = p.taskLog({
|
|
30
|
+
title: 'Ensuring repair dataset',
|
|
31
|
+
})
|
|
32
|
+
const provider = await getPDPProvider(client, {
|
|
33
|
+
providerId: repair.targetProviderId,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
if (!provider) throw new Error(`Target provider ${repair.targetProviderId} not found or inactive`)
|
|
37
|
+
|
|
38
|
+
let datasetId: bigint | null = null
|
|
39
|
+
// check if dataset already exists
|
|
40
|
+
const existingDatasetId = await getRepairDataset({
|
|
41
|
+
indexerDb,
|
|
42
|
+
providerId: repair.targetProviderId,
|
|
43
|
+
payer: client.account.address,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (existingDatasetId) {
|
|
47
|
+
datasetId = existingDatasetId
|
|
48
|
+
log.success(`Data set #${datasetId} already exists at ${provider.pdp.serviceURL}`)
|
|
49
|
+
} else {
|
|
50
|
+
const { txHash, statusUrl } = await SP.createDataSet(client, {
|
|
51
|
+
payee: provider.payee,
|
|
52
|
+
serviceURL: provider.pdp.serviceURL,
|
|
53
|
+
payer: client.account.address,
|
|
54
|
+
cdn: false,
|
|
55
|
+
metadata: getRepairDatasetMetadata(),
|
|
56
|
+
})
|
|
57
|
+
log.message(`Waiting for data to be created at ${provider.pdp.serviceURL} ${hashLink(txHash, client.chain)}...`)
|
|
58
|
+
const waitForResult = await SP.waitForCreateDataSet({
|
|
59
|
+
statusUrl,
|
|
60
|
+
})
|
|
61
|
+
datasetId = waitForResult.dataSetId
|
|
62
|
+
log.success(`Data set #${datasetId} created at ${provider.pdp.serviceURL}`)
|
|
63
|
+
}
|
|
64
|
+
await repairUpdate({
|
|
65
|
+
localDb,
|
|
66
|
+
repairId: repair.id,
|
|
67
|
+
targetDataSetId: datasetId,
|
|
68
|
+
})
|
|
69
|
+
return datasetId
|
|
70
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { taskLog } from '@clack/prompts'
|
|
2
|
+
import * as Piece from '@filoz/synapse-core/piece'
|
|
3
|
+
import { createPieceUrlPDP } from '@filoz/synapse-core/piece'
|
|
4
|
+
import * as SP from '@filoz/synapse-core/sp'
|
|
5
|
+
import { and, asc, eq, gt, inArray } from 'drizzle-orm'
|
|
6
|
+
import PQueue from 'p-queue'
|
|
7
|
+
import { getTargetDataset } from '../db/get-target-dataset.ts'
|
|
8
|
+
import { syncPiecesOnchain } from '../db/sync-pieces-onchain.ts'
|
|
9
|
+
import { updateOperation } from '../db/update-operation.ts'
|
|
10
|
+
import { upsertOperations } from '../db/upsert-operations.ts'
|
|
11
|
+
import type { OperationSelect, RepairSelect } from '../local-schema.ts'
|
|
12
|
+
import type { IndexerDatabase, LocalDatabase, WalletClient } from '../types.ts'
|
|
13
|
+
import { hashLink } from '../utils.ts'
|
|
14
|
+
|
|
15
|
+
/** Pending `add_piece` operations batched for a single pull job. */
|
|
16
|
+
export type PullPiecesBatch = {
|
|
17
|
+
operations: OperationSelect[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type RunPullPiecesPhaseOptions = {
|
|
21
|
+
localDb: LocalDatabase
|
|
22
|
+
indexerDb: IndexerDatabase
|
|
23
|
+
repair: RepairSelect
|
|
24
|
+
concurrency: number
|
|
25
|
+
batchSize: number
|
|
26
|
+
client: WalletClient
|
|
27
|
+
reset: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Mock pull worker: logs each batch and its piece CIDs. */
|
|
31
|
+
export function createPullPiecesWorker({
|
|
32
|
+
localDb,
|
|
33
|
+
indexerDb,
|
|
34
|
+
repair,
|
|
35
|
+
client,
|
|
36
|
+
state,
|
|
37
|
+
log,
|
|
38
|
+
}: {
|
|
39
|
+
localDb: LocalDatabase
|
|
40
|
+
indexerDb: IndexerDatabase
|
|
41
|
+
repair: RepairSelect
|
|
42
|
+
client: WalletClient
|
|
43
|
+
state: {
|
|
44
|
+
totalBatches: number
|
|
45
|
+
totalOperations: number
|
|
46
|
+
completedOperations: number
|
|
47
|
+
failedOperations: number
|
|
48
|
+
}
|
|
49
|
+
log: ReturnType<typeof taskLog>
|
|
50
|
+
}) {
|
|
51
|
+
return async (batch: PullPiecesBatch, batchNumber: number) => {
|
|
52
|
+
let completedCids = 0
|
|
53
|
+
let failedCids = 0
|
|
54
|
+
const cidToOperation = new Map<string, OperationSelect>()
|
|
55
|
+
|
|
56
|
+
const spin = log.group(`Batch ${batchNumber}/${state.totalBatches}`)
|
|
57
|
+
spin.message(`Pull 0 completed, 0 failed`)
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const dataset = await getTargetDataset({ localDb, repairId: repair.id, client })
|
|
61
|
+
|
|
62
|
+
for (const operation of batch.operations) {
|
|
63
|
+
cidToOperation.set(operation.cid, operation)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// sync pieces onchain to avoid duplicates
|
|
67
|
+
const completedOperations1 = await syncPiecesOnchain({
|
|
68
|
+
indexerDb,
|
|
69
|
+
localDb,
|
|
70
|
+
dataSetId: dataset.dataSetId,
|
|
71
|
+
cidToOperation,
|
|
72
|
+
})
|
|
73
|
+
state.completedOperations += completedOperations1
|
|
74
|
+
completedCids += completedOperations1
|
|
75
|
+
|
|
76
|
+
// create pull pieces
|
|
77
|
+
const pullPieces: SP.PullPieceInput[] = []
|
|
78
|
+
for (const [cid, operation] of cidToOperation) {
|
|
79
|
+
const pieceCid = Piece.from(cid)
|
|
80
|
+
const sourceUrl = createPieceUrlPDP({
|
|
81
|
+
cid,
|
|
82
|
+
serviceURL: operation.alternateProvider,
|
|
83
|
+
})
|
|
84
|
+
pullPieces.push({ pieceCid, sourceUrl })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (pullPieces.length > 0) {
|
|
88
|
+
// wait for pull pieces
|
|
89
|
+
const pullResult = await SP.waitForPullPieces(client, {
|
|
90
|
+
serviceURL: repair.targetProviderUrl,
|
|
91
|
+
dataSetId: dataset.dataSetId,
|
|
92
|
+
clientDataSetId: dataset.clientDataSetId,
|
|
93
|
+
pieces: pullPieces,
|
|
94
|
+
timeout: 1000 * 60 * 30,
|
|
95
|
+
onStatus: (_status) => {
|
|
96
|
+
const completed = _status.pieces.filter((piece) => piece.status === 'complete').length
|
|
97
|
+
const failed = _status.pieces.filter((piece) => piece.status === 'failed').length
|
|
98
|
+
spin.message(`Pull ${completed} completed, ${failed} failed`)
|
|
99
|
+
},
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
for (const { pieceCid, status } of pullResult.pieces) {
|
|
103
|
+
const cid = pieceCid.toString()
|
|
104
|
+
const operation = cidToOperation.get(cid)
|
|
105
|
+
if (!operation) {
|
|
106
|
+
console.log(`operation not found for cid ${cid}`)
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (status !== 'complete') {
|
|
111
|
+
state.failedOperations++
|
|
112
|
+
failedCids++
|
|
113
|
+
cidToOperation.delete(cid)
|
|
114
|
+
await updateOperation({
|
|
115
|
+
localDb,
|
|
116
|
+
operationId: operation.id,
|
|
117
|
+
status: 'failed',
|
|
118
|
+
error: `pull failed with status ${status}`,
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// sync against indexer to avoid duplicates
|
|
125
|
+
const completedOperations2 = await syncPiecesOnchain({
|
|
126
|
+
indexerDb,
|
|
127
|
+
localDb,
|
|
128
|
+
dataSetId: dataset.dataSetId,
|
|
129
|
+
cidToOperation,
|
|
130
|
+
})
|
|
131
|
+
state.completedOperations += completedOperations2
|
|
132
|
+
completedCids += completedOperations2
|
|
133
|
+
|
|
134
|
+
const commitPieces: SP.addPieces.PieceType[] = []
|
|
135
|
+
for (const [cid] of cidToOperation) {
|
|
136
|
+
commitPieces.push({
|
|
137
|
+
pieceCid: Piece.from(cid),
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (commitPieces.length > 0) {
|
|
142
|
+
const addPiecesResult = await SP.addPieces(client, {
|
|
143
|
+
serviceURL: repair.targetProviderUrl,
|
|
144
|
+
dataSetId: dataset.dataSetId,
|
|
145
|
+
clientDataSetId: dataset.clientDataSetId,
|
|
146
|
+
pieces: commitPieces,
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
spin.message(`Waiting for add pieces ${hashLink(addPiecesResult.txHash, client.chain)}...`)
|
|
150
|
+
const addPiecesResult2 = await SP.waitForAddPieces(addPiecesResult)
|
|
151
|
+
state.completedOperations += cidToOperation.size
|
|
152
|
+
completedCids += cidToOperation.size
|
|
153
|
+
await upsertOperations({
|
|
154
|
+
localDb,
|
|
155
|
+
operations: Array.from(cidToOperation.values()).map((operation) => ({
|
|
156
|
+
...operation,
|
|
157
|
+
status: 'completed',
|
|
158
|
+
error: null,
|
|
159
|
+
result: { dataSetId: addPiecesResult2.dataSetId, txHash: addPiecesResult2.txHash },
|
|
160
|
+
})),
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
spin.success(`Batch ${batchNumber}/${state.totalBatches} ${completedCids} added, ${failedCids} failed`)
|
|
164
|
+
} catch (error) {
|
|
165
|
+
state.failedOperations += cidToOperation.size
|
|
166
|
+
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
167
|
+
spin.error(`Batch ${batchNumber}/${state.totalBatches} - ${message.replace(/\n/g, ' ')}`)
|
|
168
|
+
await upsertOperations({
|
|
169
|
+
localDb,
|
|
170
|
+
operations: Array.from(cidToOperation.values()).map((operation) => ({
|
|
171
|
+
...operation,
|
|
172
|
+
status: 'failed',
|
|
173
|
+
error: message,
|
|
174
|
+
})),
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Pull pending `add_piece` operations without loading the whole repair into memory.
|
|
182
|
+
*
|
|
183
|
+
* Pending piece operations are fetched lazily and queued with bounded backpressure. Failed piece
|
|
184
|
+
* operations are intentionally skipped unless `reset` is set.
|
|
185
|
+
*/
|
|
186
|
+
export async function runPullPiecesPhase({
|
|
187
|
+
localDb,
|
|
188
|
+
indexerDb,
|
|
189
|
+
repair,
|
|
190
|
+
concurrency,
|
|
191
|
+
batchSize,
|
|
192
|
+
client,
|
|
193
|
+
reset,
|
|
194
|
+
}: RunPullPiecesPhaseOptions): Promise<void> {
|
|
195
|
+
const localSchema = localDb._.fullSchema
|
|
196
|
+
const pullConcurrency = Math.max(1, concurrency)
|
|
197
|
+
const pullBatchSize = Math.max(1, batchSize)
|
|
198
|
+
let pullCursor = 0
|
|
199
|
+
|
|
200
|
+
const totalOperations = await localDb.$count(
|
|
201
|
+
localSchema.operations,
|
|
202
|
+
and(
|
|
203
|
+
eq(localSchema.operations.repairId, repair.id),
|
|
204
|
+
eq(localSchema.operations.type, 'add_piece'),
|
|
205
|
+
inArray(localSchema.operations.status, reset ? ['pending', 'failed'] : ['pending'])
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
let batchNumber = 0
|
|
209
|
+
const state = {
|
|
210
|
+
totalBatches: Math.ceil(totalOperations / pullBatchSize),
|
|
211
|
+
totalOperations,
|
|
212
|
+
completedOperations: 0,
|
|
213
|
+
failedOperations: 0,
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const log = taskLog({
|
|
217
|
+
title: 'Pulling pieces',
|
|
218
|
+
limit: 1,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
async function getNextPullBatch(): Promise<PullPiecesBatch | null> {
|
|
222
|
+
const operations = await localDb.query.operations.findMany({
|
|
223
|
+
where: and(
|
|
224
|
+
eq(localSchema.operations.repairId, repair.id),
|
|
225
|
+
eq(localSchema.operations.type, 'add_piece'),
|
|
226
|
+
inArray(localSchema.operations.status, reset ? ['pending', 'failed'] : ['pending']),
|
|
227
|
+
gt(localSchema.operations.id, pullCursor)
|
|
228
|
+
),
|
|
229
|
+
orderBy: [asc(localSchema.operations.id)],
|
|
230
|
+
limit: pullBatchSize,
|
|
231
|
+
})
|
|
232
|
+
if (operations.length === 0) {
|
|
233
|
+
return null
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
pullCursor = operations.at(-1)?.id ?? pullCursor
|
|
237
|
+
return { operations }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const pullPiecesWorker = createPullPiecesWorker({ localDb, indexerDb, repair, client, state, log })
|
|
241
|
+
const pullPiecesQueue = new PQueue({ concurrency: pullConcurrency })
|
|
242
|
+
|
|
243
|
+
while (true) {
|
|
244
|
+
await pullPiecesQueue.onSizeLessThan(pullConcurrency)
|
|
245
|
+
const batch = await getNextPullBatch()
|
|
246
|
+
if (!batch) break
|
|
247
|
+
batchNumber++
|
|
248
|
+
const currentBatchNumber = batchNumber
|
|
249
|
+
pullPiecesQueue.add(() => pullPiecesWorker(batch, currentBatchNumber)).catch(console.error)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
await pullPiecesQueue.onIdle()
|
|
253
|
+
|
|
254
|
+
log.success(`Pulled ${state.completedOperations} pieces, ${state.failedOperations} failed`)
|
|
255
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Chain } from '@filoz/synapse-core/chains'
|
|
2
|
+
import type { Client } from '@libsql/client'
|
|
3
|
+
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
|
|
4
|
+
import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
|
|
5
|
+
import type { z } from 'incur'
|
|
6
|
+
import type { Pool } from 'pg'
|
|
7
|
+
import type { Account, Address, Hex, Transport, Client as ViemClient } from 'viem'
|
|
8
|
+
import type * as indexerSchema from './indexer-schema.ts'
|
|
9
|
+
import type * as localSchema from './local-schema.ts'
|
|
10
|
+
import type { contextSchema } from './middleware.ts'
|
|
11
|
+
export type LocalDatabase = LibSQLDatabase<typeof localSchema> & {
|
|
12
|
+
$client: Client
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type IndexerDatabase = NodePgDatabase<typeof indexerSchema> & {
|
|
16
|
+
$client: Pool
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface Config {
|
|
20
|
+
privateKey: Hex
|
|
21
|
+
indexerMainnetUrl: string
|
|
22
|
+
indexerCalibrationUrl: string
|
|
23
|
+
chainId: number
|
|
24
|
+
dbPath: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type WalletClient = ViemClient<Transport, Chain, Account>
|
|
28
|
+
|
|
29
|
+
export type Context = z.infer<typeof contextSchema>
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Provider details used for repair selection and CID replica lookup.
|
|
33
|
+
*/
|
|
34
|
+
export type RepairProvider = {
|
|
35
|
+
providerId: bigint
|
|
36
|
+
providerAddress: Address
|
|
37
|
+
name: string
|
|
38
|
+
serviceUrl: string
|
|
39
|
+
approved: boolean
|
|
40
|
+
endorsed: boolean
|
|
41
|
+
}
|