/** * ChainRetriever - Queries on-chain data to find and retrieve pieces * * This retriever uses the Warm Storage service to find service providers * that have the requested piece, then attempts to download from them. */ import type { SPRegistryService } from '../sp-registry/index.ts' import type { PieceCID, PieceRetriever, ProviderInfo } from '../types.ts' import { createError } from '../utils/index.ts' import type { WarmStorageService } from '../warm-storage/index.ts' import { fetchPiecesFromProviders } from './utils.ts' export class ChainRetriever implements PieceRetriever { private readonly warmStorageService: WarmStorageService private readonly childRetriever?: PieceRetriever private readonly spRegistry: SPRegistryService constructor(warmStorageService: WarmStorageService, spRegistry: SPRegistryService, childRetriever?: PieceRetriever) { this.warmStorageService = warmStorageService this.spRegistry = spRegistry this.childRetriever = childRetriever } /** * Find providers that can serve pieces for a client * @param client - The client address * @param providerAddress - Optional specific provider to use * @returns List of provider info */ private async findProviders(client: string, providerAddress?: string): Promise { if (providerAddress != null) { // Direct provider case - skip data set lookup entirely const provider = await this.spRegistry.getProviderByAddress(providerAddress) if (provider == null) { throw createError('ChainRetriever', 'findProviders', `Provider ${providerAddress} not found in registry`) } return [provider] } // Multiple provider case - need data sets to find providers // Get client's data sets with details const dataSets = await this.warmStorageService.getClientDataSetsWithDetails(client) // Filter for live data sets with pieces const validDataSets = dataSets.filter((ds) => ds.isLive && ds.currentPieceCount > 0) if (validDataSets.length === 0) { throw createError('ChainRetriever', 'findProviders', `No active data sets with data found for client ${client}`) } // Get unique provider IDs from data sets (much more reliable than using payee addresses) const uniqueProviderIds = [...new Set(validDataSets.map((ds) => ds.providerId))] // Batch fetch provider info for all unique provider IDs const providerInfos = await this.spRegistry.getProviders(uniqueProviderIds) // Filter out null values (providers not found in registry) const validProviderInfos = providerInfos.filter((info): info is ProviderInfo => info != null) if (validProviderInfos.length === 0) { throw createError( 'ChainRetriever', 'findProviders', 'No valid providers found (all providers may have been removed from registry or are inactive)' ) } return validProviderInfos } async fetchPiece( pieceCid: PieceCID, client: string, options?: { providerAddress?: string withCDN?: boolean signal?: AbortSignal } ): Promise { // Helper function to try child retriever or throw error const tryChildOrThrow = async (reason: string): Promise => { if (this.childRetriever !== undefined) { return await this.childRetriever.fetchPiece(pieceCid, client, options) } throw createError('ChainRetriever', 'fetchPiece', `Failed to retrieve piece ${pieceCid.toString()}: ${reason}`) } // Find providers let providersToTry: ProviderInfo[] = [] try { providersToTry = await this.findProviders(client, options?.providerAddress) } catch (error) { // Provider discovery failed - this is a critical error const message = error instanceof Error ? error.message : 'Provider discovery failed' return await tryChildOrThrow(message) } // If no providers found, try child retriever if (providersToTry.length === 0) { return await tryChildOrThrow('No providers found and no additional retriever method was configured') } // Try to fetch from providers try { return await fetchPiecesFromProviders(providersToTry, pieceCid, 'ChainRetriever', options?.signal) } catch { // All provider attempts failed return await tryChildOrThrow( 'All provider retrieval attempts failed and no additional retriever method was configured' ) } } }