11import { logger } from '@libp2p/logger'
2- import type { BlockRetriever } from '@helia/interface/blocks'
3- import type { AbortOptions } from 'interface-store'
2+ import type { BlockRetrievalOptions , BlockRetriever } from '@helia/interface/blocks'
43import type { CID } from 'multiformats/cid'
54import type { ProgressEvent , ProgressOptions } from 'progress-events'
65
@@ -10,9 +9,9 @@ const log = logger('helia:trustless-gateway-block-broker')
109 * A `TrustlessGateway` keeps track of the number of attempts, errors, and
1110 * successes for a given gateway url so that we can prioritize gateways that
1211 * have been more reliable in the past, and ensure that requests are distributed
13- * across all gateways within a given `TrustedGatewayBlockBroker ` instance.
12+ * across all gateways within a given `TrustlessGatewayBlockBroker ` instance.
1413 */
15- class TrustlessGateway {
14+ export class TrustlessGateway {
1615 public readonly url : URL
1716 /**
1817 * The number of times this gateway has been attempted to be used to fetch a
@@ -30,6 +29,13 @@ class TrustlessGateway {
3029 */
3130 #errors = 0
3231
32+ /**
33+ * The number of times this gateway has returned an invalid block. A gateway
34+ * that returns the wrong blocks for a CID should be considered for removal
35+ * from the list of gateways to fetch blocks from.
36+ */
37+ #invalidBlocks = 0
38+
3339 /**
3440 * The number of times this gateway has successfully fetched a block.
3541 */
@@ -91,7 +97,7 @@ class TrustlessGateway {
9197 * Unused gateways have 100% reliability; They will be prioritized over
9298 * gateways with a 100% success rate to ensure that we attempt all gateways.
9399 */
94- get reliability ( ) : number {
100+ reliability ( ) : number {
95101 /**
96102 * if we have never tried to use this gateway, it is considered the most
97103 * reliable until we determine otherwise (prioritize unused gateways)
@@ -100,6 +106,11 @@ class TrustlessGateway {
100106 return 1
101107 }
102108
109+ if ( this . #invalidBlocks > 0 ) {
110+ // this gateway may not be trustworthy..
111+ return - Infinity
112+ }
113+
103114 /**
104115 * We have attempted the gateway, so we need to calculate the reliability
105116 * based on the number of attempts, errors, and successes. Gateways that
@@ -110,6 +121,13 @@ class TrustlessGateway {
110121 */
111122 return this . #successes / ( this . #attempts + ( this . #errors * 3 ) )
112123 }
124+
125+ /**
126+ * Increment the number of invalid blocks returned by this gateway.
127+ */
128+ incrementInvalidBlocks ( ) : void {
129+ this . #invalidBlocks++
130+ }
113131}
114132
115133export type TrustlessGatewayGetBlockProgressEvents =
@@ -119,24 +137,39 @@ export type TrustlessGatewayGetBlockProgressEvents =
119137 * A class that accepts a list of trustless gateways that are queried
120138 * for blocks.
121139 */
122- export class TrustedGatewayBlockBroker implements BlockRetriever <
140+ export class TrustlessGatewayBlockBroker implements BlockRetriever <
123141ProgressOptions < TrustlessGatewayGetBlockProgressEvents >
124142> {
125143 private readonly gateways : TrustlessGateway [ ]
126144
127- constructor ( urls : Array < string | URL > ) {
128- this . gateways = urls . map ( ( url ) => new TrustlessGateway ( url ) )
145+ constructor ( gatewaysOrUrls : Array < string | URL | TrustlessGateway > ) {
146+ this . gateways = gatewaysOrUrls . map ( ( gatewayOrUrl ) => {
147+ if ( gatewayOrUrl instanceof TrustlessGateway || Object . prototype . hasOwnProperty . call ( gatewayOrUrl , 'getRawBlock' ) ) {
148+ return gatewayOrUrl as TrustlessGateway
149+ }
150+ // eslint-disable-next-line no-console
151+ console . trace ( 'creating new TrustlessGateway for %s' , gatewayOrUrl )
152+ return new TrustlessGateway ( gatewayOrUrl )
153+ } )
129154 }
130155
131- async retrieve ( cid : CID , options : AbortOptions & ProgressOptions < TrustlessGatewayGetBlockProgressEvents > = { } ) : Promise < Uint8Array > {
156+ async retrieve ( cid : CID , options : BlockRetrievalOptions < ProgressOptions < TrustlessGatewayGetBlockProgressEvents > > = { } ) : Promise < Uint8Array > {
132157 // Loop through the gateways until we get a block or run out of gateways
133- const sortedGateways = this . gateways . sort ( ( a , b ) => b . reliability - a . reliability )
158+ const sortedGateways = this . gateways . sort ( ( a , b ) => b . reliability ( ) - a . reliability ( ) )
134159 const aggregateErrors : Error [ ] = [ ]
135160 for ( const gateway of sortedGateways ) {
136161 log ( 'getting block for %c from %s' , cid , gateway . url )
137162 try {
138163 const block = await gateway . getRawBlock ( cid , options . signal )
139164 log . trace ( 'got block for %c from %s' , cid , gateway . url )
165+ try {
166+ await options . validateFn ?.( block )
167+ } catch ( err ) {
168+ log . error ( 'failed to validate block for %c from %s' , cid , gateway . url , err )
169+ gateway . incrementInvalidBlocks ( )
170+
171+ throw new Error ( `unable to validate block for CID ${ cid } from gateway ${ gateway . url } ` )
172+ }
140173
141174 return block
142175 } catch ( err : unknown ) {
0 commit comments