@@ -584,24 +584,44 @@ export class StorageService {
584
584
if ( b . currentRootCount > 0 && a . currentRootCount === 0 ) return 1
585
585
return a . pdpVerifierProofSetId - b . pdpVerifierProofSetId
586
586
} )
587
- const selected = sorted [ 0 ]
588
587
589
- // Get the provider for this proof set
590
- const providerId = await pandoraService . getProviderIdByAddress ( selected . payee )
591
- if ( providerId === 0 ) {
592
- throw createError (
593
- 'StorageService' ,
594
- 'smartSelectProvider' ,
595
- `Provider ${ selected . payee } for proof set ${ selected . pdpVerifierProofSetId } is not currently approved`
596
- )
588
+ // Convert sorted proof sets to providers and use shared selection logic
589
+ const existingProviders : ApprovedProviderInfo [ ] = [ ]
590
+ for ( const proofSet of sorted ) {
591
+ const providerAddress = proofSet . payee . toLowerCase ( )
592
+ if ( existingProviders . some ( p => p . owner . toLowerCase ( ) === providerAddress ) ) {
593
+ continue
594
+ }
595
+ const providerId = await pandoraService . getProviderIdByAddress ( proofSet . payee )
596
+ if ( providerId === 0 ) {
597
+ console . warn ( `Provider ${ proofSet . payee } for proof set ${ proofSet . pdpVerifierProofSetId } is not currently approved, skipping` )
598
+ continue
599
+ }
600
+ const provider = await pandoraService . getApprovedProvider ( providerId )
601
+ existingProviders . push ( provider )
597
602
}
598
603
599
- const provider = await pandoraService . getApprovedProvider ( providerId )
604
+ if ( existingProviders . length > 0 ) {
605
+ const selectedProvider = await StorageService . selectProviderWithPing ( existingProviders )
600
606
601
- return {
602
- provider,
603
- proofSetId : selected . pdpVerifierProofSetId ,
604
- isExisting : true
607
+ // Find the first matching proof set ID for this provider
608
+ const matchingProofSet = sorted . find ( ps =>
609
+ ps . payee . toLowerCase ( ) === selectedProvider . owner . toLowerCase ( )
610
+ )
611
+
612
+ if ( matchingProofSet == null ) {
613
+ throw createError (
614
+ 'StorageService' ,
615
+ 'smartSelectProvider' ,
616
+ 'Selected provider not found in proof sets'
617
+ )
618
+ }
619
+
620
+ return {
621
+ provider : selectedProvider ,
622
+ proofSetId : matchingProofSet . pdpVerifierProofSetId ,
623
+ isExisting : true
624
+ }
605
625
}
606
626
}
607
627
@@ -627,33 +647,90 @@ export class StorageService {
627
647
}
628
648
629
649
/**
630
- * Select a random provider from the given list
650
+ * Select a random provider from the given list with ping validation
651
+ * @param providers - List of available providers
652
+ * @param signer - Signer for entropy generation
653
+ * @returns A provider that responds to ping
654
+ * @throws Error if no providers are reachable
631
655
*/
632
656
private static async selectRandomProvider (
633
657
providers : ApprovedProviderInfo [ ] ,
634
658
signer : ethers . Signer
635
659
) : Promise < ApprovedProviderInfo > {
636
- let randomIndex : number
660
+ if ( providers . length === 0 ) {
661
+ throw createError (
662
+ 'StorageService' ,
663
+ 'selectRandomProvider' ,
664
+ 'No providers available'
665
+ )
666
+ }
637
667
638
- // Try crypto.getRandomValues if available (HTTPS contexts)
639
- if ( typeof globalThis . crypto !== 'undefined' && globalThis . crypto . getRandomValues != null ) {
640
- const randomBytes = new Uint8Array ( 1 )
641
- globalThis . crypto . getRandomValues ( randomBytes )
642
- randomIndex = randomBytes [ 0 ] % providers . length
643
- } else {
644
- // Fallback for HTTP contexts - use multiple entropy sources
645
- const timestamp = Date . now ( )
646
- const random = Math . random ( )
647
- // Use wallet address as additional entropy
648
- const addressBytes = await signer . getAddress ( )
649
- const addressSum = addressBytes . split ( '' ) . reduce ( ( a , c ) => a + c . charCodeAt ( 0 ) , 0 )
668
+ // Randomly sort providers inline
669
+ const randomlySorted : ApprovedProviderInfo [ ] = [ ]
670
+ const remaining = [ ...providers ]
671
+
672
+ while ( remaining . length > 0 ) {
673
+ let randomIndex : number
674
+
675
+ // Try crypto.getRandomValues if available (HTTPS contexts)
676
+ if ( typeof globalThis . crypto !== 'undefined' && globalThis . crypto . getRandomValues != null ) {
677
+ const randomBytes = new Uint8Array ( 1 )
678
+ globalThis . crypto . getRandomValues ( randomBytes )
679
+ randomIndex = randomBytes [ 0 ] % remaining . length
680
+ } else {
681
+ // Fallback for HTTP contexts - use multiple entropy sources
682
+ const timestamp = Date . now ( )
683
+ const random = Math . random ( )
684
+ // Use wallet address as additional entropy
685
+ const addressBytes = await signer . getAddress ( )
686
+ const addressSum = addressBytes . split ( '' ) . reduce ( ( a , c ) => a + c . charCodeAt ( 0 ) , 0 )
687
+
688
+ // Combine sources for better distribution
689
+ const combined = ( timestamp * random * addressSum ) % remaining . length
690
+ randomIndex = Math . floor ( Math . abs ( combined ) )
691
+ }
692
+
693
+ randomlySorted . push ( remaining . splice ( randomIndex , 1 ) [ 0 ] )
694
+ }
695
+
696
+ return await StorageService . selectProviderWithPing ( randomlySorted )
697
+ }
698
+
699
+ /**
700
+ * Select a provider from a sorted list with ping validation.
701
+ * This is shared logic used by both smart selection and random selection.
702
+ * @param sortedProviders - List of providers to try in order
703
+ * @returns A provider that responds to ping
704
+ * @throws Error if no providers are reachable
705
+ */
706
+ private static async selectProviderWithPing ( sortedProviders : ApprovedProviderInfo [ ] ) : Promise < ApprovedProviderInfo > {
707
+ if ( sortedProviders . length === 0 ) {
708
+ throw createError (
709
+ 'StorageService' ,
710
+ 'selectProviderWithPing' ,
711
+ 'No reachable storage providers available after ping validation'
712
+ )
713
+ }
650
714
651
- // Combine sources for better distribution
652
- const combined = ( timestamp * random * addressSum ) % providers . length
653
- randomIndex = Math . floor ( Math . abs ( combined ) )
715
+ // Try providers in order until we find one that responds to ping
716
+ for ( const provider of sortedProviders ) {
717
+ try {
718
+ // Create a temporary PDPServer for this specific provider's endpoint
719
+ const providerPdpServer = new PDPServer ( null , provider . pdpUrl , provider . pieceRetrievalUrl )
720
+ await providerPdpServer . ping ( )
721
+ return provider
722
+ } catch ( error ) {
723
+ console . warn ( `Provider ${ provider . owner } failed ping test:` , error instanceof Error ? error . message : String ( error ) )
724
+ // Continue to next provider
725
+ }
654
726
}
655
727
656
- return providers [ randomIndex ]
728
+ // All providers failed ping test
729
+ throw createError (
730
+ 'StorageService' ,
731
+ 'selectProviderWithPing' ,
732
+ `All ${ sortedProviders . length } available storage providers failed ping validation`
733
+ )
657
734
}
658
735
659
736
/**
0 commit comments