filecoin-pin
Version:
Bridge IPFS content to Filecoin Onchain Cloud using familiar tools
188 lines (161 loc) • 7.01 kB
text/typescript
/**
* Helper: createStorageContextFromDataSetId
*
* Optimized helper to create a StorageContext for an existing dataset ID without
* triggering the expensive "scan all client datasets" hotpath.
*
* This is a workaround for https://github.com/FilOzone/synapse-sdk/issues/435
* until https://github.com/FilOzone/synapse-sdk/pull/438 is merged and published.
*
* Benefits:
* - Reuses Synapse's existing WarmStorageService (no extra initialization)
* - Only fetches data for the single dataset needed (~5-6 RPC calls vs 1500+)
* - Validates ownership, isLive, and isManaged
*
* TODO: Replace with synapse.storage.createContextFromDataSetId() once PR #438 is published
*/
import type { Synapse, WarmStorageService } from '@filoz/synapse-sdk'
import { StorageContext } from '@filoz/synapse-sdk'
import { type ProviderInfo, SPRegistryService } from '@filoz/synapse-sdk/sp-registry'
import type { Logger } from 'pino'
import { DEFAULT_DATA_SET_METADATA, DEFAULT_STORAGE_CONTEXT_CONFIG } from './index.js'
export type StorageContextHelperResult = {
storage: StorageContext
providerInfo: ProviderInfo
}
export type CreateStorageContextFromDataSetIdOptions = {
/**
* Whether to check if the provider is approved or not. We default to false here because for many read operations,
* we might want to re-use the dataset even if it's not owned by an approved provider.
* @default false
*/
checkProviderApproval?: boolean
}
/**
* Create a StorageContext for an existing dataSetId without scanning all datasets.
*/
export async function createStorageContextFromDataSetId(
synapse: Synapse,
dataSetId: number,
options?: CreateStorageContextFromDataSetIdOptions
): Promise<StorageContextHelperResult> {
const { checkProviderApproval = false } = options ?? {}
// Access Synapse's internal WarmStorageService (avoids creating a new one)
// @ts-expect-error - Accessing private _warmStorageService temporarily until SDK is updated
const warmStorage = synapse.storage._warmStorageService
if (!warmStorage) {
throw new Error('WarmStorageService not available on Synapse instance')
}
// Get basic dataset info and validate in parallel
const [dataSetInfo] = await Promise.all([warmStorage.getDataSet(dataSetId), warmStorage.validateDataSet(dataSetId)])
// Verify ownership
const signerAddress = await synapse.getClient().getAddress()
if (dataSetInfo.payer.toLowerCase() !== signerAddress.toLowerCase()) {
throw new Error(`Data set ${dataSetId} is not owned by ${signerAddress} (owned by ${dataSetInfo.payer})`)
}
const registryAddress = warmStorage.getServiceProviderRegistryAddress()
const spRegistry = new SPRegistryService(synapse.getProvider(), registryAddress)
if (checkProviderApproval) {
/**
* If asked to check provider approval and it's not an approved provider, we're going to throw an error.
*/
const isProviderApproved = await warmStorage.isProviderIdApproved(dataSetInfo.providerId)
if (!isProviderApproved) {
throw new Error(`Data set ${dataSetId} is not owned by an approved provider`)
}
}
// Get provider info and metadata in parallel
const [providerInfo, dataSetMetadata] = await Promise.all([
spRegistry.getProvider(dataSetInfo.providerId),
warmStorage.getDataSetMetadata(dataSetId),
])
if (providerInfo == null) {
throw new Error(`Unable to resolve provider info for data set ${dataSetId} and provider ${dataSetInfo.providerId}`)
}
// Construct storage context directly
const withCDN = dataSetInfo.cdnRailId > 0
const storageContext = new StorageContext(synapse, warmStorage, providerInfo, dataSetId, { withCDN }, dataSetMetadata)
return {
storage: storageContext,
providerInfo,
}
}
export type CreateNewStorageContextOptions = {
providerId?: number
providerAddress?: string
warmStorage?: WarmStorageService
spRegistry?: SPRegistryService
logger?: Logger
}
/**
* Create a StorageContext configured for creating a brand new dataset without scanning all
* existing datasets. Provider selection favors explicit overrides and otherwise randomly selects
* an active provider that exposes a PDP endpoint.
*/
export async function createStorageContextForNewDataSet(
synapse: Synapse,
options: CreateNewStorageContextOptions = {}
): Promise<StorageContextHelperResult> {
// @ts-expect-error - Accessing private _warmStorageService temporarily until SDK is updated
const warmStorage = options.warmStorage ?? synapse.storage._warmStorageService
if (!warmStorage) {
throw new Error('WarmStorageService not available on Synapse instance')
}
const registryAddress = warmStorage.getServiceProviderRegistryAddress()
const spRegistry = options.spRegistry ?? new SPRegistryService(synapse.getProvider(), registryAddress)
const providerInfo = await getApprovedProviderInfo(warmStorage, spRegistry, options.providerId, options.logger)
const mergedMetadata = { ...DEFAULT_DATA_SET_METADATA }
const storageOptions = {
...DEFAULT_STORAGE_CONTEXT_CONFIG,
metadata: mergedMetadata,
}
const storageContext = new StorageContext(
synapse,
warmStorage,
providerInfo,
undefined,
storageOptions,
mergedMetadata
)
return {
storage: storageContext,
providerInfo,
}
}
/**
* getApprovedProviderInfo should only be called from createStorageContextForNewDataSet, which is only called with a providerId if a user has explicitly provided one as a query parameter.
*/
async function getApprovedProviderInfo(
warmStorage: WarmStorageService,
spRegistry: SPRegistryService,
providerId?: number,
logger?: Logger
): Promise<ProviderInfo> {
let providerInfo: ProviderInfo | null = null
if (providerId != null) {
// if given a providerId, check if it is approved and log a warning if it's not an approved provider.
const isProviderApproved = await warmStorage.isProviderIdApproved(providerId)
if (!isProviderApproved) {
logger?.warn(
{ providerId },
`Presuming given providerId ${providerId} is a queryParam and allowing creation to continue with a non-approved provider`
)
}
providerInfo = await spRegistry.getProvider(providerId)
} else {
// otherwise, get all approved provider ids and randomly select one.
const approvedProviderIds = await warmStorage.getApprovedProviderIds()
// select a random approved provider id
const randomApprovedProviderId = pickRandom(approvedProviderIds)
providerInfo = await spRegistry.getProvider(randomApprovedProviderId)
}
if (providerInfo == null) {
throw new Error(`Unable to resolve an approved storage provider for new data set creation`)
}
return providerInfo
}
function pickRandom<T>(providerIds: T[]): T {
if (providerIds.length === 0) throw new Error('No approved storage providers available for new data set creation')
return providerIds[Math.floor(Math.random() * providerIds.length)] as T
}
export default createStorageContextFromDataSetId