Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions packages/synapse-core/src/utils/rand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,18 @@ export function randIndex(length: number): number {
return fallbackRandIndex(length)
}
}

/**
* Shuffles an array using the Fisher-Yates algorithm
* @param array - The array to shuffle
* @returns A new array with the elements shuffled
*/
export function shuffle<T>(array: T[]): T[] {
// Fisher-Yates shuffle
const arr = array.slice()
for (let i = arr.length; i-- > 1; ) {
const j = randIndex(i + 1)
;[arr[i], arr[j]] = [arr[j], arr[i]]
}
return arr
}
1 change: 1 addition & 0 deletions packages/synapse-sdk/src/sp-registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type {
PDPServiceInfo,
PRODUCTS,
ProductType,
ProviderFilterOptions,
ProviderInfo,
ProviderRegistrationInfo,
ServiceProduct,
Expand Down
53 changes: 52 additions & 1 deletion packages/synapse-sdk/src/sp-registry/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@

import type { Chain } from '@filoz/synapse-core/chains'
import * as SP from '@filoz/synapse-core/sp-registry'
import { shuffle } from '@filoz/synapse-core/utils'
import type { Account, Address, Client, Hash, Transport } from 'viem'
import type { PDPOffering, ProductType, ProviderRegistrationInfo } from './types.ts'
import {
type PDPOffering,
PRODUCTS,
type ProductType,
type ProviderFilterOptions,
type ProviderRegistrationInfo,
} from './types.ts'

export class SPRegistryService {
private readonly _client: Client<Transport, Chain>
Expand Down Expand Up @@ -309,4 +316,48 @@ export class SPRegistryService {
providerIds,
})
}

/**
* Filter providers based on criteria
* @param filter - Filtering options
* @returns Filtered list of providers
*/
async filterProviders(filter?: ProviderFilterOptions): Promise<SP.PDPProvider[]> {
const providers = await this.getAllActiveProviders()
if (!filter) return providers

if (filter.type !== undefined) {
const requestedTypeValue = PRODUCTS[filter.type]
if (requestedTypeValue === undefined) {
return [] // Invalid product type
}
}

const typeKey = (filter.type ?? 'PDP').toLowerCase()

const result = providers.filter((d) => {
switch (typeKey) {
case 'pdp': {
const offering = d[typeKey as keyof typeof d] as PDPOffering
return (
(!filter.location || offering.location?.toLowerCase().includes(filter.location.toLowerCase())) &&
(filter.minPieceSizeInBytes === undefined ||
offering.maxPieceSizeInBytes >= BigInt(filter.minPieceSizeInBytes)) &&
(filter.maxPieceSizeInBytes === undefined ||
offering.minPieceSizeInBytes <= BigInt(filter.maxPieceSizeInBytes)) &&
(filter.ipniIpfs === undefined || offering.ipniIpfs === filter.ipniIpfs) &&
(filter.ipniPiece === undefined || offering.ipniPiece === filter.ipniPiece) &&
(filter.maxStoragePricePerTibPerDay === undefined ||
offering.storagePricePerTibPerDay <= BigInt(filter.maxStoragePricePerTibPerDay)) &&
(filter.minProvingPeriodInEpochs === undefined ||
offering.minProvingPeriodInEpochs >= BigInt(filter.minProvingPeriodInEpochs))
)
}
default:
return false // Unsupported product type
}
})

return filter.randomize ? shuffle(result) : result
}
}
20 changes: 20 additions & 0 deletions packages/synapse-sdk/src/sp-registry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,23 @@ export interface PDPServiceInfo {
capabilities: Record<string, string> // Object map of capability key-value pairs
isActive: boolean
}

/** * Options for filtering providers
*/
export interface ProviderFilterOptions {
type?: keyof typeof PRODUCTS // 'PDP' etc.
location?: string

minPieceSizeInBytes?: number
maxPieceSizeInBytes?: number

ipniIpfs?: boolean
ipniPiece?: boolean

serviceStatus?: string // or union type below

maxStoragePricePerTibPerDay?: number
minProvingPeriodInEpochs?: number

randomize?: boolean
}
19 changes: 19 additions & 0 deletions packages/synapse-sdk/src/synapse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { FilBeamService } from './filbeam/index.ts'
import { PaymentsService } from './payments/index.ts'
import { ChainRetriever, FilBeamRetriever } from './retriever/index.ts'
import type { ProviderFilterOptions } from './sp-registry/index.ts'
import { SPRegistryService } from './sp-registry/index.ts'
import type { StorageContext } from './storage/index.ts'
import { StorageManager } from './storage/manager.ts'
Expand Down Expand Up @@ -236,4 +237,22 @@ export class Synapse {
console.warn('synapse.getStorageInfo() is deprecated. Use synapse.storage.getStorageInfo() instead.')
return await this._storageManager.getStorageInfo()
}

