@filoz/repair-cli 1.0.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 +2 -2
- package/dist/src/commands/datasets.d.ts +2 -2
- package/dist/src/commands/datasets.js +9 -2
- package/dist/src/commands/datasets.js.map +1 -1
- package/dist/src/commands/providers.d.ts +2 -2
- package/dist/src/commands/repair.d.ts +2 -2
- package/dist/src/commands/repair.js +5 -1
- package/dist/src/commands/repair.js.map +1 -1
- package/dist/src/commands/replicate.d.ts +2 -2
- 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 +2 -2
- 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 +2 -2
- package/dist/src/commands/wallet.d.ts +2 -2
- package/dist/src/middleware.d.ts +5 -5
- package/dist/src/middleware.d.ts.map +1 -1
- package/dist/src/middleware.js.map +1 -1
- package/dist/src/pipeline/add-pieces.d.ts +1 -1
- package/dist/src/pipeline/add-pieces.d.ts.map +1 -1
- package/dist/src/pipeline/add-pieces.js +20 -15
- package/dist/src/pipeline/add-pieces.js.map +1 -1
- package/dist/src/types.d.ts +2 -2
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils.d.ts +38 -27
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +46 -0
- package/dist/src/utils.js.map +1 -1
- package/package.json +2 -2
- package/readme.md +6 -6
- package/src/commands/datasets.ts +9 -2
- package/src/commands/repair.ts +5 -1
- package/src/commands/replicate.ts +5 -1
- package/src/commands/second-copy.ts +5 -1
- package/src/middleware.ts +3 -3
- package/src/pipeline/add-pieces.ts +35 -17
- package/src/types.ts +2 -2
- package/src/utils.ts +69 -3
|
@@ -1,5 +1,4 @@
|
|
|
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'
|
|
@@ -10,14 +9,25 @@ import { repairUpdate } from '../db/repair-update.ts'
|
|
|
10
9
|
import { upsertOperations } from '../db/upsert-operations.ts'
|
|
11
10
|
import type { OperationSelect, RepairSelect } from '../local-schema.ts'
|
|
12
11
|
import type { IndexerApi, LocalDatabase, WalletClient } from '../types.ts'
|
|
13
|
-
import {
|
|
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
|
|
14
23
|
|
|
15
24
|
export type RunPullPiecesPhaseOptions = {
|
|
16
25
|
localDb: LocalDatabase
|
|
17
26
|
api: IndexerApi
|
|
18
27
|
repair: RepairSelect
|
|
19
28
|
concurrency: number
|
|
20
|
-
|
|
29
|
+
/** Max pieces per batch. Defaults to as many as fit in one addPieces message. */
|
|
30
|
+
batchSize?: number
|
|
21
31
|
client: WalletClient
|
|
22
32
|
}
|
|
23
33
|
|
|
@@ -28,7 +38,6 @@ type CreateAddPiecesWorkerOptions = {
|
|
|
28
38
|
repair: RepairSelect
|
|
29
39
|
client: WalletClient
|
|
30
40
|
state: {
|
|
31
|
-
totalBatches: number
|
|
32
41
|
totalOperations: number
|
|
33
42
|
completedOperations: number
|
|
34
43
|
failedOperations: number
|
|
@@ -50,7 +59,7 @@ function createAddPiecesWorker({ localDb, targetCids, repair, client, state, log
|
|
|
50
59
|
let failedOps = 0
|
|
51
60
|
let operations: OperationSelect[] = options.operations
|
|
52
61
|
|
|
53
|
-
const group = log.group(`Batch ${options.batchNumber}
|
|
62
|
+
const group = log.group(`Batch ${options.batchNumber} (${options.operations.length} pieces)`)
|
|
54
63
|
|
|
55
64
|
try {
|
|
56
65
|
const dataset = await getTargetDataset({ localDb, repairId: repair.id, client })
|
|
@@ -76,7 +85,6 @@ function createAddPiecesWorker({ localDb, targetCids, repair, client, state, log
|
|
|
76
85
|
dataSetId: dataset.dataSetId,
|
|
77
86
|
clientDataSetId: dataset.clientDataSetId,
|
|
78
87
|
pieces: operationsToPullPieces(operations),
|
|
79
|
-
timeout: 1000 * 60 * 30, // 30 minutes
|
|
80
88
|
onStatus: (status) => {
|
|
81
89
|
const completed = status.pieces.filter((piece) => piece.status === 'complete').length
|
|
82
90
|
const failed = status.pieces.filter((piece) => piece.status === 'failed').length
|
|
@@ -110,10 +118,7 @@ function createAddPiecesWorker({ localDb, targetCids, repair, client, state, log
|
|
|
110
118
|
serviceURL: repair.targetProviderUrl,
|
|
111
119
|
dataSetId: dataset.dataSetId,
|
|
112
120
|
clientDataSetId: dataset.clientDataSetId,
|
|
113
|
-
pieces: operations
|
|
114
|
-
pieceCid: Piece.from(operation.cid),
|
|
115
|
-
metadata: targetCids ? undefined : operation.metadata,
|
|
116
|
-
})),
|
|
121
|
+
pieces: operationsToAddPieces(operations, !targetCids),
|
|
117
122
|
})
|
|
118
123
|
await upsertOperations({
|
|
119
124
|
localDb,
|
|
@@ -125,6 +130,8 @@ function createAddPiecesWorker({ localDb, targetCids, repair, client, state, log
|
|
|
125
130
|
|
|
126
131
|
group.message(`Waiting for add pieces ${hashLink(addPiecesResult.txHash, client.chain)}...`)
|
|
127
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
|
|
128
135
|
state.completedOperations += operations.length
|
|
129
136
|
completedOps += operations.length
|
|
130
137
|
for (const operation of operations) targetCids?.add(operation.cid)
|
|
@@ -134,7 +141,7 @@ function createAddPiecesWorker({ localDb, targetCids, repair, client, state, log
|
|
|
134
141
|
...operation,
|
|
135
142
|
status: 'completed',
|
|
136
143
|
error: null,
|
|
137
|
-
txHash:
|
|
144
|
+
txHash: confirmedTxHash,
|
|
138
145
|
})),
|
|
139
146
|
})
|
|
140
147
|
}
|
|
@@ -179,7 +186,6 @@ export async function runAddPieces({
|
|
|
179
186
|
)
|
|
180
187
|
let batchNumber = 0
|
|
181
188
|
const state = {
|
|
182
|
-
totalBatches: Math.ceil(totalOperations / batchSize),
|
|
183
189
|
totalOperations,
|
|
184
190
|
completedOperations: 0,
|
|
185
191
|
failedOperations: 0,
|
|
@@ -190,16 +196,20 @@ export async function runAddPieces({
|
|
|
190
196
|
limit: 1,
|
|
191
197
|
})
|
|
192
198
|
|
|
199
|
+
const dataset = totalOperations > 0 ? await getTargetDataset({ localDb, repairId: repair.id, client }) : undefined
|
|
200
|
+
|
|
193
201
|
// Repairs and second copies dedupe into one shared dataset; read its CIDs once instead of per batch.
|
|
194
202
|
let targetCids: Set<string> | undefined
|
|
195
|
-
if (repair.repairDataSetId == null &&
|
|
196
|
-
const dataset = await getTargetDataset({ localDb, repairId: repair.id, client })
|
|
203
|
+
if (repair.repairDataSetId == null && dataset) {
|
|
197
204
|
const pieces = await listDataSetPieces(api, dataset.dataSetId, { removed: false })
|
|
198
205
|
targetCids = new Set(pieces.flatMap((piece) => (piece.cid == null ? [] : [piece.cid])))
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
async function getNextBatch(): Promise<OperationSelect[] | null> {
|
|
202
|
-
|
|
209
|
+
if (!dataset) {
|
|
210
|
+
return null
|
|
211
|
+
}
|
|
212
|
+
const candidates = await localDb.query.operations.findMany({
|
|
203
213
|
where: and(
|
|
204
214
|
eq(localSchema.operations.repairId, repair.id),
|
|
205
215
|
eq(localSchema.operations.type, 'add_piece'),
|
|
@@ -207,12 +217,20 @@ export async function runAddPieces({
|
|
|
207
217
|
gt(localSchema.operations.id, cursor)
|
|
208
218
|
),
|
|
209
219
|
orderBy: [asc(localSchema.operations.id)],
|
|
210
|
-
limit: batchSize,
|
|
220
|
+
limit: batchSize ?? MAX_BATCH_CANDIDATES,
|
|
211
221
|
})
|
|
212
|
-
if (
|
|
222
|
+
if (candidates.length === 0) {
|
|
213
223
|
return null
|
|
214
224
|
}
|
|
215
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
|
+
|
|
216
234
|
cursor = operations.at(-1)?.id ?? cursor
|
|
217
235
|
return operations
|
|
218
236
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ApiClient } from '@filecoin-project/api-client'
|
|
2
|
-
import type {
|
|
2
|
+
import type { FilecoinChain } from '@filoz/synapse-core/chains'
|
|
3
3
|
import type { Client } from '@libsql/client'
|
|
4
4
|
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
|
|
5
5
|
import type { z } from 'incur'
|
|
@@ -26,7 +26,7 @@ export interface Config {
|
|
|
26
26
|
dbPath: string
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export type WalletClient = ViemClient<Transport,
|
|
29
|
+
export type WalletClient = ViemClient<Transport, FilecoinChain, Account>
|
|
30
30
|
|
|
31
31
|
export type Context = z.infer<typeof contextSchema>
|
|
32
32
|
|
package/src/utils.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
6
|
import type { SQLiteTable } from 'drizzle-orm/sqlite-core'
|
|
@@ -166,7 +166,7 @@ export async function migrateLocalDatabase(db: LocalDatabase) {
|
|
|
166
166
|
* @param chain - The chain to use
|
|
167
167
|
* @returns The link
|
|
168
168
|
*/
|
|
169
|
-
export function hashLink(hash: string, chain:
|
|
169
|
+
export function hashLink(hash: string, chain: FilecoinChain) {
|
|
170
170
|
const link = terminalLink(hash, `${chain.blockExplorers?.default?.url}/tx/${hash}`)
|
|
171
171
|
return link
|
|
172
172
|
}
|
|
@@ -327,3 +327,69 @@ export function operationsToPullPieces(operations: OperationSelect[]) {
|
|
|
327
327
|
}
|
|
328
328
|
return pieces
|
|
329
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
|
+
}
|