Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
93d32ae
fix: pieces can be removed from a data-set
SgtPooki Nov 19, 2025
9abd9ea
Merge branch 'master' into 8-add-rm---piece-piececid-command
SgtPooki Nov 24, 2025
09a2f91
fix: handle pieces pending removal
SgtPooki Nov 25, 2025
e7e2fc4
fix: display piece onchain vs pdpserver status
SgtPooki Nov 25, 2025
2c4b84f
fix: rm cmd requires --piece and --data-set
SgtPooki Nov 25, 2025
ffd4d01
fix: fix default to orphaned piece status
SgtPooki Nov 25, 2025
d139558
Merge branch 'master' into 8-add-rm---piece-piececid-command
SgtPooki Dec 4, 2025
9069e21
deps: upgrade to latest synapse
SgtPooki Dec 4, 2025
28c0883
fix: use new createStorageContext signature in rm
SgtPooki Dec 4, 2025
2d9fc7f
test: fix mock merge issue
SgtPooki Dec 4, 2025
01f6e6c
chore: remove unnecessary as string casting
SgtPooki Dec 4, 2025
b60eae6
fix: logger is no longer required for createStorageContext
SgtPooki Dec 4, 2025
6fcb9d6
test: remove-piece is tested
SgtPooki Dec 4, 2025
362cca6
test: assert runRmPiece happy path
SgtPooki Dec 4, 2025
6e372f6
chore: remove outdated comment
SgtPooki Dec 4, 2025
e77693f
Update src/core/data-set/get-detailed-data-set.ts
SgtPooki Dec 4, 2025
414aec3
Update src/rm/remove-piece.ts
SgtPooki Dec 4, 2025
2198e51
Update src/rm/remove-piece.ts
SgtPooki Dec 4, 2025
a66dd7a
Update src/rm/remove-piece.ts
SgtPooki Dec 4, 2025
797def7
chore: update jsdoc comments in runRmPiece
SgtPooki Dec 4, 2025
f0450a2
fix: removePiece requires storageContext
SgtPooki Dec 6, 2025
f8c977f
fix: document more about piece orphans
SgtPooki Dec 6, 2025
c2a60d3
Update src/core/data-set/types.ts
SgtPooki Dec 6, 2025
f31e0ed
Update src/rm/remove-piece.ts
SgtPooki Dec 6, 2025
701a78c
fix: export Warning type
SgtPooki Dec 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
"types": "./dist/core/payments/index.d.ts",
"default": "./dist/core/payments/index.js"
},
"./core/piece": {
"types": "./dist/core/piece/index.d.ts",
"default": "./dist/core/piece/index.js"
},
"./core/synapse": {
"types": "./dist/core/synapse/index.d.ts",
"default": "./dist/core/synapse/index.js"
Expand Down
2 changes: 2 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { addCommand } from './commands/add.js'
import { dataSetCommand } from './commands/data-set.js'
import { importCommand } from './commands/import.js'
import { paymentsCommand } from './commands/payments.js'
import { rmCommand } from './commands/rm.js'
import { serverCommand } from './commands/server.js'
import { checkForUpdate, type UpdateCheckStatus } from './common/version-check.js'
import { version as packageVersion } from './core/utils/version.js'
Expand All @@ -24,6 +25,7 @@ program.addCommand(paymentsCommand)
program.addCommand(dataSetCommand)
program.addCommand(importCommand)
program.addCommand(addCommand)
program.addCommand(rmCommand)

// Default action - show help if no command specified
program.action(() => {
Expand Down
19 changes: 19 additions & 0 deletions src/commands/rm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Command } from 'commander'
import { runRmPiece } from '../rm/index.js'
import { addAuthOptions } from '../utils/cli-options.js'

export const rmCommand = new Command('rm')
.description('Remove a Piece from a DataSet')
.option('--piece <cid>', 'Piece CID to remove')
.option('--data-set <id>', 'DataSet ID to remove the piece from')
.option('--wait-for-confirmation', 'Wait for transaction confirmation before exiting')
.action(async (options) => {
try {
await runRmPiece(options)
} catch {
// Error already displayed by clack UI in runRmPiece
process.exit(1)
}
})

addAuthOptions(rmCommand)
71 changes: 69 additions & 2 deletions src/core/data-set/get-data-set-pieces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@
*/

import { getSizeFromPieceCID } from '@filoz/synapse-core/piece'
import { METADATA_KEYS, type StorageContext, type Synapse, WarmStorageService } from '@filoz/synapse-sdk'
import {
type DataSetPieceData,
METADATA_KEYS,
PDPServer,
PDPVerifier,
type StorageContext,
type Synapse,
WarmStorageService,
} from '@filoz/synapse-sdk'
import { isStorageContextWithDataSetId } from './type-guards.js'
import type {
DataSetPiecesResult,
Expand All @@ -16,6 +24,7 @@
PieceInfo,
StorageContextWithDataSetId,
} from './types.js'
import { PieceStatus } from './types.js'

/**
* Get all pieces for a dataset from a StorageContext
Expand Down Expand Up @@ -59,13 +68,43 @@
const pieces: PieceInfo[] = []
const warnings: DataSetWarning[] = []

// call PDPVerifier.getScheduledRemovals to get the list of pieces that are scheduled for removal
let scheduledRemovals: number[] = []
let pdpServerPieces: DataSetPieceData[] = []
try {
const warmStorage = await WarmStorageService.create(synapse.getProvider(), synapse.getWarmStorageAddress())
const pdpVerifier = new PDPVerifier(synapse.getProvider(), warmStorage.getPDPVerifierAddress())
scheduledRemovals = await pdpVerifier.getScheduledRemovals(storageContext.dataSetId)

Check failure on line 77 in src/core/data-set/get-data-set-pieces.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, windows-latest)

Property 'getScheduledRemovals' does not exist on type 'PDPVerifier'.

Check failure on line 77 in src/core/data-set/get-data-set-pieces.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, ubuntu-latest)

Property 'getScheduledRemovals' does not exist on type 'PDPVerifier'.

Check failure on line 77 in src/core/data-set/get-data-set-pieces.ts

View workflow job for this annotation

GitHub Actions / test (current, windows-latest)

Property 'getScheduledRemovals' does not exist on type 'PDPVerifier'.

Check failure on line 77 in src/core/data-set/get-data-set-pieces.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, macos-latest)

Property 'getScheduledRemovals' does not exist on type 'PDPVerifier'.

Check failure on line 77 in src/core/data-set/get-data-set-pieces.ts

View workflow job for this annotation

GitHub Actions / test (current, ubuntu-latest)

Property 'getScheduledRemovals' does not exist on type 'PDPVerifier'.

Check failure on line 77 in src/core/data-set/get-data-set-pieces.ts

View workflow job for this annotation

GitHub Actions / test (current, macos-latest)

Property 'getScheduledRemovals' does not exist on type 'PDPVerifier'.

Check failure on line 77 in src/core/data-set/get-data-set-pieces.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, windows-latest)

Property 'getScheduledRemovals' does not exist on type 'PDPVerifier'.

Check failure on line 77 in src/core/data-set/get-data-set-pieces.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, macos-latest)

Property 'getScheduledRemovals' does not exist on type 'PDPVerifier'.

Check failure on line 77 in src/core/data-set/get-data-set-pieces.ts

View workflow job for this annotation

GitHub Actions / test (current, windows-latest)

Property 'getScheduledRemovals' does not exist on type 'PDPVerifier'.

Check failure on line 77 in src/core/data-set/get-data-set-pieces.ts

View workflow job for this annotation

GitHub Actions / test (current, ubuntu-latest)

Property 'getScheduledRemovals' does not exist on type 'PDPVerifier'.

Check failure on line 77 in src/core/data-set/get-data-set-pieces.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, ubuntu-latest)

Property 'getScheduledRemovals' does not exist on type 'PDPVerifier'.

Check failure on line 77 in src/core/data-set/get-data-set-pieces.ts

View workflow job for this annotation

GitHub Actions / test (current, macos-latest)

Property 'getScheduledRemovals' does not exist on type 'PDPVerifier'.
const providerInfo = await synapse.getProviderInfo(storageContext.provider.serviceProvider)
const pdpServer = new PDPServer(null, providerInfo.products?.PDP?.data?.serviceURL ?? '')
const dataSet = await pdpServer.getDataSet(storageContext.dataSetId)
pdpServerPieces = dataSet.pieces
} catch (error) {
logger?.warn({ error }, 'Failed to create WarmStorageService or PDPVerifier for scheduled removals')
}

// Use the async generator to fetch all pieces
try {
const getPiecesOptions = { ...(signal && { signal }) }
const providerPiecesById = new Map(pdpServerPieces.map((piece) => [piece.pieceId, piece]))
for await (const piece of storageContext.getPieces(getPiecesOptions)) {
const pieceId = piece.pieceId
const pieceCid = piece.pieceCid
const pieceInfo: PieceInfo = { pieceId, pieceCid: pieceCid.toString() }
const status = getPieceStatus(pieceId, scheduledRemovals, providerPiecesById)
const pieceInfo: PieceInfo = {
pieceId,
pieceCid: pieceCid.toString(),
status,
}
if (status === PieceStatus.ONCHAIN_ORPHANED) {
warnings.push({
code: 'ONCHAIN_ORPHANED',
message: 'Piece is on-chain but the provider does not report it',
context: { pieceId, pieceCid },
})
} else if (status === PieceStatus.ACTIVE) {
providerPiecesById.delete(pieceId)
}

// Calculate piece size from CID
try {
Expand All @@ -79,6 +118,20 @@

pieces.push(pieceInfo)
}
for (const piece of providerPiecesById.values()) {
// add the rest of the pieces to the pieces list
pieces.push({
pieceId: piece.pieceId,
pieceCid: piece.pieceCid.toString(),
status: PieceStatus.OFFCHAIN_ORPHANED,
})
warnings.push({
code: 'OFFCHAIN_ORPHANED',
message: 'Piece is reported by provider but not on-chain',
context: { pieceId: piece.pieceId, pieceCid: piece.pieceCid.toString() },
})
}
pieces.sort((a, b) => a.pieceId - b.pieceId)
} catch (error) {
// If getPieces fails completely, throw - this is a critical error
logger?.error({ dataSetId: storageContext.dataSetId, error }, 'Failed to retrieve pieces from dataset')
Expand Down Expand Up @@ -106,6 +159,20 @@
return result
}

function getPieceStatus(
pieceId: number,
scheduledRemovals: number[],
providerPiecesById: Map<DataSetPieceData['pieceId'], DataSetPieceData>
): PieceStatus {
if (scheduledRemovals.includes(pieceId)) {
return PieceStatus.PENDING_REMOVAL
}
if (providerPiecesById.has(pieceId)) {
return PieceStatus.ACTIVE
}
return PieceStatus.ONCHAIN_ORPHANED
}

/**
* Internal helper: Enrich pieces with metadata from WarmStorage
*
Expand Down
1 change: 1 addition & 0 deletions src/core/data-set/get-detailed-data-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export async function getDetailedDataSet(

const storageContext = await synapse.storage.createContext({
dataSetId: dataSet.dataSetId,
providerId: dataSet.providerId,
})

const piecesResult = await getDataSetPieces(synapse, storageContext, {
Expand Down
18 changes: 18 additions & 0 deletions src/core/data-set/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@
import type { EnhancedDataSetInfo, ProviderInfo, StorageContext } from '@filoz/synapse-sdk'
import type { Logger } from 'pino'

/**
* Status of the piece, e.g. "pending removal", "active", "orphaned"
*
* - "pending-removal": the piece is scheduled for deletion, but still showing on chain
* - "active": the piece is active, onchain and known by the provider
* - "onchain-orphaned": the piece is not known by the provider, but still on chain
* - "offchain-orphaned": the piece is known by the provider, but not on chain
*
* The orphaned states should not happen, but have been observed and should be logged and displayed to the user.
*/
export enum PieceStatus {
ACTIVE = 'ACTIVE',
PENDING_REMOVAL = 'PENDING_REMOVAL',
ONCHAIN_ORPHANED = 'ONCHAIN_ORPHANED',
OFFCHAIN_ORPHANED = 'OFFCHAIN_ORPHANED',
}

/**
* Information about a single piece in a dataset
*/
Expand All @@ -19,6 +36,7 @@ export interface PieceInfo {
pieceId: number
/** Piece Commitment (CommP) as string */
pieceCid: string
status: PieceStatus
/** Root IPFS CID (from metadata, if available) */
rootIpfsCid?: string
/** Piece size in bytes (if available) */
Expand Down
1 change: 1 addition & 0 deletions src/core/piece/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './remove-piece.js'
Loading
Loading