/**
* Get providers with filtering options
* @param filter - Filtering options
* @returns Filtered list of providers
*/
async filterProviders(filter?: ProviderFilterOptions): Promise<PDPProvider[]> {
// Create SPRegistryService
try {
const _registryAddress = this._warmStorageService.getServiceProviderRegistryAddress()
const spRegistry = new SPRegistryService(this._client)

const providers = await spRegistry.filterProviders(filter)
return providers
} catch (error) {
throw new Error(`Failed to filter providers: ${error instanceof Error ? error.message : String(error)}`)
}
}
}
39 changes: 39 additions & 0 deletions packages/synapse-sdk/src/test/sp-registry-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,4 +372,43 @@ describe('SPRegistryService', () => {
}
})
})

describe('Provider Filtering', () => {
it('should filter providers by multiple criteria', async () => {
server.use(Mocks.JSONRPC(Mocks.presets.basic))

// Test location filtering (case-insensitive partial match)
const byLocation = await service.filterProviders({ location: 'US' })
assert.equal(byLocation.length, 2) // Both providers have 'US' location

const byPrice = await service.filterProviders({ maxStoragePricePerTibPerDay: 999999 })
assert.equal(byPrice.length, 0)

// Test piece size filtering
const byPieceSize = await service.filterProviders({
minPieceSizeInBytes: Number(SIZE_CONSTANTS.KiB),
maxPieceSizeInBytes: Number(SIZE_CONSTANTS.GiB),
})
assert.equal(byPieceSize.length, 2) // Both providers support this range

// Test no filters returns all
const all = await service.filterProviders()
assert.equal(all.length, 2)
})

it('should randomize results when requested', async () => {
server.use(Mocks.JSONRPC(Mocks.presets.basic))

const results = []
for (let i = 0; i < 5; i++) {
const filtered = await service.filterProviders({ randomize: true })
results.push(filtered.map((p) => p.serviceProvider))
}

// At least one result should have different order (with high probability)
const firstOrder = JSON.stringify(results[0])
const hasDifferentOrder = results.some((r) => JSON.stringify(r) !== firstOrder)
assert.isTrue(hasDifferentOrder || results[0].length === 1, 'Results should be randomized')
})
})
})
13 changes: 12 additions & 1 deletion packages/synapse-sdk/src/test/synapse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import { calibration } from '@filoz/synapse-core/chains'
import * as Mocks from '@filoz/synapse-core/mocks'
import * as Piece from '@filoz/synapse-core/piece'
import { getPermissionFromTypeHash, type SessionKeyPermissions } from '@filoz/synapse-core/session-key'
import { assert } from 'chai'
import { setup } from 'iso-web/msw'
import { HttpResponse, http } from 'msw'
Expand Down Expand Up @@ -902,4 +901,16 @@ describe('Synapse', () => {
})
})
})

describe('Provider Filtering', () => {
it('should filter providers through Synapse class', async () => {
server.use(Mocks.JSONRPC(Mocks.presets.basic))

const synapse = new Synapse({ client })
// Test filtering by location
const filtered = await synapse.filterProviders({ location: 'US' })
assert.equal(filtered.length, 2) // Both providers have 'US' location in preset
assert.exists(filtered[0].pdp.location)
})
})
})
1 change: 0 additions & 1 deletion packages/synapse-sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import type { PieceCID } from '@filoz/synapse-core/piece'
import type { PDPProvider } from '@filoz/synapse-core/sp-registry'
import type { MetadataObject } from '@filoz/synapse-core/utils'
import type { Account, Address, Client, Hex, Transport } from 'viem'

// Re-export PieceCID and PDPProvider types
export type { PieceCID, PDPProvider }
export type PrivateKey = string
Expand Down
Loading