Skip to content

Commit 6282f91

Browse files
authored
feat(warm-storage): add hasActivePieces and use it in resolveByProviderId (#852)
resolveByProviderId checked dataset emptiness with getActivePieceCount, which is O(n) in piece count and reverts on large sets (FilOzone/pdp#265). hasActivePieces reads a single piece (getActivePieces, limit 1) for an O(1) boolean, and resolveByProviderId now uses it. Iteration/batching is unchanged.
1 parent 881cf6b commit 6282f91

4 files changed

Lines changed: 61 additions & 11 deletions

File tree

packages/synapse-sdk/src/storage/context.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ export class StorageContext {
483483
type EvaluatedDataSet = {
484484
dataSetId: bigint
485485
dataSetMetadata: Record<string, string>
486-
activePieceCount: bigint
486+
hasPieces: boolean
487487
}
488488

489489
// Sort ascending by ID (oldest first) for deterministic selection
@@ -501,9 +501,9 @@ export class StorageContext {
501501
const batchResults: (EvaluatedDataSet | null)[] = await Promise.all(
502502
sortedDataSets.slice(i, i + BATCH_SIZE).map(async (dataSet) => {
503503
const { dataSetId } = dataSet
504-
const [dataSetMetadata, activePieceCount] = await Promise.all([
504+
const [dataSetMetadata, hasPieces] = await Promise.all([
505505
warmStorageService.getDataSetMetadata({ dataSetId }),
506-
warmStorageService.getActivePieceCount({ dataSetId }),
506+
warmStorageService.hasActivePieces({ dataSetId }),
507507
])
508508

509509
if (!metadataMatches(dataSetMetadata, requestedMetadata)) {
@@ -513,15 +513,15 @@ export class StorageContext {
513513
return {
514514
dataSetId,
515515
dataSetMetadata,
516-
activePieceCount,
516+
hasPieces,
517517
}
518518
})
519519
)
520520

521521
for (const result of batchResults) {
522522
if (result == null) continue
523523

524-
if (result.activePieceCount > 0) {
524+
if (result.hasPieces) {
525525
selectedDataSet = result
526526
break
527527
}
@@ -531,7 +531,7 @@ export class StorageContext {
531531
}
532532
}
533533

534-
if (selectedDataSet != null && selectedDataSet.activePieceCount > 0) {
534+
if (selectedDataSet?.hasPieces) {
535535
break
536536
}
537537
}

packages/synapse-sdk/src/test/storage.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -314,13 +314,13 @@ describe('StorageService', () => {
314314
...Mocks.presets.basic,
315315
pdpVerifier: {
316316
...Mocks.presets.basic.pdpVerifier,
317-
getActivePieceCount: (args) => {
317+
getActivePieces: (args) => {
318318
const [dataSetId] = args
319319
if (dataSetId === 2n) {
320-
return [2n]
321-
} else {
322-
return [0n]
320+
const cid = CID.parse('bafkzcibcd4bdomn3tgwgrh3g532zopskstnbrd2n3sxfqbze7rxt7vqn7veigmy')
321+
return [[{ data: bytesToHex(cid.bytes) }], [101n], false]
323322
}
323+
return [[], [], false]
324324
},
325325
},
326326
warmStorageView: {

packages/synapse-sdk/src/test/warm-storage-service.test.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { calibration } from '@filoz/synapse-core/chains'
88
import * as Mocks from '@filoz/synapse-core/mocks'
99
import { assert } from 'chai'
1010
import { setup } from 'iso-web/msw'
11-
import { type Address, createWalletClient, http as viemHttp } from 'viem'
11+
import { CID } from 'multiformats/cid'
12+
import { type Address, bytesToHex, createWalletClient, http as viemHttp } from 'viem'
1213
import { privateKeyToAccount } from 'viem/accounts'
1314
import { WarmStorageService } from '../warm-storage/index.ts'
1415

@@ -47,6 +48,37 @@ describe('WarmStorageService', () => {
4748
})
4849
})
4950

51+
describe('hasActivePieces', () => {
52+
it('should return true when the data set has at least one active piece', async () => {
53+
const cid = CID.parse('bafkzcibcd4bdomn3tgwgrh3g532zopskstnbrd2n3sxfqbze7rxt7vqn7veigmy')
54+
server.use(
55+
Mocks.JSONRPC({
56+
...Mocks.presets.basic,
57+
pdpVerifier: {
58+
...Mocks.presets.basic.pdpVerifier,
59+
getActivePieces: () => [[{ data: bytesToHex(cid.bytes) }], [101n], false],
60+
},
61+
})
62+
)
63+
const warmStorageService = await createWarmStorageService()
64+
assert.isTrue(await warmStorageService.hasActivePieces({ dataSetId: 1n }))
65+
})
66+
67+
it('should return false when the data set has no active pieces', async () => {
68+
server.use(
69+
Mocks.JSONRPC({
70+
...Mocks.presets.basic,
71+
pdpVerifier: {
72+
...Mocks.presets.basic.pdpVerifier,
73+
getActivePieces: () => [[], [], false],
74+
},
75+
})
76+
)
77+
const warmStorageService = await createWarmStorageService()
78+
assert.isFalse(await warmStorageService.hasActivePieces({ dataSetId: 1n }))
79+
})
80+
})
81+
5082
describe('getDataSet', () => {
5183
it('should return a single data set by ID', async () => {
5284
server.use(Mocks.JSONRPC(Mocks.presets.basic))

packages/synapse-sdk/src/warm-storage/service.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,24 @@ export class WarmStorageService {
292292
return PDPVerifier.getActivePieceCount(this._client, { dataSetId: options.dataSetId })
293293
}
294294

295+
/**
296+
* Report whether a data set has at least one active piece.
297+
*
298+
* Reads a single piece (`limit: 1`) rather than the full active-piece count, so
299+
* the cost is independent of how many pieces the data set holds.
300+
* @param options - Options for the data set
301+
* @param options.dataSetId - The PDPVerifier data set ID
302+
* @returns True when the data set has at least one active piece
303+
*/
304+
async hasActivePieces(options: { dataSetId: bigint }): Promise<boolean> {
305+
const { pieces } = await PDPVerifier.getActivePieces(this._client, {
306+
dataSetId: options.dataSetId,
307+
offset: 0n,
308+
limit: 1n,
309+
})
310+
return pieces.length > 0
311+
}
312+
295313
// ========== Metadata Operations ==========
296314

297315
/**

0 commit comments

Comments
 (0)